1) 스프링 AOP?
- 애플리케이션은 핵심 기능과 부가 기능으로 이루어진다고 할 수 있다.
- 핵심 기능들은 구현 로직간에 공통된 부분이 거의 없지만, 부가 기능의 경우 말그대로 부가적인 기능이기 때문에 동일한 코드가 사용되기 쉽다. 무지성으로 부가 기능을 적용하다보면 전방위 적으로 중복코드가 생겨날 위험이 있다. 즉, 유지보수와 변경 사항이 생기면 이를 반영하는 데에 큰 어려움이 생긴다.
- 그래서 이러한 핵심기능과 부가기능을 분리하는 데에 집중한 프로그래밍이 AOP, 즉 관점지향 프로그래밍이다.
- Aspect 프레임워크가 AOP 기능의 모든 것을 담고 있는데, 스프링에선 이 중에 일부 기능만을 지원한다.
- Aspect AOP의 기능은 총 3가지(컴파일 시점, 클래스 로딩 시점, 런타임 시점)이지만, 스프링 AOP는 런타임 시점의 기능만을 제공한다.
- 런타임 AOP란 프록시를 통해 부가 기능이 적용되는 방식으로, 스프링 AOP는 항상 프록시를 통해야 부가 기능을 사용할 수 있다. (참고로 스프링 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시이다.)
- 즉, 스프링 AOP는 메소드 실행 지점에만 AOP를 적용할 수 있다.
- 프록시는 메소드 오버라이딩 개념으로 동작하기 때문이다. (생성자나 static 메소드, 필드 값 접근에는 프록시 개념이 적용될 수 없다.)
- 또한 스프링 AOP는 스프링 컨테이너가 관리할 수 있는 스프링 빈에만 AOP를 적용할 수 있다.
2) AOP 용어
- 타겟 (Target)
- 프록시 (Proxy)
- 타겟을 감싸서 타겟의 요청을 대신 받아주는 랩핑 객체이다.
- 타겟을 호출하게 되면 타겟이 아닌 타겟을 감싸고 있는 프록시가 호출되어, 타겟 메소드 실행 전에 “선처리”, 타겟 메소드 실행 후에 “후처리”를 수행하도록 구성되어 있다.
- 어드바이스 (Advice)
- 실질적인 부가기능을 담은 구현 메소드이다.
- @Before, @After, @Around 등이 붙은 메소드를 뜻한다고 볼 수 있다.
- 포인트컷 (PointCut)
- 부가기능이 적용될 대상 메소드(조인포인트)를 선별하는 기능(메소드) 이다.
- @PointCut이 붙은 메소드라고 할 수 있다.
- 조인포인트 (JoinPoint)
- 어드바이스가 적용될 수 있는 위치이다.
- 풀어이야기 하면, AOP를 적용할 수 있는 모든 지점, 즉 AOP를 끼워 넣을 수 있는 지점이다.
- 스프링 AOP에선 메소드 조인포인트만 제공하기 때문에, 조인포인트는 항상 부가기능 메소드의 실행지점이라고 봐도 무방하다.
- 에스펙트 (Aspect)
- 부가기능의 모듈화 객체이다. 어드바이스 + 포인트컷을 모듈화한 것 => @Aspect라고 생각하면 된다.
3) 스프링 AOP를 적용한 프로젝트 코드 예시 - 1
@Aspect
@Component
class ValidationAspect {
private val log = LoggerFactory.getLogger(java.class)
// @After("execution(* beyond.crud_sql.controller..*(..))")
// @Before("execution(* beyond.crud_sql.controller..*(..))")
@Around("execution(* beyond.crud_sql.controller..*(..))")
fun doValidation(joinPoint: ProceedingJoinPoint) {
val args = joinPoint.args
log.info("[trace] {} args={}", joinPoint.signature, args)
for (arg in args) {
if (arg is BindingResult) {
if (arg.fieldErrors.size > 0) {
val errors = mutableMapOf<String?, MutableList<String>?>()
for (e in arg.fieldErrors) {
if (errors[e.field] == null) errors[e.field] = mutableListOf()
errors[e.field]!!.add(e.defaultMessage!!)
}
throw ClassValidatorException("유효성 검사 에러입니다.", errors)
}
}
}
joinPoint.proceed() // @Around에서는 항상 필요 (그래야 조인 포인트가 실행이 됨)
}
}
- @Aspect와 @Component를 클래스 위에 붙이면, 이제 애플리케이션 AOP 기능을 적용할 수 있는 에스팩트 객체가 된 것이다. 말했듯 에스팩트 객체란 어드바이스 + 포인트컷을 모듈화 한 것이다.
- @Before, @After, @Around 등은 어드바이스의 일부이자, 부가기능을 실행할 시점(조인포인트 실행 시점)을 결정하는 에노테이션이다.
- @Before: 타겟 메소드 호출 이전에 어드바이스 기능을 실행
- @After: 타겟 메소드 호출 결과와 상관없이, 반드시 마지막에 어드바이스 기능을 실행 (마치 finally 처럼)
- @AfterReturning : 타겟 메소드가 정상적으로 리턴(완료)된 후 어드바이스 기능을 실행
- @AfterThrowing : 타겟 메소드가 예외를 던지는 경우에 어드바이스 기능을 실행
- @Around는 만능으로서 타겟 메소드 호출 전후에 실행, 조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등도 가능
- 사실 @Around 를 제외한 나머지 에노테이션은 @Around 가 할 수 있는 일의 일부만 제공할 뿐이다. 따라서 @Around만 사용해도 필요한 기능을 모두 수행할 수 있다.
- 단, @Around 는 파라미터에 JoinPoint가 아닌, ProceedingJoinPoint 을 사용해야 한다.