김영한의 스프링 핵심 원리(고급편) - 빈 후처리기

2025. 2. 22. 02:05·김영한의 스프링 핵심 원리 - 고급편

Quesiton

  • 스프링 빈이 등록되는 과정을 빈 후처리기를 포함하여 설명하시오
  • 빈 후처리기를 사용하면 기존에 컴포넌트 스캔을 활용할 수 없는 문제와 각 빈마다 프록시 팩토리를 만들어야 하는 중복 문제를 어떻게 개선하는가?
  • 빈 후처리기 사용 시 대상 여부 판단에서 주의할 점은?
  • 빈 후처리기 사용 시 포인트컷이 사용되는 2가지 부분은?
  • 하나의 빈에 여러 어드바이저가 적용된다면 어떠한 현상이 벌어지는가?

 

빈 후처리기

  • 일반적으로 @Bean이나 컴포넌트 스캔으로 스프링 빈을 등록하면, 스프링은 대상 객체를 생성하고 스프링 컨테이너 내부의 빈 저장소에 등록
  • 이후에서는 스프링 컨테이너를 통해 빈 저장소에 등록된 빈을 조회해서 사용
  • 빈 후처리기(BeanPostProcessor)는 빈 저장소에 등록할 목적으로 생성된 객체를 빈 저장소에 등록하기 직전에 조작할 수 있음
  • 빈 후처리기는 말 그대로 빈을 생성한 후 무언가를 처리하는 용도
  • 객체를 조작할 수도 있고 다른 객체로 바꿔치기도 가능

 

빈 후처리기로 빈 바꿔치기

@Slf4j
static class AToBPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        log.info("beanName={} bean={}", beanName, bean);
        if (bean instanceof A) {
            return new B();
        }
        return bean;
    }
}
  • BeanPostProcessor를 구현하는 빈후처리기 클래스 생성
  • postProcessAfterInitialization 함수를 오버라이드
  • 만약에 bean이 A면 B를 반환하는 코드로 빈 바꿔치기 시도
@Slf4j
@Configuration
static class BeanPostProcessorConfig {
    @Bean(name = "beanA")
    public A a() {
        return new A();
    }

    @Bean
    public AToBPostProcessor helloPostProcessor() {
        return new AToBPostProcessor();
    }
}
  • beanA도 스프링 빈으로 등록하고 앞서 만든 빈후처리기도 스프링 빈으로 등록
@Test
void postProcessor() {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanPostProcessorConfig.class);

    // beanA 이름으로 B 객체가 빈으로 등록된다.
    B b = applicationContext.getBean("beanA", B.class);
    b.helloB();

    // A는 빈으로 등록되지 않는다.
    Assertions.assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(A.class));
}
  • 스프링 컨테이너에서 beanA라는 빈 가져오기 시도
  • 하지만, beanA를 가져왔지만 실제로는 beanB인 상황
  • beanA -> beanB로 바꿔치기 됨
  • 이 말은 우리가 원하는대로 특정 bean을 프록시로 교체 가능하다는 말

 

빈 후처리기를 LogTrace에 적용

@Slf4j
public class PackageLogTraceProxyPostProcessor implements BeanPostProcessor {
    private final String basePackage;
    private final Advisor advisor;

    public PackageLogTraceProxyPostProcessor(String basePackage, Advisor advisor) {
        this.basePackage = basePackage;
        this.advisor = advisor;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        log.info("param beanName={} bean={}", beanName, bean.getClass());
        
        // 프록시 적용 대상 여부 체크
        // 프록시 적용 대상이 아니면 원본을 그대로 반환
        String packageName = bean.getClass().getPackageName();
        if (!packageName.startsWith(basePackage)) {
            return bean;
        }

        // 프록시 대상이면 프록시를 만들어서 반환
        ProxyFactory proxyFactory = new ProxyFactory(bean);
        proxyFactory.addAdvisor(advisor);
        Object proxy = proxyFactory.getProxy();
        log.info("create proxy: target={} proxy={}", bean.getClass(), proxy.getClass());
        return proxy;
    }
}
  • 프록시 팩토리로 프록시를 만들어서 우리가 원하는 빈을 프록시로 반환해주려고 함
  • 프록시 팩토리에 넣어줄 어드바이저는 외부에서 주입해줌
  • 중요한 점은 지정한 basePackage가 아닌 경우에는 일반 bean을 리턴하게 한 부분
  • 왜냐하면, 스프링은 우리가 직접 등록하는 빈 외에 기본 빈들이 무수히 많아서, 이러한 빈들에는 프록시 설정을 해 줄 필요가 없기 때문
  • 또한, 특정 기본 빈들은 프록시 객체를 만들 수 없는 빈도 있어 오류가 생길 수 있음
@Slf4j
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class BeanPostProcessorConfig {

    @Bean
    public PackageLogTraceProxyPostProcessor logTraceProxyPostProcessor(LogTrace logTrace) {
        return new PackageLogTraceProxyPostProcessor("hello.proxy.app", getAdvisor(logTrace));
    }

    private Advisor getAdvisor(LogTrace logTrace) {
        // pointcut
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedNames("request*", "order*", "save*");

        // advice
        LogTraceAdvice advice = new LogTraceAdvice(logTrace);

        // advisor = pointcut + advice
        return new DefaultPointcutAdvisor(pointcut, advice);
    }
}
  • 빈 후처리기를 빈으로 등록
  • 빈 후처리기의 파라미터로 베이스 페키지 경로를 넣어 줬음
  • 어드바이저는 외부에서 주입해주기로 했으므로 어드바이저도 파라미터로 넣어 줌
  • 어드바이저에는 포인트 컷과 어드바이스르 넣어 줘야 함
  • 이 Configuration에는 더 이상 프록시 생성 코드가 존재하지 않음

 

빈 후처리기로 프록시 팩토리의 문제점 해결

  • 프록시 생성을 하나의 부분으로 집중 가능
  • 여러 스프링 빈마다 중복으로 프록시 팩토리 코드 넣어야 하는 중복 문제 해결
  • 컴포넌트 스캔처럼 자동 생성 빈에도 프록시 적용 가능

 

 

스프링이 제공하는 빈 후처리기

  • 스프링 부트는 자동으로 AnnotationAwareAspectJAutoProxyCreator 라는 빈 후처리기를 빈으로 등록
  • 자동으로 프록시를 생성해주는 빈 후처리기
  • 스프링 빈으로 등록된 Advisor를 자동으로 찾아서 프록시가 필요한 곳에 적용해줌
  • Advisor 안에는 Pointcut과 Advice가 포함되어 있기 때문에 Advisor만 알면 그 안의 Pointcut으로 어떤 스프링 빈에 프록시를 적용할 지 알 수 있음
  • Pointcut만 알면 어떤 스프링 빈에 프록시를 적용할 지 알 수 있다는 점이 핵심
  • 객체의 모든 메서드를 포인트컷에 매칭해보고 하나라도 만족하면 프록시 대상, 하나도 만족하지 못하면 원본 객체를 스프링 빈으로 등록

 

스프링이 제공하는 빈 후처리기 사용 방법

@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class AutoProxyConfig {

    @Bean
    public Advisor advisor1(LogTrace logTrace) {
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedNames("request*", "order*", "save*");

        LogTraceAdvice advice = new LogTraceAdvice(logTrace);

        // advisor = pointcut + advice
        return new DefaultPointcutAdvisor(pointcut, advice);
    }
}
  • 위와 같이 어드바이저만 빈으로 등록하면 스프링이 프록시를 자동으로 생성해줌
  • 더 이상 빈 후처리기를 우리가 만들어 줄 필요가 없음
  • 스프링이 그냥 "request*", "order*", "save*"에 매칭되는 메서드를 가친 모든 빈을 advice를 적용한 프록시 빈으로 등록함.

 

포인트 컷이 사용되는 2곳

  • 기존에 포인트 컷은 advice를 적용할 위치를 지정하는 하나의 용도로 사용되었지만 이젠 두 곳에서 사용 됨
  • 프록시 적용 여부 판단 - 생성 단계
    • 만약 포인트컷의 조건에 맞는 메서드가 하나라도 있으면 프록시를 생성
    • 만약 포인트컷의 조건에 맞는 메서드가 하나도 없으면 프록시를 생성할 필요가 없으므로, 프록시를 생성하지 않음
    어드바이스 적용 여부 판단 - 사용 단계
    • 프록시가 호출되었을 때 부가 기능인 어드바이스를 적용할지 말지를 포인트컷을 보고 판단

 

 

포인트 컷 개선

@Bean
public Advisor advisor2(LogTrace logTrace) {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression("execution(* hello.proxy.app..*(..))");

    LogTraceAdvice advice = new LogTraceAdvice(logTrace);

    // advisor = pointcut + advice
    return new DefaultPointcutAdvisor(pointcut, advice);
}
  • "request*", "order*", "save*" 와 같은 포인트 컷은 우리가 원치 않는 여러 메서드도 포함되는 문제가 발생할 수 있음
  • 따라서, 더 정교한 포인트컷을 만들 필요가 있음
  • pointcut.setExpression("execution(* hello.proxy.app..*(..))")
    • AspectJ가 제공하는 포인트컷 형식
    • 추후 더 자세히 다룰 예정
    • * : 모든 반환 타입
    • hello.proxy.app.. : 해당 패키지와 그 하위 패키지
    • *(..) : * 모든 메서드 이름, (..) 파라미터는 상관 없음

 

 

하나의 프록시에 여러 Advisor 적용

  • 만약 하나의 빈에 여러 Advisor의 포인트 컷이 만족한다면?
  • 프록시 자동 생성기 상황별 정리
    • advisor1 의 포인트컷만 만족 프록시1개 생성, 프록시에 advisor1 만 포함
    • advisor1 , advisor2 의 포인트컷을 모두 만족 프록시1개 생성, 프록시에 advisor1 , advisor2 모두 포함
    • advisor1 , advisor2 의 포인트컷을 모두 만족하지 않음 프록시가 생성되지 않음
  • 중요한 점은 프록시가 여러 개가 생성되는 것이 아닌 하나의 프록시에 여러 advisor가 등록 됨

 

 

 

'김영한의 스프링 핵심 원리 - 고급편' 카테고리의 다른 글

김영한의 스프링 핵심 원리(고급편) - AOP의 포인트컷, AOP의 어드바이스 종류  (0) 2025.02.25
김영한의 스프링 핵심 원리(고급편) - @Aspect, AOP  (0) 2025.02.24
김영한의 스프링 핵심 원리(고급편) - 프록시 팩토리  (0) 2025.02.22
김영한의 스프링 핵심 원리(고급편) - 동적 프록시 기술  (0) 2025.02.21
김영한의 스프링 핵심 원리(고급편) - 프록시 패턴, 데코레이터 패턴  (0) 2025.02.21
'김영한의 스프링 핵심 원리 - 고급편' 카테고리의 다른 글
  • 김영한의 스프링 핵심 원리(고급편) - AOP의 포인트컷, AOP의 어드바이스 종류
  • 김영한의 스프링 핵심 원리(고급편) - @Aspect, AOP
  • 김영한의 스프링 핵심 원리(고급편) - 프록시 팩토리
  • 김영한의 스프링 핵심 원리(고급편) - 동적 프록시 기술
5jyan5
5jyan5
  • 5jyan5
    jyan
    5jyan5
  • 전체
    오늘
    어제
    • 분류 전체보기 (242)
      • 김영한의 스프링 핵심 원리(기본편) (8)
      • 김영한의 스프링 핵심 원리 - 고급편 (11)
      • 김영한의 스프링 MVC 1편 (1)
      • 김영한의 스프링 DB 1편 (3)
      • 김영한의 스프링 MVC 2편 (3)
      • 김영한의 ORM 표준 JPA 프로그래밍(기본편) (9)
      • 김영한의 스프링 부트와 JPA 활용2 (2)
      • 김영한의 실전 자바 - 중급 1편 (1)
      • 김영한의 실전 자바 - 고급 1편 (9)
      • 김영한의 실전 자바 - 고급 2편 (9)
      • Readable Code: 읽기 좋은 코드를 작성.. (2)
      • 김영한의 실전 자바 - 고급 3편 (9)
      • CKA (118)
      • 개발 (37)
      • 경제 (4)
      • 리뷰 (1)
      • 정보 (2)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      @args
      log trace
      @within
      버퍼
      페치 조인
      jdk 동적 프록시
      JPQL
      자바
      빈 후처리기
      hibernate5module
      단방향 맵핑
      양방향 맵핑
      @discriminatorcolumn
      requset scope
      WAS
      typequery
      jpq
      조회 성능 최적화
      락
      김영한
      고급
      Target
      gesingleresult
      프록시 팩토리
      Thread
      cglib
      스레드
      reentarantlock
      @discriminatorvalue
      프록시
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.2
    5jyan5
    김영한의 스프링 핵심 원리(고급편) - 빈 후처리기
    상단으로

    티스토리툴바