BeanPostProcessor
빈 생명주기에서 postProcessAfterInitialization이 원본 대신 프록시를 반환하는 방식을 실행하고, 어드바이저 검색·적용 대상 빈 판정·인프라 제외·조기 참조·이중 프록시를 운영 관점에서 검증합니다.
ProxyFactory를 빈마다 호출하면 래퍼 클래스 반복은 사라져도 구성 반복은 남습니다. BeanPostProcessor는 컨테이너가 만드는 빈을 초기화 전후에 관찰하고 최종 참조를 다른 객체로 바꿀 수 있습니다. Spring의 자동 프록시 생성기는 등록된 어드바이저와 빈을 대조해 필요한 대상만 프록시로 감싸므로 애플리케이션 코드와 연결에서 횡단 관심을 분리합니다.
빈 후처리기 확장 지점
컨테이너는 인스턴스 생성, 의존성 주입, 인식 콜백, 초기화 전 처리기, 초기화 콜백, 초기화 후 처리기를 거쳐 빈 참조를 노출합니다. 프록시는 흔히 초기화 후 단계에서 완성된 대상을 감싸 반환됩니다.
모든 빈을 무조건 프록시로 만들면 구성 클래스, 어드바이저, 후처리기 자체까지 재귀 적용될 수 있습니다. 인프라 타입과 포인트컷 불일치 빈은 원본을 반환합니다. 빈 이름 문자열만으로 중요한 보안 범위를 정하지 않습니다.
package board.autoproxy;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public final class TimingPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (!(bean instanceof TimedComponent)) {
return bean;
}
var factory = new ProxyFactory(bean);
factory.addAdvice((MethodInterceptor) invocation -> {
long started = System.nanoTime();
try {
return invocation.proceed();
} finally {
System.out.println("timed=" + invocation.getMethod().getName()
+ " nanos=" + (System.nanoTime() - started));
}
});
return factory.getProxy();
}
public interface TimedComponent { }
}표시 인터페이스는 학습용 선택 기준입니다. 운영 환경에서는 어드바이저 포인트컷과 인프라 자동 프록시 생성기를 사용해 타입·애노테이션·메서드 조건을 분리합니다.
최종 프록시 주입
다음 컨텍스트는 사용자 정의 후처리기를 정적 빈으로 먼저 등록하고 표시 서비스를 프록시로 바꿉니다. 클라이언트는 생성 사실을 몰라도 인터페이스 호출이 측정됩니다.
package board.autoproxy;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public final class PostProcessorDemo {
interface ReportService {
int weeklyPostCount(long authorId);
}
static final class ReportTarget
implements ReportService, TimingPostProcessor.TimedComponent {
@Override
public int weeklyPostCount(long authorId) {
return authorId == 41L ? 12 : 0;
}
}
@Configuration(proxyBeanMethods = false)
static class Config {
@Bean
static TimingPostProcessor timingPostProcessor() {
return new TimingPostProcessor();
}
@Bean
ReportService reportService() {
return new ReportTarget();
}
}
public static void main(String[] args) {
try (var context = new AnnotationConfigApplicationContext(Config.class)) {
ReportService report = context.getBean(ReportService.class);
System.out.println("postCount=" + report.weeklyPostCount(41L));
System.out.println("proxy=" + AopUtils.isAopProxy(report));
}
}
}timed=weeklyPostCount nanos=(0 이상의 값)
postCount=12
proxy=true후처리기 빈 팩토리 메서드를 정적으로 두면 구성 인스턴스를 너무 일찍 생성하는 생명주기 경고를 피할 수 있습니다. 사용자 정의 처리기가 다른 빈을 조회하면 조기 초기화와 순환 의존이 생길 수 있어 의존성을 최소화합니다.
자동 프록시 생성
Spring 인프라는 BeanFactory의 어드바이저를 조회하고 각 빈 클래스와 메서드에 포인트컷이 맞는지 검사합니다. 하나라도 맞으면 프록시를 만들고 적용 가능한 어드바이저를 순서에 따라 체인에 넣습니다. 맞지 않으면 객체 동일성과 시작 비용을 보존하기 위해 원본을 반환합니다.
DefaultAdvisorAutoProxyCreator는 어드바이저 기반 학습에 유용하고 AspectJ 애노테이션을 처리하는 생성기는 애스펙트 메타데이터를 어드바이저로 변환합니다.
여러 자동 프록시 생성기를 임의로 동시에 등록하지 않고 프레임워크가 적절한 인프라를 승격하도록 둡니다.
어드바이저 자체, Advice, Pointcut, AopInfrastructureBean은 다시 프록시하지 않습니다. 사용자 정의 생성기를 만들 때도 이 제외 규칙을 지키지 않으면 무한 래핑이나 시작 실패가 생깁니다.
조기 참조와 순환 의존성
순환 의존성을 해결하려고 대상의 조기 참조가 다른 빈에 먼저 주입되고 나중에 프록시가 만들어지면 일부 호출자는 원본, 일부는 프록시를 볼 수 있습니다. Spring 자동 프록시 생성기는 조기 프록시 참조를 다루는 훅을 갖지만 순환 구조를 정당화하는 이유는 아닙니다.
생성자 주입과 책임 분리로 순환을 제거합니다. 지연 주입으로 숨기면 런타임 첫 호출에서 문제가 이동할 수 있습니다. 트랜잭션·보안 어드바이스가 원본 참조에서 빠지는 것은 정확성 결함입니다.
이중 프록시도 관찰합니다.
서로 다른 처리기가 같은 빈을 연속 감싸면 어드바이저 순서와 대상 클래스 조회가 복잡해집니다.
AopUtils.isAopProxy, Advised.getAdvisors, 최종 대상 클래스를 진단할 때 확인하되 애플리케이션 로직은 프록시 내부를 뜯지 않습니다.
시작·실행 범위 검증
컨텍스트 테스트는 긍정 빈이 프록시인지, 부정 빈은 원본인지, 어드바이저 수와 순서가 맞는지 확인합니다. 동작 테스트는 메서드 호출에서 어드바이스와 대상이 각각 한 번인지, 예외 동일성과 반환 값이 유지되는지 봅니다.
운영 환경 시작 로그에 모든 빈 이름을 쏟지 않고 예상 프록시 개수와 핵심 보안 빈 적용 실패를 상태 확인 또는 시작 검증으로 제한합니다. 프록시 생성 시간이 시작 예산을 넘는다면 포인트컷 클래스 필터를 좁히고 런타임 매처를 줄입니다.
연습 문제
애노테이션 기반 어드바이저와 DefaultAdvisorAutoProxyCreator를 등록해 애플리케이션 패키지의 @Measured 메서드만 자동 프록시로 만드세요.
어드바이저 빈과 구성 빈이 프록시되지 않고 대상 서비스는 한 번만 감싸지는지 검증하세요.
해설 보기
생성기와 어드바이저는 인프라 역할로 구분하고 포인트컷을 구체 애플리케이션 패키지로 좁힙니다.
package board.autoproxy;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class AutoProxyConfiguration {
@Bean
static DefaultAdvisorAutoProxyCreator autoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
@Bean
Advisor measuredAdvisor(board.advisor.MeasuredPointcut pointcut) {
MethodInterceptor advice = invocation -> invocation.proceed();
return new DefaultPointcutAdvisor(pointcut, advice);
}
}실제 테스트에서는 Advised로 어드바이저 수를 읽되 운영 환경 코드가 이 형 변환에 의존하지 않게 합니다.