본문 바로가기
Java/Spring

[2] 간략한 스프링 개요 - AOP

by Riverandeye 2020. 11. 28.

1. AOP 

반복되어 사용되는 로직을 분리하여 한 곳에서 보관하고

어디서든 적용할 수 있게 구성하는 것입니다.  

 

@Transactional 이 AOP 로 구성되어 있는 어노테이션 중 하나입니다. 

 

AOP를 구현할 수 있는 방법은 3가지가 있습니다. 

1. 컴파일 시간에 class file을 생성하는 중에 넣어준다 (AspectJ)

2. class file을 실행시는 중에 classLoader가 클래스를 로딩하는 시점에 넣어준다 (AspectJ)

3. 프록시 패턴 - Spring AOP가 사용하는 방식 (디자인 패턴)

 

2. Proxy Pattern

 

이 부분은 HFDP 에서도 따로 정리할 계획입니다.

refactoring.guru/design-patterns/proxy

프록시 패턴은 말 그대로 "프록시"를 해주는 대상으로 한번 더 감싸주는 것을 의미합니다. 

 

Proxy Pattern - Class Diagram

 

위 다이어그램을 보면, 같은 인터페이서를 상속하는 CreditCard와 Cash가 있습니다.

CreditCard는 Cash를 의존성으로 가지고 잇는 상황입니다.

결제를 하게 되면, CreditCard로 결제를 했는데 한도가 초과되면 그때 현금 결제를 한다고 하면

CreditCard의 메소드 안에서 Cash의 메소드를 조건부로 호출하는 식으로 구성이 될 것입니다.

 

Payment를 사용하는 쪽에서는 Payment 인터페이스 만으로 이를 쉽게 이용할 수 있고

CreditCard냐 Cash냐에 대한 조건을 분리할 수 있게 됩니다. 

 

스프링에서 프록시 패턴이 동작하는 방식은 

프록시 대상 클래스를 프록시 클래스에 주입하고

해당 프록시 클래스가 Bean으로 등록되어 실제로 다른 클래스에 주입되는 방식으로 구성이 됩니다.

 

스프링 JPA의 Transactional Annotation의 경우

매번 데이터베이스에 접근하는 것이 비효율적이니

해당 메소드 내에 정의된 access를 모두 하나의 트랜잭션으로 묶어서 구성하는데

이를 단순 어노테이션이라는 좀 더 쉬운 방식으로 사용할 수 있게 해줍니다.

기존에는 setautocommit(false)와 같은 문자열이 붙고 커밋이나 롤백이 붙어야하는데

그 단어가 생략되게 해주는 것이 @Transactional 입니다. 

 

그런걸 숨김으로써 복잡한 것을 쉽게쉽게 짜게 해주는 것이 매우 편리합니다. 

 

3. Usage

 

스프링의 AOP는 프록시 패턴을 기반으로 동작합니다. 

 

특정 클래스 혹은 메소드를 대상으로 마킹을 할 Annotation을 지정하고

어노테이션을 한 대상 객체를 감쌀 프록시 클래스를 Aspect로 지정하면

런타임에 ExecutionContext에서 프록시 클래스가 해당 마킹된 클래스를 주입받고

그 프록시 클래스가 Bean으로 동작하여 주입되게 됩니다. 

 

다음 예시는 개별 메소드의 실행 시간을 측정하는 모듈을 AOP로 적용하는 예시입니다.

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

 

@interface를 통해 Annotation을 정의 해주고

위와 같이 Method에 적용될 Annotation임을 선언해줍니다.

@Component
@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;
   }
}

 

실제로 대신 주입되어야 할 Aspect를 정의합니다.

@Around를 통해 어떤 대상이 적용되야하나를 명시하는데요

LogExecutionTime으로 어노테이션 되어있는 메소드를 대상으로 적용한다는 것을 명시합니다. 

@Around메소드를 사용한 메소드는 ProceedingJoinPoint 객체를 인자로 받는데요

JoinPoint는 기존 Target 메소드가 인터페이스 타입으로 주입이 되는 것입니다. 

자연스럽게 joinPoint.proceed() 를 호출하면, 어노테이션 된 메소드를 실행시키는 것이 됩니다. 

댓글