스프링 핵심 원리 - 고급편 - 섹션7. 빈 후처리기
CS/김영한 스프링 강의

스프링 핵심 원리 - 고급편 - 섹션7. 빈 후처리기

빈 후처리기는 @Bean으로 정의한 걸 스프링이 컴포넌트 스캔해서 가져가는데, 빈 저장소에 넣기 직전에 뭔가를 더 해줄 수 있도록 정의하는 것. 이 빈 후처리기에서 바꿔주면 된다.

 

 

테스트 코드를 작성해보자.

 

public class BasicTest {

    @Test
    void basicConfig() {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(BasicConfig.class);

        // A는 빈으로 등록된다.
        A a = applicationContext.getBean("beanA", A.class);
        a.helloA();

        // B는 빈으로 등록되지 않는다.
        Assertions.assertThrows(
                NoSuchBeanDefinitionException.class,
                () -> applicationContext.getBean(B.class)
        );
    }

    @Slf4j
    @Configuration
    static class BasicConfig {
        @Bean(name = "beanA")
        public A a() {
            return new A();
        }
    }

    @Slf4j
    static class A {
        public void helloA() {
            log.info("hello A");
        }
    }

    @Slf4j
    static class B {
        public void helloB() {
            log.info("hello B");
        }
    }
}

A 클래스만 등록하는 예시이다. 당연히 A만 빈에 등록되어있고 B는 되어있지 않아 불러오려고 하면 에러가 뜬다.

이 A 클래스를 B로 바꿔보자.

 

public class BeanPostProcessorTest {

    @Test
    void basicConfig() {
        AnnotationConfigApplicationContext 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)
        );
    }

    @Slf4j
    @Configuration
    static class BeanPostProcessorConfig {
        @Bean(name = "beanA")
        public A a() {
            return new A();
        }

        @Bean
        public AToBPostProcessor helloPostProcessor() {
            return new AToBPostProcessor();
        }
    }

    @Slf4j
    static class A {
        public void helloA() {
            log.info("hello A");
        }
    }

    @Slf4j
    static class B {
        public void helloB() {
            log.info("hello B");
        }
    }

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

PostProcessor가 더 우선권을 가진다고 보면 된다. 후처리로 클래스를 바꿔주었다.

참고로 실무에선 @PostConstruct를 사용한다.

 

이제 실제로 해보자.

 

내가 원하는 클래스라면 그 클래스에 프록시를 씌워준다. 여기선 hello.proxy.app에 있는 클래스들이라면 씌우는 것으로 함.

기본값으로도 등록하는 빈들은 엄청 많기 때문에 저렇게 내가 원하는걸 if문으로 걸러내야 한다.

 

 

 

그럼 v3에서 컴포넌트 스캔을 해버려 프록시로 교체 못하는 문제도 이제 해결했다.

 

이제 사실 문제는 없는데, 포인트컷을 확장해서 어떤걸 프록시 클래스를 만들어 적용할지에 사용한다. 여기 위에선 특정 패키지 밑에 있는 것들을 프록시를 적용하라고 했지만, 포인트컷으로 하면 더 편리하다고. 이게 스프링이 제공하는 빈 후처리기다.

 

 

사용하려면 aspectjweaver라는 aspectJ 관련 라이브러리가 필요해서 패키지 깔아야 한다.

 

 

 

하는 방법은 위와 달리 configuration 클래스 하나만 만들면 된다.

 

 

이래도 되는 이유는, 전은 어떤 클래스에 프록시를 적용할 지 패키지 범위로써든 어쨌든 구체적으로 정했지만 이건 그럴 필요 없이, 저 포인트컷이 하나라도 적용될 수 있는 클래스라면 프록시를 적용한다. 즉, 난 함수만 작성하면 빈에 등록하는 모든 클래스를 점검해서 그걸 만족할 가능성이 있는 애들은 싸그리 프록시를 적용하는 것.

 

 

하지만 현재 함수 이름으로 조건을 걸고 했기 때문에 그냥 우연히 비슷한 함수명이 있는것도 다 프록시가 걸려버렸다.

이런것들 때문에 좀 더 구체화할 필요가 있다.

 

 

그래서 사용하는게 AspectJExpressionPointcut이다. 실무에선 이것만 쓴다고 함.

 

 

아직 noLog에 대한 예외는 없기 때문에 이것도 추가해주면 된다.

 

 

 

만약 어드바이저가 여러개일 경우, 그 여러개의 어드바이저를 적용한 프록시 클래스 하나를 만들어 적용한다. 헷갈릴 수 있어서 여러번 말하는 듯.