본문 바로가기

소프트웨어

Spring AOP (Aspect Oriented Programming)

반응형

Spring을 공부하면서 AOP가 무엇인지 간단하게 알아봤습니다

 

구글에 AOP를 검색하면 이런 이미지를 볼 수 있습니다

출처 : https://velog.io/@max9106/Spring-AOP란-93k5zjsm95

이미지를 봐도 알 수 있듯이 클래스마다 비슷한 부분들이 있고 그 비슷한 부분들을 묶는 개념이라는 걸 알 수 있죠. 다수의 클래스에 같은 기능을 횡단으로 적용하는 것입니다.

 

무슨 말인지 잘 모르겠으니 바로 예제를 살펴보겠습니다

 

먼저, 간단한 Controller를 만들겠습니다

 

package com.zziri.test.controller;
// import ...

@RestController
@RequestMapping("/api")
public class ApiController {
    @GetMapping("/aop")
    public String aop(@RequestParam String param) {
        return param;
    }
}

 

aop() 메서드를 호출하려면 이런 식으로 GET 요청을 하면 되겠죠

http://localhost:8080/api/aop?param=test

 

이제, AOP 기능을 구현할 클래스를 만들겠습니다

 

@Aspect
@Component
public class ParameterAop {
    @Pointcut("execution(* com.zziri.test.controller..*.*(..))")
    private void cut() {}

    @Before("cut()")
    public void before(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        System.out.println(String.format("method = %s", methodSignature.getMethod()));
        Arrays.stream(joinPoint.getArgs()).forEach(arg -> {
            System.out.println(String.format("arg = %s", arg.toString()));
        });
    }

    @AfterReturning(value = "cut()", returning = "returnObj")
    public void afterReturn(JoinPoint joinPoint, Object returnObj) {
        System.out.println(String.format("returnObj = %s", returnObj));
    }
}

특정 패키지의 모든 메서드에 횡단으로 적용되는 AOP 입니다. 메서드 호출 전 메소드 이름과 arguments를 출력하고 반환 후에 returnObj를 출력하는 간단한 로직입니다.

 

코드를 간단하게 살펴보겠습니다

 

@Component

-> 빈에 등록해서 AOP가 동작하도록 합니다

 

@Pointcut("execution(* com.zziri.test.controller..*.*(..))")

-> com.zziri.test.controller 패키지의 파라미터가 0개 이상인 모든 메서드를 대상으로 합니다

 

@Before("cut()")

-> cut() Pointcut 이전에 호출하는 메서드를 지정합니다

 

@AfterReturning(value = "cut()", returning = "returnObj")

-> cut() Pointcut이 적용된 메서드 호출 이후에 실행합니다

 

 

패키지를 기준으로 모든 메서드를 대상으로 AOP 적용을 했는데요, 어노테이션 기반으로 AOP를 적용하도록 클래스를 하나 더 만들겠습니다

 

그 전에 어노테이션을 먼저 만들어야겠습니다

 

package com.zziri.test.annotation;
// import ...

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}

 

@Aspect
@Component
public class TimerAop {

    @Pointcut("@annotation(com.zziri.test.annotation.Timer)")
    private void enableTimer() {

    }

    @Around("enableTimer()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        proceedingJoinPoint.proceed();
        stopWatch.stop();
        System.out.println(String.format("total time : %s", stopWatch.getTotalTimeSeconds()));
    }
}

@Timer annotation이 적용된 모든 메서드에 횡단으로 적용되는 AOP 입니다. 메소드 실행 시간을 측정해서 출력하는 기능입니다.

 

코드를 살펴보겠습니다

 

@Pointcut("@annotation(com.zziri.test.annotation.Timer)")

-> com.zziri.test.annotation.Timer 어노테이션이 적용된 메서드를 대상으로 합니다

 

@Around("enableTimer()")

-> enableTimer() Pointcut이 적용된 메서드를 @Around가 적용된 메서드 안에서 호출할 수 있도록 합니다. 주로 메서드 호출 전 후 동작을 하나의 메서드에 구현할 필요가 있을 때 사용합니다.

 

 

구현한 기능은 aop() 메서드 호출 전 어떤 메서드인지 출력하고 aop() 메서드의 arguments를 출력한 다음에 메서드 실행 시간을 출력하고 returnObj 출력 후에 끝날겁니다

 

이제 실행을 해봐야겠죠.... 그런데

 

method = public java.lang.String com.zziri.test.controller.ApiController.aop(java.lang.String)
arg = test
total time : 0.0203523
returnObj = null

 

returnObj가 null이 나오는건 왜일까요

 

aop() 메서드는 분명 Request parameter로 받은 param을 return 했을 겁니다. 그런데 @Around로 감싸져서 return value가 @AfterReturning이 적용된 메서드에 제대로 전달되지 않아서 생긴 문제죠.

 

그래서 코드를 수정했습니다

 

@Aspect
@Component
public class TimerAop {

    @Pointcut("@annotation(com.zziri.test.annotation.Timer)")
    private void enableTimer() {}

    @Around("enableTimer()")
    // Object return
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // Get return value
        Object ret = proceedingJoinPoint.proceed();
        stopWatch.stop();
        System.out.println(String.format("total time : %s", stopWatch.getTotalTimeSeconds()));
        // return
        return ret;
    }
}

 

method = public java.lang.String com.zziri.test.controller.ApiController.aop(java.lang.String)
arg = test
total time : 0.0171948
returnObj = test

 

제대로 출력되네요!

 

AOP를 알아보고 @Around 와 @AfterReturning을 함께 사용할 때 주의할 점도 알 수 있게 되었습니다

반응형