AOP 정의
관점 지향 프로그래밍.
서비스의 핵심 관점과 횡단 관심을 분리하여 반복되는 횡단 관심을 모듈화.
구성 요소
- Aspect : 횡단 관심을 모듈화 한 것
- Target : Aspect가 적용되는 대상 (클래스, 메서드..)
- Advice : Aspect의 구현체 (횡단 관심의 구현체)
- JoinPoint : Advice의 구현이 적용되는 지점
- PointCut : Advice 적용 대상
예제 코드
예전에 시간 측정을 위해서 작성했던 클래스이다.
우선 클래스 위에 @Aspect어노테이션으로 AOP 대상임을 명시한다.
@Slf4j
@Aspect
@Component
public class MeasureTimeImpl {
@Pointcut("@annotation(com.example.springdataelastic.common.MeasureTime)")
private void timer() {};
@Around("timer()")
public Object AssumeExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch watch = new StopWatch();
watch.start();
Object proceed = joinPoint.proceed();
watch.stop();
float time = watch.getTotalTimeMillis() / 1000f;
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
String name = signature.getName();
log.info("method : {}, time(s) : {}", name, time);
return proceed;
}
}
@PointCut()어노테이션으로 Adivce의 적용 대상을 지정한다.- 위의 경우는 MeasureTime이라는 커스텀 어노테이션이 붙은 대상을 지정했다.
@Around는 Aspect가 실행된는 시점을 지정한다. (메서드 실행 전 후)@Before실행 이전@After실행 이후
AssumeExecutionTime메서드가 실질적 구현체joinPoint.proceed()로 실제 메서드 호출
AOP 작동 원리
스프링 AOP는 프록시 패턴으로 작동한다.
프록시란?
대리자. 요청을 직접 처리하는 것이 아닌 대리자를 통해 간접적을 처리하는 방법.
위의 AssumeExecutionTime메서드를 살펴보면 실제 로직(joinPoint.proceed())을 구현하기 전과 후에 횡단 관심 로직이 작성된 것을 볼 수 있다. @MeasureTime이 붙은 메서드를 호출할 때는 실제 객체가 아닌 프록시 객체를 호출하여 AssumeExecutionTime를 실행한다.
방법 3가지
- 컴파일 시점 (AspectJ)
- 클래스 로딩 시점
- 런타임 시점 (JDK Dynamic Proxy, CGlibs)
AspectJ
자바에서 완벽한 AOP 솔루션 제공을 목표로하는 기술. 컴파일 시 조작. 상당히 복잡함
JDK Dynamic Proxy
Interface를 기반으로 Proxy를 생성하는 방식. InvocationHandler를 구현받아서 횡단 관심을 구현한다.
-> Reflection을 사용함. (런타임 에러 가능성 있음)
CGlib
바이트 코드를 조작해서 Proxy를 생성하는 방식(상속을 이용). Reflection을 사용하지 않고 Interface 없어도 가능
-> MethodInterceptor를 구현해서 횡단 관심 구현
스프링에서의 AOP
스프링에서는 Interface가 있는 경우는 JDK Dynamic Proxy를 없는 경우에는 CGlib을 사용한다.
궁금증들
Spring에서 Proxy객체를 만드는 시점?
자바가 실행되고 스프링이 객체를 만들어서 스프링 컨테이너의 빈 저장소에 Bean으로 저장한다. 이때 BeanPostProcessor(후처리기)를 통해서 생성된 객체를 조작하여 빈 저장소에 저장할 수 있다. BeanPostProcessor에서 프록시 적용 대상을 체크한 다음 적용 대상인 경우 객체를 프록시 객체로 변경한 다음 빈 저장소에 저장한다.
-> 프록시 적용이 필요한 객체는 Bean저장 시에 프록시 객체로 저장된다.
Bean만 AOP가 가능한 이유?
스프링 AOP는 스프링 컨테이너와 프록시 패턴으로 구현된다. 스프링 컨테이너에 프록시 객체가 있어야지 AOP를 적용할 수 있다.
@Transactional에서 내부 메서드 or private 메서드는 작동하지 않는 이유?
AOP의 순서는
실행 전 로직 수행 -> 호출 -> 실행 후 로직 수행
순으로 이어진다. 이때 호출은 Bean에 저장된 객체의 메서드를 호출한다. 하지만 내부 메서드는 메서드 내부에서만 호출되므로 AOP를 적용할 수가 없다. 또한 private은 클래스 외부에서 호출을 하지 못하므로 AOP의 대상이 될 수 없다.
'Java > Spring' 카테고리의 다른 글
| Spring MVC의 예외처리 (0) | 2023.05.09 |
|---|---|
| 동시성 제어 (0) | 2023.05.04 |
| [Spring] IoC/DI (0) | 2023.05.01 |
| SpringMVC (3) - SpringMVC (0) | 2023.04.27 |
| JPA 간단정리 (0) | 2023.04.25 |