개념 소개
관점 지향 프로그래밍(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());
}
}