[SpringBoot]Spring AOP와 프록시 패턴

2020. 5. 1. 13:59개발/Spring

Spring AOP(Aspect Oriented Programing), 프록시 패턴

관점 지향 프로그래밍

관심사를 중심으로 프로그래밍?

프로그래밍을 하다보면 공통적인 작업을 수행하는 코드들이 있다. 여러 메서드에 공통적으로 들어갈 수 있고 다른 클래스에서도 있을 수 있다. 예를들어 메서드의 성능을 측정하기 위한 코드가 그렇다. 메서드의 시작 부분에서 측정을 시작하고, 메서드가 끝난부분에서 측정을 멈춰 결과를 출력한다. 우리는 이런 공통적인 부분을 끄집어 내서 하나의 동작으로 만들고자 한다. 마치 Flowchart(일의 순서를 적은 차트)를 만드는 일과 비슷할 것이다.

🤔 AOP를 구현하는 방법에는 어떤것이 있을까?

  • 컴파일
    • A라는 java 파일이 있다고 가정하자. 컴파일 과정을 거치게 되면서 class파일로 바뀔 것이다. 그 과정이 A.java → Compiler → A.class 로 보여질텐데, 이때 컴파일러에서 내가 원하는 과정을 끼워넣어 컴파일을 해준다면? A.java → AOP 과정을 씌운 Compile 과정 → A.class 로 Compile 이 될 수 있을것이다.
  • 바이트코드 조작
    • ClassLoader가 class를 메모리에 올릴때 AOP과정을 끼워 넣어서 올린다.
    • A.java → A.class --- (AOP) ---> 메모리. 이와 같은 절차이다. (AspectJ 참조)
  • 프록시 패턴 (Spring AOP)
    • 디자인 패턴중의 하나를 활용해 AOP와 같은 효과를 내는 방법.
🤫 프록시 패턴에 대해 알아보자!

기존의 코드를 수정하지 않고 새로운 코드가 추가되어 내가 원하는 작업을 처리한다. 내가 새로운 코드를 추가 하는건 아니고 Spring 내부에서 복잡한 매커니즘에 의해 수행된다.

Spring 에서 사용하는 Proxy 패턴의 예를 한번 살펴보도록 하겠다.

JDBC에서 Transaction을 처리하려면 코드 앞 뒤에 특정 코드가 붙는다. 하지만 Spring JPA에서는 @Transactional Annotation을 붙이면 다른 코드를 추가할 필요 없이 내가 원하는 작업을 수행할 수 있다. 이것도 Spring의 내부에서 Proxy 패턴에 따라 동작한다.

프록시 (Proxy)
타겟을 감싸서 타겟의 요청을 대신 받아주는 랩핑(Wrapping) 오브젝트입니다.
호출자 (클라이언트)에서 타겟을 호출하게 되면 타겟이 아닌 타겟을 감싸고 있는 프록시가 호출되어, 타겟 메소드 실행전에 선처리, 타겟 메소드 실행 후, 후처리를 실행시키도록 구성되어있습니다.

AOP에서 프록시는 호출을 가로챈 후, 어드바이스에 등록된 기능을 수행 후 타겟 메소드를 호출합니다.

img

이동욱 개발자님 블로그 중에서

🥕 Spring AOP를 사용해 메서드 성능 측정하기

@LogExecutionTime 이라는 Annotation을 새로 만들어서 성능을 측정할 것이다. 처음 @LogExecutionTime 이라는 어노테이션을 선언하면 없는 어노테이션 이기 때문에 @LogExecutionTime을 찾을 수 없다고 나올것이다. alt + Enter 로 Annotaion을 새로 만들어 주자.

image

새로 만들어진 Annotation interface에서 설정해 주어야 할 부분이 있다.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
    //Noting yet
}

먼저 @Target 으로 이 Annotation을 어디서 사용할 것인지 focus를 맞추어 주어야 하고 @Retention 을 언제 까지 유지할 것인가. 를 설정해 주어야 한다. RUNTIME으로 설정하면 실행중에도 유지된다.

이제 Aspect를 작성해보도록 하자!

@Component
@Aspect //Aspect라는걸 알림.
public class LogAspect {

    Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Around("@annotation(LogExecutionTime)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Object proceed = joinPoint.proceed();

        stopWatch.stop();
        logger.info(stopWatch.prettyPrint());

        return proceed;
    }
}

@Around("@annotation(LogExecutionTime)")@LogExecutionTime 이 붙은 주변.(여기선 메서드. 아까 Annotation을 만들때 ElementType을 바꾸어 주면 다른 말로 바뀔 수 있겠죠?)

@Around 를 사용하면 ProceedingJoinPoint 를 매개변수로 받을 수 있다. ProceedingJoinPoint는 우리가 Target으로 설정한 method들(@LogExecutionTime이 설정된 method) 이 들어간다.

따라서 joinPoint.preceed()@LogExecutionTime이 붙은 메서드들을 실행시켜주고 다만, 실행시키기 전에 시작 측정을 시작하고 실행이 끝나면 시간 측정을 멈춘다.

image

콘솔에 잘 출력되는걸 볼 수 있다

@Around 말고도 AOP에서 사용되는 많은 내용들이 있다.

이동욱 개발자님 블로그 에 AOP에 관해 자세히 설명되어 있으니 이 글을 참조하자.