지금까지(챕터 2·3)는 ProxyFactory로 수동으로 프록시를 만들었다. 하지만 실제 애플리케이션에서 모든 빈을 손으로 감쌀 수는 없다. @Transactional 붙은 빈, 포인트컷에 걸리는 빈을 컨테이너가 알아서 프록시로 바꿔줘야 한다. 그 일을 하는 것이 자동 프록시 생성기 — BeanPostProcessor를 구현한 AbstractAutoProxyCreator 계열이다. 이 챕터는 "빈 하나가 컨테이너에서 어떻게 프록시로 둔갑하는가"를 다룬다.
SmartInstantiationAwareBeanPostProcessor (spring-beans 훅)
▲
AbstractAutoProxyCreator ★ 프록시 생성 공통 골격
│ postProcessBeforeInstantiation() ← 커스텀 TargetSource면 여기서 프록시
│ postProcessAfterInitialization() ← 일반적 경로: wrapIfNecessary()
│ createProxy() → ProxyFactory 구성
│ getAdvicesAndAdvisorsForBean() ← (추상) "이 빈에 뭘 적용?"
▲
┌────┴───────────────────────────┬──────────────────────────┐
BeanNameAutoProxyCreator AbstractAdvisorAutoProxyCreator (직접 지정형)
(이름 패턴으로 지정) │ findEligibleAdvisors()
│ ├ findCandidateAdvisors() 컨테이너의 Advisor 빈 수집
│ ├ findAdvisorsThatCanApply() 대상에 맞는 것만
│ └ sortAdvisors() 순서 정렬
▲
┌──────────────────┼─────────────────────────────────┐
DefaultAdvisorAutoProxyCreator InfrastructureAdvisorAutoProxyCreator AspectJAware...
(모든 Advisor 빈 대상) (role=INFRASTRUCTURE 인 것만; tx 등 내부용) (챕터 5)
이 클래스는 BeanPostProcessor의 여러 콜백에 훅을 건다. 핵심은 두 지점이다.
postProcessBeforeInstantiation()(인스턴스화 전): 이 빈에 커스텀TargetSource(풀링 등)가 지정돼 있으면, 대상 빈을 아예 인스턴스화하지 않고 여기서 바로 프록시를 만들어 반환한다. 대상 생명주기를 TargetSource가 전담하도록.postProcessAfterInitialization()(초기화 후, 일반 경로): 보통의 빈은 정상 생성·초기화된 뒤 여기서wrapIfNecessary()로 감싸진다.
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyBeanReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey); // 핵심
}
}
return bean;
}earlyBeanReferences 체크는 순환 참조 시 이미 조기 노출(getEarlyBeanReference)로 프록시된 빈을 두 번 감싸지 않기 위한 장치다.
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 1) 이미 커스텀 TargetSource로 처리됐거나, FALSE로 표시된 빈은 그대로
if (this.advisedBeans.get(cacheKey) == Boolean.FALSE) return bean;
// 2) 인프라 클래스(Advice/Advisor/Pointcut 등)나 skip 대상은 프록시 안 함
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 3) 이 빈에 적용할 어드바이스/어드바이저 조회 (서브클래스 책임)
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 4) 적용할 게 있으면 프록시 생성. 대상은 SingletonTargetSource로 고정
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy; // ★ 원본 대신 프록시를 컨테이너에 등록
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean; // 적용할 게 없으면 원본 그대로
}핵심은 (3)의 추상 메서드 getAdvicesAndAdvisorsForBean()이다. "이 빈에 무엇을 적용할 것인가" 라는 정책을 서브클래스에 위임한다.
- 반환이
DO_NOT_PROXY(=null)면 적용할 게 없으니 원본 반환. - 아니면 그 어드바이저들로
createProxy()를 호출하고, 결과 프록시를 컨테이너가 받아 등록한다. 이후 이 빈을 주입받는 모든 곳은 프록시를 보게 된다.
infrastructure class(Advice·Advisor·Pointcut·AopInfrastructureBean) 자신은 절대 프록시하지 않는다 — AOP 도구가 자기 자신을 감싸면 무한 재귀에 빠지기 때문.
createProxy()는 결국 챕터 2의 ProxyFactory(정확히는 그 부모 ProxyCreatorSupport)를 구성해 프록시를 뽑는다.
createProxy(beanClass, beanName, specificInterceptors, targetSource)
│
├ ProxyFactory.copyFrom(this) // proxyTargetClass 등 설정 복사
├ 인터페이스 결정 (proxyTargetClass면 클래스 프록시)
├ buildAdvisors(): specificInterceptors + commonInterceptors 를 Advisor[]로
├ proxyFactory.addAdvisors(advisors)
├ proxyFactory.setTargetSource(targetSource)
└ proxyFactory.getProxy(classLoader) → 챕터 2·3 메커니즘
즉 자동 프록시 생성기는 "어떤 빈에 어떤 어드바이저를" 의 정책만 담당하고, 실제 프록시 제작은 챕터 2의 엔진을 그대로 빌린다.
AbstractAdvisorAutoProxyCreator는 가장 널리 쓰이는 전략이다. getAdvicesAndAdvisorsForBean()을 "컨테이너에 등록된 모든 Advisor 빈 중, 이 대상에 적용 가능한 것" 으로 구현한다.
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource ts) {
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
}
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors(); // 1
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, // 2
beanClass, beanName);
extendAdvisors(eligibleAdvisors); // 3
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors); // 4
}
return eligibleAdvisors;
} 빈 X 생성 중
│
1) findCandidateAdvisors()
└ BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans()
컨테이너의 모든 Advisor 타입 빈을 수집
(+AspectJ 서브클래스는 @Aspect → Advisor 변환분도 추가, 챕터 5)
│
2) findAdvisorsThatCanApply(candidates, X)
└ AopUtils.findAdvisorsThatCanApply()
각 Advisor의 Pointcut으로 X에 적용 가능한지 검사
(ClassFilter + MethodMatcher 로 X의 메서드 중 하나라도 매칭?)
│
3) extendAdvisors() : 필요 시 ExposeInvocationInterceptor 등 보강
│
4) sortAdvisors() : @Order/Ordered 로 순서 정렬
│
▼
결과 Advisor[] → createProxy() (비었으면 DO_NOT_PROXY)
- (1) 후보 수집:
BeanFactoryAdvisorRetrievalHelper가BeanFactory에서Advisor타입 빈을 전부 찾는다. 이 헬퍼는 결과를 캐싱한다. - (2) 적용 가능성 필터:
AopUtils.findAdvisorsThatCanApply()가 각 후보의 포인트컷을 대상 클래스에 돌려, 메서드 중 하나라도 매칭되는 어드바이저만 남긴다. 이 과정 동안ProxyCreationContext에 현재 빈 이름을 ThreadLocal로 노출해, 포인트컷이 빈 이름을 참조할 수 있게 한다. - (3) 보강:
extendAdvisors()는 훅. AspectJ 계열은 여기서ExposeInvocationInterceptor.ADVISOR를 체인 맨 앞에 추가해, AspectJ 어드바이스가 현재 invocation을 ThreadLocal로 얻을 수 있게 한다. - (4) 정렬:
@Order/Ordered로 어드바이저 실행 순서를 정한다.AnnotationAwareOrderComparator사용.
이 전략의 자동 프록시는 항상 advisorsPreFiltered()=true를 반환한다 — 이미 클래스 단위로 걸렀으므로, 챕터 3의 체인 조립에서 isPreFiltered() 분기가 ClassFilter 재검사를 생략하게 한다.
InfrastructureAdvisorAutoProxyCreator:role == ROLE_INFRASTRUCTURE인 어드바이저만 본다. 프레임워크 내부용(트랜잭션 등@EnableTransactionManagement가 등록)으로, 사용자 어드바이저와 섞이지 않는다.DefaultAdvisorAutoProxyCreator: 컨테이너의 모든 어드바이저 빈을 대상으로 한다. XML 시절 범용 자동 프록시.BeanNameAutoProxyCreator:AbstractAutoProxyCreator직계. 어드바이저 매칭 대신 빈 이름 패턴("*Service"등)으로 프록시 대상을 지정하고, 공통 인터셉터를 모두에 적용한다.AnnotationAwareAspectJAutoProxyCreator:@EnableAspectJAutoProxy가 등록하는 것. @AspectJ를 추가로 처리한다(챕터 5).
AopConfigUtils가 자동 프록시 생성기 빈의 등록과 승급(escalation) 을 관리한다. 빈 이름은 org.springframework.aop.config.internalAutoProxyCreator 하나로 고정이며, 우선순위 사다리가 있다.
static { // 낮음 → 높음
APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
}여러 기능(@EnableTransactionManagement + @EnableAspectJAutoProxy)이 동시에 자동 프록시를 요구하면, 같은 빈 이름에 대해 더 강력한(높은 우선순위) 생성기로 승급시킨다. 그래서 하나의 자동 프록시 생성기가 모든 요구를 함께 처리한다. forceAutoProxyCreatorToUseClassProxying()·...ExposeProxy()로 proxyTargetClass/exposeProxy도 강제할 수 있다.
wrapIfNecessary(): 자동 프록시의 의사결정 본체. 인프라/skip/캐시 확인 → 어드바이저 조회 → 프록시 생성.getAdvicesAndAdvisorsForBean()(추상): "이 빈에 무엇을" 정책. 서브클래스마다 다르게 구현.findEligibleAdvisors(): 후보 수집 → 적용 가능 필터 → 보강 → 정렬.AopConfigUtils.registerOrEscalateApcAsRequired(): 자동 프록시 생성기 등록/승급.
- BeanPostProcessor 통합: AOP가 IoC 컨테이너에 투명하게 녹아드는 지점. 빈 정의는 그대로 두고 후처리 단계에서 원본을 프록시로 바꿔치기한다.
- 정책과 메커니즘의 분리: "어떤 빈에 무엇을(
getAdvicesAndAdvisorsForBean)"은 서브클래스 정책, "프록시를 어떻게 만드나(createProxy→ProxyFactory)"는 공통 메커니즘. - 승급 사다리: 한 빈 이름으로 자동 프록시 생성기를 단일화하고 필요 시 더 강한 구현으로 올림으로써, 여러 AOP 기능이 충돌 없이 한 프록시에 합류한다.
- 순환 참조 대응:
getEarlyBeanReference()로 조기 프록시를 노출하고earlyBeanReferences로 이중 래핑을 방지한다.
자동 프록시 생성기는 BeanPostProcessor로서 빈 초기화 후 wrapIfNecessary()를 호출해, 적용 가능한 어드바이저가 있으면 원본을 프록시로 바꿔 컨테이너에 등록한다. AbstractAdvisorAutoProxyCreator는 컨테이너의 Advisor 빈을 수집·필터·정렬해 적용하며, AopConfigUtils가 단일 생성기 빈을 우선순위에 따라 승급시켜 여러 AOP 기능을 한데 모은다. 다음 챕터는 @AspectJ 애너테이션이 이 흐름에 어떻게 Advisor로 합류하는지 본다.