Skip to content

Latest commit

 

History

History
188 lines (151 loc) · 12.5 KB

File metadata and controls

188 lines (151 loc) · 12.5 KB

04. 자동 프록시 생성기 (Auto-Proxy Creator)

무엇을 / 왜

지금까지(챕터 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)

AbstractAutoProxyCreator — 언제 끼어드는가

이 클래스는 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)로 프록시된 빈을 두 번 감싸지 않기 위한 장치다.

wrapIfNecessary — 감쌀지 말지 결정

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 — ProxyFactory 재사용

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의 엔진을 그대로 빌린다.

동작 흐름 — Advisor 기반 자동 프록시

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) 후보 수집: BeanFactoryAdvisorRetrievalHelperBeanFactory에서 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

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)"은 서브클래스 정책, "프록시를 어떻게 만드나(createProxyProxyFactory)"는 공통 메커니즘.
  • 승급 사다리: 한 빈 이름으로 자동 프록시 생성기를 단일화하고 필요 시 더 강한 구현으로 올림으로써, 여러 AOP 기능이 충돌 없이 한 프록시에 합류한다.
  • 순환 참조 대응: getEarlyBeanReference()로 조기 프록시를 노출하고 earlyBeanReferences로 이중 래핑을 방지한다.

정리

자동 프록시 생성기는 BeanPostProcessor로서 빈 초기화 후 wrapIfNecessary()를 호출해, 적용 가능한 어드바이저가 있으면 원본을 프록시로 바꿔 컨테이너에 등록한다. AbstractAdvisorAutoProxyCreator는 컨테이너의 Advisor 빈을 수집·필터·정렬해 적용하며, AopConfigUtils가 단일 생성기 빈을 우선순위에 따라 승급시켜 여러 AOP 기능을 한데 모은다. 다음 챕터는 @AspectJ 애너테이션이 이 흐름에 어떻게 Advisor로 합류하는지 본다.