본문 바로가기
Spring/Advanced

빈 후처리기

by JHyun0302 2023. 8. 17.
728x90

빈 후처리기 - 소개

 

 

 

  • @Bean 이나 컴포넌트 스캔으로 빈을 등록하면, 스프링은 대상 객체를 생성하고 스프링 컨테이너 내부의 빈 저장소에 등록한다.
  • 이후에는 스프링 컨테이너를 통해 등록한 스프링 빈을 조회해서 사용하면 된다.

 

 

 

BeanPostProcessor : 생성한 객체를 빈 저장소에 등록하기 직전에 조작

  1. 객체를 조작할 수 있다.
  2. 완전히 다른 객체로 바꾸는 것도 가능하다.

 

 

 

빈 후처리기 과정

 

  1. 생성: 스프링 빈 대상이 되는 객체를 생성한다. ( @Bean , 컴포넌트 스캔 모두 포함)
  2. 전달: 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달한다.
  3. 후 처리 작업: 빈 후처리기는 전달된 스프링 빈 객체를 조작하거나 다른 객체로 바뀌치기 할 수 있다.
  4. 등록: 빈 후처리기가 빈을 반환한다. 빈을 그대로 반환하면 해당 빈이 등록되고, 바꿔치기 하면 다른 객체가 빈 저장소에 등록된다.

 

 

 

 

 

다른 객체로 바꿔치는 빈 후처리기

 

 

 

 

 

 

 

 

 


빈 후처리기 - 예제

 

 

 

일반적인 스프링 빈 등록 과정

 

 

 

 

 

 

빈 후처리기 - 스프링 빈 바꿔치기

 

 

 

 

BeanPostProcessor 인터페이스 - 스프링 제공

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
}

 

  • postProcessBeforeInitialization : 객체 생성 이후 @PostConstruct 같은 초기화가 발생하기 전에 호출되는 포스트 프로세서
  • postProcessAfterInitialization : 객체 생성 이후 @PostConstruct 같은 초기화가 발생한 다음에 호출되는 포스트 프로세서

 

 

 

 

 

BeanPostProcessorTest

public class BeanPostProcessorTest {
    
    @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));
    }

	// BeanPostProcessorConfig

    @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;
    	}
    } 
}

 

AToBPostProcessor - 빈 후처리기

BeanPostProcessor 를 구현하고, 스프링 빈으로 등록하면 스프링 컨테이너가 빈 후처리기로 인식하고 동작한다.

 

 

 

 

 

★ 정리

  • 빈 후처리기는 빈을 조작하고 변경할 수 있는 후킹 포인트이다.
  • 개발자가 등록하는 모든 빈을 중간에 조작할 수 있다. 즉, 빈 객체를 프록시로 교체하는 것도 가능하다.

 

 

 

 

 

 


빈 후처리기 - 적용

 

 

 

빈 후처리기 - 프록시로 바꿔치기

 

 

 

 

PackageLogTraceProxyPostProcessor

@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;
    }
}

 

 

 

BeanPostProcessorConfig

@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);
    }
}

 

→ 프록시를 적용할 패키지 정보(basePackage : hello.proxy.app)와 어드바이저( getAdvisor(logTrace) )를 넘겨준다.

  • 프록시를 생성하는 코드가 설정 파일에는 필요 없다. 순수한 빈 등록만 고민하면 된다.
  • 프록시를 생성하고 프록시를 스프링 빈으로 등록하는 것은 빈 후처리기가 모두 처리해준다.

 

 

 

v1: 인터페이스가 있으므로 JDK 동적 프록시가 적용된다. (인터페이스)

v2: 구체 클래스만 있으므로 CGLIB 프록시가 적용된다. (구체 클래스)

v3: 구체 클래스만 있으므로 CGLIB 프록시가 적용된다. (컴포넌트 스캔)

 

 

 

 

 

프록시 적용 대상 여부 체크 : basePackage 를 사용해서 특정 패키지를 기준으로 해당 패키지와그 하위 패키지의 빈들을 프록시 만듬.

 

 

 

 

 

 

 


빈 후처리기 - 정리

 

 

 

문제점

  1. 너무 많은 설정 
             → 프록시를 직접 스프링 빈으로 등록하는 프록시 관련 설정(@Configuration)이 지나치게 많다는 문제가 있다.
  2. 컴포넌트 스캔
            → 컴포넌트 스캔을 쓸 경우 프록시 적용 X.

 

 

 

문제 해결

  1. 프록시를 생성하는 부분을 하나로 집중
  2. 컴포넌트 스캔을 쓰더라도 중간에 빈 등록 과정을 가로채서 원본 대신에 프록시를 스프링 빈으로 등록할 수 있다.

 

 

 

 

 

★ 중요 - 포인트컷 사용

 

  1. 프록시 적용 대상 여부를 체크해서 꼭 필요한 곳에만 프록시를 적용한다. (빈 후처리기 - 자동 프록시 생성)
  2. 프록시의 어떤 메서드가 호출 되었을 때 어드바이스를 적용할 지 판단한다. (프록시 내부)

 

 

 

 

 

 

 

 

 


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

 

 

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-aop'

 

 

 

 

 

 

자동 프록시 생성기 - AutoProxyCreator

 자동으로 프록시를 생성해주는 빈 후처리기 (AnnotationAwareAspectJAutoProxyCreator)

 Advisor 와 @Aspect 자동으로 인식해서 프록시를 만들고 AOP를 적용

 

 

 

 

자동 프록시 생성기 동작 원리

 

 

 

 

 

 

생성된 프록시

 

 

 

 

 

 

★ 중요: 포인트컷 사용처

 

 

  1. 프록시 적용 여부 판단 - 생성 단계
             자동 프록시 생성기는 포인트컷을 사용해서 해당 빈이 프록시를 생성할 필요가 있는지 없는지 체크

  2. 어드바이스 적용 여부 판단 - 사용 단계
             프록시가 호출되었을 때 부가 기능인 어드바이스를 적용할지 말지 포인트컷을 보고 판단

 

 

 

 

 

 

 

 


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

 

 

 

AspectJExpressionPointcut

특별한 표현식으로 복잡한 포인트컷을 만들 수 있다.

 

 

 

 

 

 

AutoProxyConfig

@Bean
public Advisor advisor3(LogTrace logTrace) {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression("execution(* hello.proxy.app..*(..)) && !execution(* hello.proxy.app..noLog(..))");
    LogTraceAdvice advice = new LogTraceAdvice(logTrace);
    //advisor = pointcut + advice
    return new DefaultPointcutAdvisor(pointcut, advice);
}

 

 

execution(* hello.proxy.app..*(..)) : AspectJ가 제공하는 포인트컷 표현식이다.

  • * : 모든 반환 타입
  • hello.proxy.app.. : 해당 패키지와 그 하위 패키지
  • *(..) : * 모든 메서드 이름, (..) 파라미터는 상관 없음


 

 

 

 

 

 


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

 

 

 

※ 프록시 자동 생성기 상황별 정리

  • advisor1 포인트컷만 만족 ▶ 프록시1개 생성, 프록시에 advisor1 만 포함
  • advisor1 , advisor2 포인트컷을 모두 만족  프록시1개 생성, 프록시에 advisor1 , advisor2 모두 포함
  • advisor1 , advisor2 포인트컷을 모두 만족하지 않음  프록시가 생성되지 않음


 

 

 

 

 

 

1개 프록시, 여러 Advisor

 

 

 

 

 

 

 

 

 

 

 

 

1. Advisor만 스프링 빈 등록하면 된다.

2. Advisor = Pointcut + Advice

 

 

 

 

반응형

'Spring > Advanced' 카테고리의 다른 글

스프링 AOP 개념  (0) 2023.08.21
@Aspect AOP  (0) 2023.08.21
스프링이 지원하는 프록시  (0) 2023.08.17
동적 프록시 기술  (0) 2023.08.16
프록시 패턴과 데코레이터 패턴  (0) 2023.08.14