🎯 빈 후처리기
Spring에서 Bean을 생성하고 컨테이너에 저장하기위해서 빈 후처리기를 사용한다.
빈 후처리기를 사용하면 @Bean과 컴포넌트 스캔 2가지 방식으로 빈을 등록하는 경우에 대해서 프록시 객체를 생성할 수 있게 해준다.
SpringBoot는 @Baen, 컴포넌트스캔을 통해서 객체를 생성한다.
그리고 생성된 객체를 빈 후처리기에 작업을 넘기고 반환받는다.
반환받은 객체를 스프링 컨테이너에 저장하게 된다.
이 과정에서 빈 후처리기는 해당 객체가 프록시 객체로 바꿔져야 하는지 판단하고, 바꿀필요가 있다면 프록시 객체로 바꿔서 반환해준다.
이렇게 빈 후처리기를 개발자가 직접 정의해서 프록시 객체로 만들어 줄 수 도 있고, 스프링부트에서 제공해주는 기본적인 후 처리기를 사용할 수도 있다.
- AnnotationAwareAspectJAutoProxyCreator - 스프링부트에서 자동설정으로 적용되는 빈 후처리기
- 스프링 빈으로 등록된 모든 Advisor들을 찾아서 프록시가 필요한 곳에 자동으로 프록시 객체로 변환하는 동작을 수행한다.
- Advisor는 앞 글에서 학습하였는데, 어떤 클래스의 어떤 메서드에 어떤 동작을 수행할지 정보를 담고 있다.(ex Advisor는 Pointcut + Advice로 Pointcut을 통해 어떤 클래스에 프록시를 적용해야 할지를 찾아낼 수 있음)
🎯 마무리
결국 SpringBoot의 자동설정 빈 후처리기를 통해서, 우리는 Advisor만 정의해주면 프록시 객체를 등록할 수 있고 AOP동작을 수행할 수 있다.
앞서서 정리했던 SpringAOP에서 @Pointcut, @Aspect오 결국 Advisor를 생성해주기 위함이고, 빈 후처리기가 이를 모두 찾아내어 프록시 객체로 새롭게 만들어 컨테이너에 등록해주는 것이다.
@Async도 결국 빈을 생성하는 과정에서 @Async가 붙은 클래스에 대해서 프록시 객체를 생성하게 되고, 스레드풀을 활용한 비동기 동작을 수행하도록 Advice가 정의되어 있는 것이다.
@Async에서 같은 클래스 내부의 메서드에서 @Async 메서드를 호출했을 때 동작하지 않는 이유도 이제 이해할 수 있다.
@Async가 붙은 메서드의 클래스는 실제 객체가 아닌 프록시 객체로 생성될 것이다.
외부에서 프록시 객체를 호출하고 내부의 메서드를 호출하게 되면서 새롭게 끼워맞춰진 동작이 수행될 것인데, 프록시 객체 내부에서 프록시 객체 내부 메서드를 호출하면 새롭게 끼워맞춰진 메서드가 호출되지 않으므로 동작하지 않는다.
코드로 확인해보기👇
아래의 RealObject의 methodB는 프록시로 동작할 것이라고 기대돼되는 methodA를 호출한다.
실제로 Proxy에는 methodA에 시작(start)과 종료(end)때 출력하는 추가 동작이 부가 되어있다.
즉, methodB를 호출했을 때 사용자가 기대하는 동작한 아래와 같다.
1. Real객체 B메서드 호출
2. start
3. Real객체 A메서드 호출
4. end
public interface Subject {
void methodA();
void methodB();
}
public class RealObject implements Subject {
@Override
public void methodA() {
System.out.println("Real객체 A메서드 호출");
}
@Override
public void methodB() {
System.out.println("Real객체 B메서드 호출");
methodA();
}
}
public class ProxyObject implements Subject {
private Subject target;
public ProxyObject(Subject target) {
this.target = target;
}
@Override
public void methodA() {
System.out.println("start"); // 프록시로 추가된 부분
target.methodA();
System.out.println("end"); // 프록시로 추가된 부분
}
@Override
public void methodB() {
target.methodB();
}
}
하지만 실제로 프록시 객체를 생성하고 mehtodB를 호출해보면 start, end는 출력되지 않는다.
왜냐하면 ProxyObject의 methodB는 ProxyObject의 methodA가 아닌 target객체(여기는 RealObject)의 methodB를 호출하기 때문이다.
public static void main(String[] args) {
Subject real = new RealObject();
Subject proxy = new ProxyObject(real);
proxy.methodB();
// 기대하는 호출
// 1. Real객체 B메서드 호출
// 2. start
// 3. Real객체 A메서드 호출
// 4. end
// 실제 호출
// 1. Real객체 B메서드 호출
// 2. Real객체 A메서드 호출
}
같은 사례로 @Transactional또한 AOP방식으로 구현되어있기 때문에 내부 메서드에서 @Trasactional이 붙은 메서드를 호출하는 경우에는 동작하지 않는 것을 확인할 수 있다.
'프로젝트 > 데브툰' 카테고리의 다른 글
[데브툰] CI 적용하기 (0) | 2024.07.16 |
---|---|
[프로젝트] @Async를 알아보자(feat. Spring과 Proxy) (0) | 2024.05.07 |
[프로젝트] @Async를 알아보자(feat. Spring AOP) (0) | 2024.05.06 |
[프로젝트] 비동기 프로그래밍 적용해보기 (0) | 2024.05.01 |
[프로젝트] 프로젝트 목표 및 기획 (0) | 2024.04.30 |