Spring AOP 프레임워크 핵심 개념 및 활용

개념 소개

관점 지향 프로그래밍(Aspect-Oriented Programming, AOP)은 소프트웨어 설계의 한 패러다임으로, 애플리케이션의 주요 비즈니스 로직과 부가 기능을 분리하여 관리하는 방법이다.

주요 목적

기존 코드 변경 없이도 메서드에 부가적인 기능을 추가할 수 있으며, 코드 중복을 줄이고 유지보수성을 향상시키는 데 있다.

핵심 용어 정리

  • 프록시(Proxy): Spring AOP는 프록시 패턴을 기반으로 구현된다
  • 조인포인트(JoinPoint): 메서드 실행 시점의 특정 지점을 의미하며, Spring AOP에서는 모든 메서드 호출을 조인포인트로 간주
  • 포인트컷(Pointcut): 조인포인트를 필터링하는 표현식으로, 공통 기능이 적용될 대상을 지정
  • 어드바이스(Advice): 포인트컷에서 실행되는 공통 기능을 포함한 코드
  • 어спект(Aspect): 어드바이스와 포인트컷의 결합된 형태
  • 타겟(Target): 프록시로 감싸지는 원본 객체

기초 구현 예제

다음은 Spring AOP를 사용한 기본적인 설정 과정이다.

의존성 설정

Maven 프로젝트의 pom.xml 파일에 다음과 같은 의존성을 추가한다:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.29</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.19</version>
    </dependency>
</dependencies>

DAO 인터페이스 및 구현체

public interface ProductRepository {
    void insert();
    void modify();
}
package com.example.repository.impl;

import com.example.repository.ProductRepository;
import org.springframework.stereotype.Repository;

@Repository
public class ProductRepositoryImpl implements ProductRepository {
    public void insert() {
        System.out.println(System.nanoTime());
        System.out.println("product repository insert ...");
    }
    
    public void modify() {
        System.out.println("product repository modify ...");
    }
}

어드바이스 클래스 작성

package com.example.advice;

import org.springframework.stereotype.Component;

@Component
public class LoggingAdvice {
    public void logEnhancement() {
        System.out.println(System.nanoTime());
        System.out.println("enhancement operation executed");
    }
}

어드바이스 설정 및 포인트컷 정의

package com.example.advice;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class LoggingAdvice {
    @Pointcut("execution(void com.example.repository.ProductRepository.modify())")
    private void pointcutDefinition() {}
    
    @Before("pointcutDefinition()")
    public void logEnhancement() {
        System.out.println(System.nanoTime());
        System.out.println("enhancement operation executed");
    }
}

Spring 설정 클래스

package com.example.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = {"com.example"})
@EnableAspectJAutoProxy
public class AppConfig {
}

실행 클래스

package com.example;

import com.example.config.AppConfig;
import com.example.repository.ProductRepository;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationRunner {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext container = 
            new AnnotationConfigApplicationContext(AppConfig.class);
        
        ProductRepository repo = container.getBean(ProductRepository.class);
        repo.insert();  // 어드바이스 적용 안됨
        repo.modify();  // 어드바이스 적용됨
    }
}

포인트컷 표현식

어드바이스가 적용될 메서드를 지정하기 위한 표현식으로, 표준 형식은 다음과 같다:

execution(접근제어자 반환형 패키지.클래스.메서드(파라미터) 예외)

예시: execution(* com.example.service.*Service.*(..))

와일드카드 설명

  • *: 단일 패키지 또는 파라미터를 매칭
  • ..: 여러 패키지 계층 또는 파라미터를 매칭
  • +: 서브타입 매칭

어드바이스 유형

  • 이전 실행(@Before): 메서드 실행 전에 작동
  • 이후 실행(@After): 메서드 완료 후 무조건 실행
  • 반환 후(@AfterReturning): 성공적으로 반환된 후 실행
  • 예외 발생 후(@AfterThrowing): 예외 발생 시 실행
  • 어라운드(@Around): 메서드 실행 전후 모두 제어 가능

데이터 접근 예제

파라미터 접근:

package com.example.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class ParameterAdvice {
    @Pointcut("execution(* com.example.repository.ProductRepository.findProduct(..))")
    private void parameterPointcut() {}
    
    @Before("parameterPointcut()")
    public void handleParameters(JoinPoint joinPoint) {
        Object[] parameters = joinPoint.getArgs();
        for (Object param : parameters) {
            System.out.println("Parameter: " + param);
        }
    }
}

반환값 처리:

package com.example.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class ReturnAdvice {
    @Pointcut("execution(* com.example.repository.ProductRepository.findProduct(..))")
    private void returnPointcut() {}
    
    @AfterReturning(value = "returnPointcut()", returning = "result")
    public void handleReturn(JoinPoint joinPoint, Object result) {
        System.out.println("Return value: " + result);
    }
}

예외 처리:

package com.example.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class ExceptionAdvice {
    @Pointcut("execution(* com.example.repository.ProductRepository.findProduct(..))")
    private void exceptionPointcut() {}
    
    @AfterThrowing(value = "exceptionPointcut()", throwing = "error")
    public void handleException(JoinPoint joinPoint, Exception error) {
        System.out.println("Exception occurred: " + error.getMessage());
    }
}

태그: spring-framework AOP aspect-oriented-programming proxy-pattern Annotations

10월 9일 21:25에 게시됨