프록시 팩토리를 사용하는 방법보다
더 좋은 방법은 없을까?
이번글은 빈 후처리기에 대해 정리했습니다.
참고 강의 : 김영한의 스프링 핵심 원리 - 고급 편
프록시 팩토리로 프록시 객체를 생성하는 방식은 어떤 문제가 존재할까요?
- 설정 파일이 너무 많아집니다. 예를 들어 스프링 빈이 100개가 존재하는 경우, 프록시를 통해 부가 기능을 적용하기 위해선 100개의 동적 프록시 생성 코드를 만들어야 합니다.
- 스프링은 컴포넌트 스캔을 사용해서 실제 객체를 스프링 빈으로 등록합니다. 동적으로 생성된 프록시 객체는 컴포넌트 스캔을 통해서 등록을 할 수 없기 때문에, 프록시 객체를 직접 빈으로 등록해야 합니다. 예를 들어, 등록해야 할 프록시 객체가 100개가 존재한다면, 수동으로 빈으로 등록해야 합니다.
스프링이 빈을 등록하는 방식을 그림으로 나타내면 다음과 같습니다.
이 문제들을 어떻게 해결할 수 있을까요?
빈 후처리기를 사용하면 해결할 수 있습니다.
빈 후처리기(BeanPostProcessor)
객체를 빈 저장소에 등록하기 직전에 조작하고 싶은 경우 빈 후처리기를 사용합니다.
BeanPostProcessor라는 이름과 같이 빈을 생성한 후에 무언가를 처리하는 용도로 사용합니다.
빈 후처리기는 객체를 조작하거나 완전히 다른 객체로 바꾸는 것이 가능합니다.
과정을 그림으로 나타내면 다음과 같습니다.
- 생성 : 스프링 빈 대상이 되는 객체를 생성합니다. (@Bean, 컴포넌트 스캔 모두 포함)
- 전달 : 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달합니다.
- 후 처리 작업 : 빈 후처리기는 전달된 스프링 빈 객체를 조작하거나 다른 객체로 바꿔치기할 수 있습니다.
- 등록 : 빈 후처리기는 빈을 반환합니다. 빈을 그대로 반환할경우 해당 빈이 등록되고, 바꿔치기한 경우 다른 객체가 빈 저장소에 등록됩니다.
빈 후처리기로 객체를 바꿔치기하는 과정은 아래와 같습니다.
빈 후처리기 적용하기
먼저, 일반적인 스프링 빈을 등록하는 과정을 코드를 통해 알아보겠습니다.
public class BasicTest {
@Test
void basicConfig() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BasicConfig.class);
A a = applicationContext.getBean("beanA", A.class);
a.helloA();
assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(B.class));
}
@Slf4j
@Configuration
static class BasicConfig {
@Bean(name = "beanA")
public A a() {
return new A();
}
}
@Slf4j
static class A {
public void helloA() {
log.info("hello A");
}
}
@Slf4j
static class B {
public void helloB() {
log.info("hello B");
}
}
}
new AnnotationConfigApplicationContext(BasicConfig.class)로 스프링 컨테이너를 생성하면서 BasicConfig.class 파일을 넘겨줍니다. BasicConfig.class 설정 파일이 스프링 빈으로 등록되게 됩니다.
@Slf4j
@Configuration
static class BasicConfig {
@Bean(name = "beanA")
public A a() {
return new A();
}
}
A객체를 beanA라는 이름으로 스프링 빈으로 등록합니다.
A a = applicationContext.getBean("beanA", A.class);
"beanA"라는 이름으로 A 타입의 스프링 빈을 찾을 수 있습니다.
applicationContext.getBean(B.class)
B타입 객체는 스프링 빈으로 등록을 하지 않았기 때문에 오류가 발생합니다.
출력 결과
이제, 빈 후처리기를 적용 해보겠습니다.
스프링에선 빈 후처리기를 위한 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;
}
}
빈 후처리기를 사용하기 하려면, BeanPostProcessor 인터페이스를 구현하고 스프링 빈으로 등록하면 됩니다.
postProcessBeforeInitialization : 객체 생성 이후, @PostConstruct 같은 초기화가 발생하기 전에 호출됩니다.
postProcessAfterInitialization : 객체 생성 이후 @PostConstruct같은 초기화가 발생한 다음에 호출됩니다.
public class BeanPostProcessTest {
@Test
void postProcessor() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanPostProcessorConfig.class);
B b = applicationContext.getBean("beanA", B.class);
b.helloB();
assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(A.class));
}
@Slf4j
@Configuration
static class BeanPostProcessorConfig {
@Bean(name = "beanA")
public A a() {
return new A();
}
@Bean
public AToBPostProcessor helloPostProcessor() {
return new AToBPostProcessor();
}
}
@Slf4j
static class A {
public void helloA(){
log.info("hello A");
}
}
@Slf4j
static class B {
public void helloB() {
log.info("hello B");
}
}
@Slf4j
static class AToBPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.info("beanName = {} bean = {}", beanName, bean);
if(bean instanceof A) {
return new B();
}
return bean;
}
}
}
AToBPostProcessor : BeanPostProcessor 인터페이스를 구현한 빈 후처리기 입니다. 스프링 빈으로 등록하는 경우 스프링 컨테이너가 빈 후처리기로 인식하고 동작합니다.
AToBPostProcessor 빈 후처리기는 A 객체를 새로운 B 객체로 바꿔치기합니다. 즉, 파라미터로 넘어온 빈 객체가 A의 인스턴스이면 새로운 B 객체를 생성해서 반환해 줍니다.
출력 결과는 다음과 같습니다.
@Slf4j
@Configuration
static class BeanPostProcessorConfig {
@Bean(name = "beanA")
public A a() {
return new A();
}
@Bean
public AToBPostProcessor helloPostProcessor() {
return new AToBPostProcessor();
}
분명히 BeanPostProcessorConfig에서 "beanA"이름으로 A객체를 빈으로 등록했음에도 불구하고
아래와 같은 빈 후처리 코드로 인해서, A객체가 B객체가 등록이 됩니다.
@Slf4j
static class AToBPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.info("beanName = {} bean = {}", beanName, bean);
if(bean instanceof A) {
return new B();
}
return bean;
}
}
빈을 생성한 후에 빈 초기화를 하는 @PostConstruct는 어떤 원리로 동작할까요?
@PostConstruct 동작 원리
@PostConstruct는 Spring 빈이 생성된 후 초기화 작업을 수행하는 역할을 합니다.
빈의 초기화란, 클래스 내부에서 @PostConstruct 애노테이션이 붙은 메서드를 실행하는 것을 의미합니다.
그러나 Spring 컨테이너는 빈으로 등록된 객체의 내부 메서드를 직접 호출하지 않습니다.
따라서 빈 후처리기(Bean PostProcessor)를 활용하여 @PostConstruct가 붙은 메서드를 찾아 실행하는 방식으로 동작합니다.
정리
- 빈 후처리기는 빈을 조작하고 변경할 수 있습니다.
- 빈 후처리기는 빈 객체를 다른 객체로 바꿀 수 있습니다.
- 빈 후처리기를 사용하면 빈 객체를 프록시로 교체하는 것도 가능합니다.
'Spring' 카테고리의 다른 글
[Spring DB] Connection Pool (0) | 2025.04.25 |
---|---|
[Spring Docs] DispatcherServlet #2 (0) | 2025.03.16 |
어드바이저 (0) | 2025.03.12 |
프록시 팩토리 (0) | 2025.03.11 |
CGLIB (0) | 2025.03.10 |