고딩왕 코범석
빈 후처리기와 자동 빈 후처리기 본문
안녕하세요! 이번 시간에는 빈 후처리기에 대해 포스팅해보겠습니다. 해당 포스팅은 스프링 핵심 원리 - 고급편 을 정리한 포스팅입니다.
우선, 저는 빈 후처리기를 강의를 통해 처음 들었습니다. 프록시 객체를 생성하면서 문득 들었던 생각이 있었는데, 그럼 Component Scan 대상 객체의 프록시를 Configuration 없이 스프링 컨테이너에 등록할 수는 없을까? 였습니다.
🤔 빈 후처리기???
스프링은 빈으로 등록된 객체를 생성하고, 컨테이너에 등록합니다. 빈 후처리기는 객체를 빈으로 등록하기 전에 조작하는 객체 이며, 다음과 같은 라이프사이클을 가집니다.
- 생성 : 스프링 빈 대상이 되는 객체를 생성
- 전달 : 생성된 객체를 빈 저장소에 등록하기 직전, 빈 후처리기로 전달
- 후처리 작업 : 전달된 스프링 빈 객체를 조작한다.
- 등록 : 후처리기는 객체를 조작 후 조작된 객체를 반환하는데, 이 반환된 객체가 빈으로 등록, 스프링 컨테이너로 등록된다.
@PostConstruct
스프링은 빈을 Heap에 등록해놓고 사용하는 방식이기 때문에 생성자를 태울 수 없습니다. 그 이유는 스프링은 빈을 싱글톤으로 생성하는데, 생성자의 경우는 new 명령이 실행되기 때문입니다. 아예 못쓰지는 않지만 스프링에서는 쓰지 않는 것을 권장합니다. 이 때, 생성자를 쓰지 않고 빈이 생성될 때 실행을 하고 싶은 코드가 있다면 이 어노테이션을 사용하면 됩니다. 또한, 한번만 실행되기 때문에 빈을 생성했을 때, 초기화한다는 개념으로 이해하면 될 것 같습니다.
🌱 BeanPostProcessor
BeanPostProcessor라는 인터페이스를 통해 이를 구현하여 빈을 조작할 수 있습니다. 해당 인터페이스를 가보면
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
이렇게 두 가지의 메서드가 존재하는데, 각각 빈이 초기화하기 전 / 후에 후처리를 할 수 있게 메서드를 제공해줍니다. 프록시를 스프링 빈으로 등록하고 싶다면, 이 인터페이스를 구현하여 조건에 맞춰 프록시를 리턴할지, 그냥 스캔된 빈을 리턴할지 구현하면 되며, 이렇게 리턴된 객체가 실제 스프링 컨테이너에 빈으로 등록됩니다.
@Slf4j
public class PackageLogTracePostProcessor implements BeanPostProcessor {
private final String basePackage;
private final Advisor advisor;
public PackageLogTracePostProcessor(String basePackage, Advisor advisor) {
this.basePackage = basePackage;
this.advisor = advisor;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
log.info("param beanName = {}, bean = {}", beanName, bean.getClass());
// 프록시 적용 대상 여부 체크
// 프록시 적용 대상이 아니면 원본을 그대로 진행
String packageName = bean.getClass().getPackageName();
if (!packageName.startsWith(basePackage)) {
return bean;
}
// 프록시 대상이면 프록시를 만들어서 반환
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.addAdvisor(advisor);
Object proxy = proxyFactory.getProxy();
log.info("create proxy : target = {}, proxy = {}", bean.getClass(), proxy.getClass());
return proxy;
}
}
위 예제의 PackageLogTracePostProcessor를 Configuration에 등록하는 코드는 다음과 같습니다.
@Configuration
public class BeanPostProcessorConfig {
@Bean
public PackageLogTracePostProcessor logTracePostProcessor(LogTrace logTrace) {
return new PackageLogTracePostProcessor("hello.proxy.app", getAdvisor(logTrace));
}
private Advisor getAdvisor(LogTrace logTrace) {
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "save*");
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
🦸 자동 프록시 생성기 AnnotationAwareAspectJAutoProxyCreator
먼저 gradle에 implementation 'org.springframework.boot:spring-boot-starter-aop
를 주입받아 aspectJ 라이브러리를 등록합니다. 역할은 AOP 관련 클래스를 스프링부트에서 자동으로 스프링 빈으로 등록하고, @EnableAspectJAutoProxy
를 추가하지 않아도 자동으로 처리해줍니다.
AnnotationAwareAspectJAutoProxyCreator
라는 빈 후처리기가 빈으로 등록됩니다. 이 빈 후처리기는 Advisor들을 찾아 조건이 맞을 경우 프록시 객체를 자동으로 빈 등록을 도와줍니다.
🧏 Advisor?
Advisor는 Pointcut + Advice 라고 생각하시면 됩니다.
Pointcut : 적용해야할 대상을 찾는 조건
Advice : 포인트컷 조건을 만족했을 때, 적용할 부가 기능
이 때, Advisor들도 Bean으로 등록해야하는데, Advisor들이 빈으로 먼저 등록되고 이후 advisor 들의 pointcut을 체크하여 프록시를 자동으로 생성해줍니다.
🛎️ Pointcut의 용도와 주의사항
- 빈 생성단계에서 후처리기로 넘어온 빈을 프록시로 적용할지 판단합니다. 표현식에 의해 클래스와 메서드 조건들을 나열했을 때, 이 중 하나라도 만족한다면 프록시를 생성합니다.
- 생성 이후, 프록시를 호출했을 때, Advice(부가기능)을 적용할지에 대한 여부도 Pointcut을 보고 판단합니다.
- 모든 클래스가 프록시를 적용할 이유는 없기 때문에 Pointcut을 잘 작성하여 최소한의 프록시만 생성해야 합니다.
'Language & Framework > Spring' 카테고리의 다른 글
Jasypt 적용하기 + 이슈 (0) | 2022.01.22 |
---|---|
스프링 AOP (0) | 2021.12.18 |
Spring Security + JWT + Redis로 로그인 구현하기 (2) 로그인 (5) | 2021.11.23 |
Spring Security + JWT + Redis로 로그인 구현하기 (1) 설정과 회원가입 (2) | 2021.11.23 |
Querydsl에서 여러 자식들을 DTO로 받기 (0) | 2021.11.17 |