고딩왕 코범석

빈 후처리기와 자동 빈 후처리기 본문

Language & Framework/Spring

빈 후처리기와 자동 빈 후처리기

고딩왕 코범석 2021. 12. 14. 18:57
반응형

안녕하세요! 이번 시간에는 빈 후처리기에 대해 포스팅해보겠습니다. 해당 포스팅은 스프링 핵심 원리 - 고급편 을 정리한 포스팅입니다.


우선, 저는 빈 후처리기를 강의를 통해 처음 들었습니다. 프록시 객체를 생성하면서 문득 들었던 생각이 있었는데, 그럼 Component Scan 대상 객체의 프록시를 Configuration 없이 스프링 컨테이너에 등록할 수는 없을까? 였습니다.

🤔 빈 후처리기???

스프링은 빈으로 등록된 객체를 생성하고, 컨테이너에 등록합니다. 빈 후처리기는 객체를 빈으로 등록하기 전에 조작하는 객체 이며, 다음과 같은 라이프사이클을 가집니다.

  1. 생성 : 스프링 빈 대상이 되는 객체를 생성
  2. 전달 : 생성된 객체를 빈 저장소에 등록하기 직전, 빈 후처리기로 전달
  3. 후처리 작업 : 전달된 스프링 빈 객체를 조작한다.
  4. 등록 : 후처리기는 객체를 조작 후 조작된 객체를 반환하는데, 이 반환된 객체가 빈으로 등록, 스프링 컨테이너로 등록된다.

image


@PostConstruct

스프링은 빈을 Heap에 등록해놓고 사용하는 방식이기 때문에 생성자를 태울 수 없습니다. 그 이유는 스프링은 빈을 싱글톤으로 생성하는데, 생성자의 경우는 new 명령이 실행되기 때문입니다. 아예 못쓰지는 않지만 스프링에서는 쓰지 않는 것을 권장합니다. 이 때, 생성자를 쓰지 않고 빈이 생성될 때 실행을 하고 싶은 코드가 있다면 이 어노테이션을 사용하면 됩니다. 또한, 한번만 실행되기 때문에 빈을 생성했을 때, 초기화한다는 개념으로 이해하면 될 것 같습니다.

🌱 BeanPostProcessor

BeanPostProcessor라는 인터페이스를 통해 이를 구현하여 빈을 조작할 수 있습니다. 해당 인터페이스를 가보면


public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

이렇게 두 가지의 메서드가 존재하는데, 각각 빈이 초기화하기 전 / 후에 후처리를 할 수 있게 메서드를 제공해줍니다. 프록시를 스프링 빈으로 등록하고 싶다면, 이 인터페이스를 구현하여 조건에 맞춰 프록시를 리턴할지, 그냥 스캔된 빈을 리턴할지 구현하면 되며, 이렇게 리턴된 객체가 실제 스프링 컨테이너에 빈으로 등록됩니다.


@Slf4j
public class PackageLogTracePostProcessor implements BeanPostProcessor {

    private final String basePackage;
    private final Advisor advisor;

    public PackageLogTracePostProcessor(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;
    }
}

위 예제의 PackageLogTracePostProcessor를 Configuration에 등록하는 코드는 다음과 같습니다.


@Configuration
public class BeanPostProcessorConfig {

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

    private Advisor getAdvisor(LogTrace logTrace) {
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedNames("request*", "order*", "save*");
        LogTraceAdvice advice = new LogTraceAdvice(logTrace);
        return new DefaultPointcutAdvisor(pointcut, advice);
    }
}

🦸 자동 프록시 생성기 AnnotationAwareAspectJAutoProxyCreator

먼저 gradle에 implementation 'org.springframework.boot:spring-boot-starter-aop 를 주입받아 aspectJ 라이브러리를 등록합니다. 역할은 AOP 관련 클래스를 스프링부트에서 자동으로 스프링 빈으로 등록하고, @EnableAspectJAutoProxy 를 추가하지 않아도 자동으로 처리해줍니다.


AnnotationAwareAspectJAutoProxyCreator라는 빈 후처리기가 빈으로 등록됩니다. 이 빈 후처리기는 Advisor들을 찾아 조건이 맞을 경우 프록시 객체를 자동으로 빈 등록을 도와줍니다.

🧏 Advisor?

Advisor는 Pointcut + Advice 라고 생각하시면 됩니다.


Pointcut : 적용해야할 대상을 찾는 조건


Advice : 포인트컷 조건을 만족했을 때, 적용할 부가 기능


image


이 때, Advisor들도 Bean으로 등록해야하는데, Advisor들이 빈으로 먼저 등록되고 이후 advisor 들의 pointcut을 체크하여 프록시를 자동으로 생성해줍니다.

🛎️ Pointcut의 용도와 주의사항

  • 빈 생성단계에서 후처리기로 넘어온 빈을 프록시로 적용할지 판단합니다. 표현식에 의해 클래스와 메서드 조건들을 나열했을 때, 이 중 하나라도 만족한다면 프록시를 생성합니다.
  • 생성 이후, 프록시를 호출했을 때, Advice(부가기능)을 적용할지에 대한 여부도 Pointcut을 보고 판단합니다.
  • 모든 클래스가 프록시를 적용할 이유는 없기 때문에 Pointcut을 잘 작성하여 최소한의 프록시만 생성해야 합니다.
반응형