티스토리 뷰

개요

만약 내가 작성한 모든 메써드의 실행 전후로 로그를 남기고 싶다면? 데이터베이스에 대한 쓰기 작업 전후의 트랜잭션 관리를 일일이 명시하지 않고 자동으로 하고 싶다면? 전통적인 클래스 관점의 OOP 세계에서는 깔끔하게 대응하기가 쉽지 않다. 답은 Aspect 관점의 AOP(Aspect Oriented Programming)를 도입하는 것이다. Java 진영에는 AspectJ라는 훌륭한 AOP 프레임워크가 존재하며 이를 이용하여 개발자는 커스텀 어노테이션을 적용한 자신 만의 편리한 Aspect를 작성할 수 있다.(AspectJ의 훌륭한 적용 사례 중 하나는 jcabi-aspects 라이브러리이다.) Spring 또한 AspectJ 스타일을 수용한 프록시 기반의 Spring AOP를 제공한다. 대상 클래스의 메써드 단위로만 적용 가능한 제한적인 AOP를 제공하지만 일반적인 상황에서 사용하는 데는 충분하다.

라이브러리 종속성 추가

dependencies {
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop'
}
  • Spring Boot 기반 애플리케이션에서 Spring AOP를 사용하려면 spring-boot-starter-aop 아티팩트를 추가하면 된다.

@EnableAspectAutoProxy

@SpringBootApplication
@EnableAspectAutoProxy
public class SomeApplication {

    public static void main(String[] args) {

        ApplicationContext ctx = SpringApplication.run(Application.class, args);
    }
}
  • @SpringBootApplication 또는 @Configuration을 명시한 클래스에 @EnableAspectAutoProxy를 명시하면 Spring AOP를 사용하기 위한 첫 준비가 끝난다.
  • @EnableAspectAutoProxyXML 기반의 ApplicationContext 설정에서의 <aop:aspectj-autoproxy />와 동일한 기능을 한다.

@Aspect

@Component
@Aspect
@Order(Ordered.LOWEST_PRECEDENCE)
public class SomeAspect {

    // Aspect 메써드 작성
}
  • 스프링 빈에 @Aspect를 명시하면 해당 빈이 Aspect로 작동한다.
  • 클래스 레벨에 @Order를 명시하여 @Aspect 빈 간의 작동 순서를 정할 수 있다. int 타입의 정수로 순서를 정할 수 있는데 값이 낮을수록 우선순위가 높다. 기본값은 가장 낮은 우선순위를 가지는 Ordered.LOWEST_PRECEDENCE이다.
  • @Aspect가 명시된 빈에는 어드바이스(Advice)라 불리는 메써드를 작성할 수 있다. 대상 스프링 빈의 메써드의 호출에 끼어드는 시점과 방법에 따라 @Before, @After, @AfterReturning, @AfterThrowing, @Around 등을 명시할 수 있다.

@Before

@Aspect 클래스의 메써드 레벨에 @Before 어드바이스를 명시하면 대상 메써드의 실행 전에 끼어 들어 원하는 작업을 할 수 있다. 끼어들기만 할 뿐 대상 메써드의 제어나 가공은 불가능하다.

@Before("execution(* com.jsonobject.example.*.*(..))")
public void doSomethingBefore(JoinPoint joinPoint) {

    // 끼어들어 실행할 로직을 작성
}
  • 어드바이스에 작성된 파라메터는 PointCut이라 부르는 표현식이다. 끼어들 메써드의 범위를 지정할 수 있다.
execution(* com.jsonobject.example.*.*(..))

*                     : 모든 리턴 타입
com.jsonobject.example: 특정 패키지
*                     : 모든 클래스
*                     : 모든 메써드
..                    : 모든 아규먼트
  • 아규먼트로 전달 받는 JoinPoint 오브젝트는 끼어든 메써드의 정보를 담고 있다.

@After

@After 어드바이스를 명시하면 대상 메써드의 실행 후에 끼어 들어 원하는 작업을 할 수 있다. 역시 끼어들기만 할 뿐 대상 메써드의 제어나 가공은 불가능하다.

@After("execution(* com.jsonobject.example.*.*(..))")
public void doSomethingAfter(JoinPoint joinPoint) {

    // 끼어들어 실행할 로직을 작성
}

@AfterReturning

@Aspect 클래스의 메써드 레벨에 @AfterReturning을 명시하면 해당 메써드의 실행이 종료되어 값을 리턴할 때 끼어 들 수 있다. 리턴 값을 확인할 수 있을 뿐 대상 메써드의 제어나 가공은 불가능하다.

@AfterReturning(pointcut = "execution(* com.jsonobject.example.*.*(..))", returning = "result")
public void doSomethingAfterReturning(JoinPoint joinPoint, Object result) {

    // 끼어들어 실행할 로직을 작성
}

@Around

@Around 어드바이스는 앞서 설명한 어드바이스의 기능을 모두 포괄하는 종합선물세트와도 같다. 대상 메써드를 감싸는 느낌으로 실행 전후 시점에 원하는 작업을 할 수 있다. 대상 메써드의 실행 제어 및 리턴 값 가공도 가능하다.

// 특정 어노테이션이 명시된 모든 메써드의 실행 전후로 끼어들 수 있다.
@Around("@annotation(someAnnotation)")
public Object doSomethingAround(final ProceedingJoinPoint joinPoint, final SomeAnnotation someAnnotation) {

    // 대상 메써드 실행 전 끼어들어 실행할 로직을 작성

    Object result = joinPoint.proceed();

    // 대상 메써드 실행 후 끼어들어 실행할 로직을 작성, 리턴 값을 가공할 수 있다.

    return result;
}
// 메써드 레벨에 특정 어노테이션이 명시된 모든 메써드의 실행 전후로 끼어들 수 있다.
@Around("@annotation(scheduled)")
public Object process(final ProceedingJoinPoint joinPoint, final Scheduled scheduled) throws Throwable {

// 클래스 레벨에 특정 어노테이션이 명시된 모든 메써드의 실행 전후로 끼어들 수 있다.
@Around("@within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Service) || @within(org.springframework.stereotype.Repository)")
public Object process(final ProceedingJoinPoint joinPoint) throws Throwable {

참고 글

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함