BeanPostProcessor란?
BeanPostProcessor는 Spring IoC 컨테이너가 빈을 생성한 직후, 초기화 전후에 개입할 수 있는 확장 포인트입니다. Spring의 핵심 기능인 @Autowired, @Async, @Transactional, AOP 프록시 생성이 모두 BeanPostProcessor로 구현되어 있습니다.
public interface BeanPostProcessor {
// 빈 초기화(@PostConstruct, afterPropertiesSet) 전에 호출
default Object postProcessBeforeInitialization(
Object bean, String beanName) throws BeansException {
return bean;
}
// 빈 초기화 후에 호출 — 프록시 생성의 주요 타이밍
default Object postProcessAfterInitialization(
Object bean, String beanName) throws BeansException {
return bean;
}
}
반환된 Object가 원본 빈을 대체합니다. 프록시 객체를 반환하면 컨테이너에 프록시가 등록됩니다. @Transactional이 프록시로 동작하는 이유가 바로 이 메커니즘입니다.
빈 생성 흐름에서의 위치
BeanPostProcessor는 빈 라이프사이클의 정확한 위치에서 동작합니다.
1. 빈 인스턴스 생성 (new)
2. 의존성 주입 (@Autowired, 생성자 주입)
3. Aware 인터페이스 콜백 (BeanNameAware, ApplicationContextAware 등)
4. ★ BeanPostProcessor.postProcessBeforeInitialization()
5. @PostConstruct 메서드 실행
6. InitializingBean.afterPropertiesSet()
7. @Bean(initMethod) 실행
8. ★ BeanPostProcessor.postProcessAfterInitialization()
9. 빈 사용 가능 상태
... (애플리케이션 실행)
10. @PreDestroy
11. DisposableBean.destroy()
12. @Bean(destroyMethod)
4단계에서 빈 상태를 검증하거나 설정을 변경하고, 8단계에서 프록시로 래핑하는 것이 일반적인 패턴입니다.
실전 1: 커스텀 어노테이션 자동 처리
@Encrypted 어노테이션이 붙은 필드를 자동으로 복호화하는 BeanPostProcessor를 만들어봅시다.
// 커스텀 어노테이션
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypted {}
// 설정 클래스에서 사용
@Component
@ConfigurationProperties(prefix = "app")
public class AppConfig {
@Encrypted
private String dbPassword;
@Encrypted
private String apiSecret;
// getter, setter
}
// BeanPostProcessor 구현
@Component
public class EncryptedFieldProcessor implements BeanPostProcessor {
private final EncryptionService encryptionService;
public EncryptedFieldProcessor(EncryptionService encryptionService) {
this.encryptionService = encryptionService;
}
@Override
public Object postProcessAfterInitialization(
Object bean, String beanName) {
for (Field field : bean.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Encrypted.class)) {
field.setAccessible(true);
try {
String encrypted = (String) field.get(bean);
if (encrypted != null) {
String decrypted = encryptionService.decrypt(encrypted);
field.set(bean, decrypted);
}
} catch (IllegalAccessException e) {
throw new BeanCreationException(
beanName, "필드 복호화 실패: " + field.getName(), e);
}
}
}
return bean;
}
}
이제 프로퍼티 파일에 암호화된 값을 넣으면, 빈 초기화 시 자동으로 복호화됩니다.
실전 2: 프록시 기반 로깅 자동 적용
특정 어노테이션이 붙은 빈의 모든 메서드에 실행 시간 로깅을 자동으로 추가합니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {}
@Component
public class TimedBeanPostProcessor implements BeanPostProcessor {
private static final Logger log =
LoggerFactory.getLogger(TimedBeanPostProcessor.class);
@Override
public Object postProcessAfterInitialization(
Object bean, String beanName) {
Class<?> targetClass = bean.getClass();
// @Timed가 붙은 클래스만 처리
if (!targetClass.isAnnotationPresent(Timed.class)) {
return bean;
}
// JDK Dynamic Proxy (인터페이스 기반)
if (targetClass.getInterfaces().length > 0) {
return Proxy.newProxyInstance(
targetClass.getClassLoader(),
targetClass.getInterfaces(),
(proxy, method, args) -> {
long start = System.nanoTime();
try {
return method.invoke(bean, args);
} finally {
long elapsed = (System.nanoTime() - start) / 1_000_000;
log.info("[{}] {}.{}() → {}ms",
beanName,
targetClass.getSimpleName(),
method.getName(),
elapsed);
}
}
);
}
return bean;
}
}
// 사용
@Timed
@Service
public class OrderServiceImpl implements OrderService {
public Order createOrder(OrderRequest req) {
// 자동으로 실행 시간 로깅
// [orderService] OrderServiceImpl.createOrder() → 45ms
}
}
BeanFactoryPostProcessor와의 차이
이름이 비슷하지만 동작 시점과 대상이 전혀 다릅니다.
| 구분 | BeanPostProcessor | BeanFactoryPostProcessor |
|---|---|---|
| 동작 시점 | 빈 인스턴스 생성 후 | 빈 정의(BeanDefinition) 로드 후, 인스턴스 생성 전 |
| 대상 | 빈 인스턴스 (Object) | 빈 메타데이터 (BeanDefinition) |
| 용도 | 프록시 생성, 필드 주입, 검증 | 프로퍼티 치환, 빈 정의 수정 |
| 대표 구현 | AutowiredAnnotationBPP, AsyncAnnotationBPP | PropertySourcesPlaceholderConfigurer |
// BeanFactoryPostProcessor: 빈 정의 단계에서 수정
@Component
public class CustomBeanFactoryPostProcessor
implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory factory) {
// 특정 빈의 스코프를 런타임에 변경
BeanDefinition bd = factory.getBeanDefinition("myService");
bd.setScope("prototype");
// 프로퍼티 값 동적 변경
bd.getPropertyValues().add("timeout", 5000);
}
}
Spring 내부의 주요 BeanPostProcessor들
- AutowiredAnnotationBeanPostProcessor:
@Autowired,@Value주입 처리 - CommonAnnotationBeanPostProcessor:
@PostConstruct,@PreDestroy,@Resource처리 - AsyncAnnotationBeanPostProcessor:
@Async메서드를 비동기 프록시로 래핑 - AnnotationAwareAspectJAutoProxyCreator: AOP 프록시 생성 (
@Transactional포함) - ScheduledAnnotationBeanPostProcessor:
@Scheduled메서드를 스케줄러에 등록
@Autowired가 동작하는 이유가 “Spring이 알아서” 가 아니라, AutowiredAnnotationBeanPostProcessor가 리플렉션으로 @Autowired 어노테이션을 탐색하고 의존성을 주입하기 때문입니다.
우선순위 제어: Ordered·PriorityOrdered
여러 BeanPostProcessor가 등록되면 실행 순서가 중요합니다.
@Component
public class FirstProcessor implements BeanPostProcessor, Ordered {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // 가장 먼저 실행
}
}
@Component
public class LastProcessor implements BeanPostProcessor, Ordered {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; // 가장 나중에 실행
}
}
// PriorityOrdered는 Ordered보다 항상 먼저 실행
// Spring 내부 프로세서(Autowired 등)는 PriorityOrdered 구현
실행 순서: PriorityOrdered 구현체 → Ordered 구현체 → 순서 미지정 → 각 그룹 내에서 order 값 오름차순.
관련 글
- Spring Bean Lifecycle 심화 — 빈 생성부터 소멸까지 전체 라이프사이클 정리
- Spring AOP 프록시 동작 원리 — BeanPostProcessor가 생성하는 AOP 프록시의 내부 구조