본문 바로가기
프로그래밍/Spring

[Spring] Spring AOP - 실전편

by 사바라다 2020. 6. 23.

안녕하세요. 오늘은 저번 포스팅, 기본 이론편에 이어서 Spring AOP에 대해서 실제 적용을 해보며 알아보는 시간을 가지도록 하겠습니다. 저번 포스팅에서 배운 이론이 어떻게 적용되는지 눈으로 확인하시며 쉽게 이해가 되셨으면 좋겠습니다.

AOP 적용 타입

Spring AOP는 아래와 같은 type의 Advice를 제공합니다.

@Before

Before는 target 메서드가 실행되기 전에 Advice가 실행됩니다. target이 실행되지 못하도록 막는 방법은 가지고 있지 않습니다. (exception을 발생시키면 되기는 합니다.)

@After

After는 target 메서드가 실행된 후에 Advice가 실행됩니다. 정상적으로 메서드가 마무리되든 비정상적으로 exception이 발생하든 무조건 실행되는 Aspect입니다..

@Around

Around는 traget 메서드를 감싸는 Advice입니다. 즉, 앞과 뒤에 모두 영향을 미칠 수 있습니다. 또한 Around는 target을 실행할 지 아니면 바로 반환할지도 정할 수 있습니다. Advice 중 가장 강력하다고 할 수 있겠습니다.

@AfterReturning

AfterReturning은 target 메서드가 정상적으로 끝낫을 경우 실행되는 Advice입니다.

@AfterThrowing

AfterThrowing은 target 메서드에서 throwing이 발생했을 때 실행되는 Advice입니다.

Spring AOP의 적용 Type을 알아봤으니 이제 실제로 적용을 해보도록 하겠습니다. 적용할 예제는 기본 이론편에서 살펴본 예제를 실제로 개발해보는 것으로 하겠습니다. 예제는 아래와 같습니다. 아래의 예제에서 timeCheck 메서드의 기능을 AOP로 개발하는 것이 목표입니다.

public void timeCheck() {
    Stopwatch stopwatch = Stopwatch.createStarted();
    doSomething();
    stopwatch.stop();
    log.info("time : " + stopwatch.elapsed(MILLISECONDS));
}

public void doSomething() {
    ...
}

개발 환경

제가 진행하는 프로젝트의 주요 환경입니다.

  • Java 8
  • Spring Boot 2.3.1
  • gradle 6.5

의존성

Spring은 AOP를 쉽게 적용할 수 있도록 도와주는 dependency를 제공합니다. gradle 버전에 따라 아래와 같이 의존성을 추가해 주시면됩니다.

compile 'org.springframework.boot:spring-boot-starter-aop:2.3.1.RELEASE'
implementation ('org.springframework.boot:spring-boot-starter-aop:2.3.1.RELEASE')

Spring Aspect 활성화

Spring AOP를 사용하기 위해서는 의존성 주입 후 Aspect를 사용한다고 활성화 해야합니다. 그러기 위해서는 @Configuration@EnableAspectAutoProxy를 추가해야합니다. 저는 @SpringBootApplication에도 @Configuration내부에 붙어 있으므로 아래처럼 하였습니다.

@SpringBootApplication
@EnableAspectJAutoProxy
public class AOPApplication {
    public static void main(String[] args) {
        SpringApplication.run(AOPApplication.class, args);
    }
}

 

 

# 덧글로 문의가 들어와 확인결과 @SpringBootApplication안에 @EnableAutoConfiguration에 포함되어있는것을 확인하였습니다. @EnableAutoConfiguration 으로 사용하는 AOP 방식이 CGLIB로 되어있습니다. JDK dynamic proxy를 사용하고자 할 때 @EnableAspectJAutoProxy을 추가하여 커스텀해주면 될 것 같습니다. AOP 방식에 대해서는 원리편에서 다루고 있습니다.

Target 선언하기

Advice를 적용 받을 target 메서드를 아래와 같이 선언하겠습니다.

@Component
public class Target {
    public void doSomething() {
        System.out.println("=== 작업 중 ===");
    }
}

Aspect 선언하기

사전 작업이 완료됬으니 이제 Aspect를 만들어보도록 하겠습니다. 먼저 Aspect를 선언해야합니다. Aspect로 이용하기 위해서는 bean으로 등록이 필요합니다. 하지만 @Aspect만으로는 bean 등록이 되지 않습니다. 따라서 component-scan으로 bean등록이 될 수 있도록 @Component를 함께 선언하였습니다.

import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component
public class TimeCheckAspect {

}

PointCut 선언하기

Aspect를 선언했으니 이제 어떤 joinpoint에서 adivce를 실행 시킬 것인지 PointCut을 선언해야합니다. Pointcut의 기본 형식은 execution(접근제어자 리턴타입 클래스.메서드(파라미터))입니다.

  • 접근제어자패턴 : private, public, protected 등, 생략가능합니다.

아래는 실제 제가 만든 프로젝트에 Pointcut 표현식을 표현해본 것입니다. personal.ykh.sample.target 패키지의 모든 클래스의 모든 메서드, 파라미터에 상관없이 public 메서드에 대해서라는 의미입니다. 표현식의 자세한 내용은 howtodoinjava에 자세하게 예제와 함께 나와있으니 참고바랍니다.

@Pointcut("execution(public * personal.ykh.sample.target.*.*(..))")
private void timeCheckPointCut() {

}

Advice 구성하기

아래는 Around Advice를 제작한 코드입니다. target method를 실행하는 부분은 joinPoint.proceed() 입니다. 그 위로 Stopwatch를 실행시키며, 그 아래로 시간을 측정합니다.

@Around("personal.ykh.sample.TimeCheckAspect.timeCheckPointCut()")
spublic Object timeCheck(ProceedingJoinPoint joinPoint) throws Throwable {
    Stopwatch stopwatch = Stopwatch.createStarted();
    Object proceed = joinPoint.proceed(); // target method 실행
    stopwatch.stop();
    System.out.println("time : " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms");
    return proceed;
}

Stopwatch는 google lobrary인 guava를 의존성을 추가하면 사용할 수 있습니다.

compile 'com.google.guava:guava:29.0-jre'

그리고 위의 나누어 놓은 pointcut 메서드와 advice 메서드를 합쳐 아래와 같이 한번에 표현할 수도 있습니다.

@Around(pointcut = "execution(public * personal.ykh.sample.target.*.*(..))")
spublic Object timeCheck(ProceedingJoinPoint joinPoint) throws Throwable {
    Stopwatch stopwatch = Stopwatch.createStarted();
    Object proceed = joinPoint.proceed();
    stopwatch.stop();
    System.out.println("time : " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms");
    return proceed;
}

완성된 Aspect

완성된 Aspect 코드는 아래와 같습니다.

import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component
public class TimeCheckAspect {

    @Around(pointcut = "execution(public * personal.ykh.sample.target.*.*(..))")
    spublic Object timeCheck(ProceedingJoinPoint joinPoint) throws Throwable {
        Stopwatch stopwatch = Stopwatch.createStarted();
        Object proceed = joinPoint.proceed();
        stopwatch.stop();
        System.out.println("time : " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms");
        return proceed;
    }
}

Sample Test Controller

AOP가 정상적으로 적용되는지 확인하기위해 SampleController를 아래와같이 작성하였습니다. 어플리케이션 실행 후 localhost:8080/test를 호출하면 테스트가 실행될 것압니다.

@RestController
public class SampleController {


    private Target target;

    public SampleController(Target target) {
        this.target = target;
    }

    @GetMapping("/test")
    public void test() {
        target.doSomething();
    }

}

결과는 아래와 같습니다. target method의 실행결과가 나오고 Aspect에서의 로그도 잘 출력되는것을 확인할 수 있습니다.

=== 작업 중 ===
time : 8 ms

]

마무리

오늘은 이렇게 Spring AOP를 실제로 사용해보며 이전 포스팅에서 봤던 이론을 다져보는 시간을 가져보았습니다. AOP는 프로젝트에서 다양하게 사용될 수 있습니다. 잘 사용만 한다면 중복코드를 수십에서 수백라인까지 줄일수 있습니다.

다음시간에는 SPring AOP에 좀 더 깊게 들어가 돌아가는 원리에 대해서 알아보는 시간을 가져보도록 하겠습니다.

감사합니다.

참조

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop

https://www.baeldung.com/spring-aop

댓글