Spring Framework - Aspect 생성 및 적용

AOP

AOP(Aspect Oriented Programming)의 목적은 횡단 관심사(cross cutting concern)를 한 군데로 모아 관리하는 것이다. 보안, 트랜잭션 관리는 여러 부분에서 필요로 하지만 서비스 로직 자체는 아니기 때문에 섞이게 되면 코드의 재사용성이 떨어지고 코드의 중복이 발생해 유지보수 비용이 높아진다.

AOP는 횡단 관심사를 Aspect로 모듈화한다. 전체 애플리케이션에 걸쳐 어려 군데에서 필요로 하는 기능을 하나의 클래스로 모은다. 서비스 모듈들의 코드가 보안과 트랜잭션 같은 횡단 관심사에 대한 코드와 분리된다.

AOP에서 사용되는 주요 개념은 다음과 같다.

  • Advice - 언제, 무엇을 할 지에 대한 정보이다.
    • 메서드 호출 전/후/전후 등, 무엇을 할 지는 메서드로 정의한다.
  • Pointcut - Advice를 적용할 구체적인 위치이다.
    • 메서드 호출에 대한 joinpoint인 경우 메서드의 signature
  • Joinpoint - Advice가 적용될 수 있는 위치의 종류이다.
    • 메서드 호출, 생성자 호출, 예외 발생, 필드 값 수정 등

스프링에서는 동적 프록시를 통해 AOP를 구현하기 때문에 메서드에 대한 Joinpoint만 지원한다. 컨테이너가 Bean을 생성한 직후 BeanPostProcessor의 콜백을 호출하여 Bean을 감싼 프록시 객체가 생성되는 방식이다.

메서드 외의 joinpoint가 필요하다면 AspectJ나 JBoss같은 AOP 프레임워크를 사용하면 된다.

Aspect 작성

아래의 코드는 상품 공급 시스템 프로젝트에서 DAO 객체가 메서드를 수행할 때 로그를 출력하는 Aspect를 작성한 것이다.

package com.example.tranche.global.common.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class Logger {

    @AfterReturning(value = "execution(* com.example.tranche.domain.member.dao.MemberDao.insertNewMembersRole(..)) ||" +
            "execution(* com.example.tranche.domain.member.dao.MemberDao.insertNewMember(..))",
            returning = "result")
    // 매개변수의 순서가 바뀌면 에러 발생
    public void printExecutionResult(JoinPoint joinPoint, Object result) {
        System.out.println("🚨" + joinPoint.getSignature().getName() + result);
    }
}
  • @Aspect - 해당 클래스가 Aspect임을 나타낸다.
  • @AfterReturning - 해당 어드바이스가 execution으로 지정된 pointcut(메서드)가 반환될때 실행됨을 의미한다.
    • @AfterReturning 애너테이션을 사용하면 어드바이스할 메서드의 반환값(Object result)에 접근할 수 있다.
  • execution - pointcut을 지정한다. 스프링 AOP는 Aspectj의 pointcut 표현식을 따른다. 정규 표현식을 사용할 수도 있고 예시 코드처럼 구체적인 메서드 이름을 명시할 수 있다.
  • Joinpoint - Jointpoint 타입의 매개변수에는 joinpoint(메서드)에 대한 정보를 담고 있는 객체가 주입된다.

Auto-proxying

Aspect를 작성했다면 해당 객체를 Aspect proxy로 만들기 위해 Bean 중에 Aspect Proxy가 있음을 스프링 컨테이너에게 알려줘야 한다.

Auto-proxying을 활성화 해줘야 한다. Auto-proxying이 진행되지 않으면 AspectJ 애너테이션이 붙어있더라도 해당 객체는 그냥 Bean일 뿐이다. Auto-proxying을 활성화 하는 방법은 두 가지이다.

  • JavaConfig - Configuration 클래스에 @EnableAsepctJAutoProxy 애너테이션 추가
  • xml - 스프링의 aop 네임스페이스에서 〈aop:aspectj-autoproxy /〉 요소 사용

Formal unbound in pointcut

Auto-proxying을 활성화 했더니 이번엔 이런 에러가 발생했다. 🤔

formal unbound error

에러 메세지에서 “formal unbound in pointcut”이라는 문구는 AOP의 pointcut expression에 문제가 있음을 의미한다. Joinpoint 타입 매개변수가 맨 앞에 오도록 수정해주면 된다.

reference

Comments