티스토리 뷰

Spring

[Spring] BeanFactoryPostProcessor 타입의 빈(PropertySourcesPlaceholderConfigurer) 생성 메소드를 static으로 선언해야 하는 이유

망나니개발자 2021. 8. 1. 00:31
반응형

Spring을 이용해 개발을 하다 보면 빈팩토리 후처리기(BeanFactoryPostProcessor)의 구현체를 빈으로 등록해주어야 하는 경우가 있다. 해당 타입의 빈을 등록하기 위해서는 static 메소드로 선언해주어야 하는데, 이번에는 그 이유를 예시와 함께 알아보고자 한다.

 

 

 

1. BeanFactoryPostProcessor 타입의 빈 메소드를 static으로 해야 하는 이유


[ 빈팩토리 후처리기(BeanFactoryPostProcessor)와 PropertySourcesPlaceholderConfigurer ]

Spring은 기본적으로 객체로 만들 빈 설정정보(BeanDefinitino)를 만들어둔 다음에 이를 바탕으로 객체를 생성한다.

이때 빈 설정정보(BeanDefinition)를 불러오고 조작하는 것이 빈팩토리 후처리기(BeanFactoryPostProcessor)이며, 모든 빈들이 만들어진 직후에 객체의 내용이나 객체 자체를 변경하기 위한 것이 빈 후처리기(BeanPostProcessor) 이다.

Spring을 이용해 개발을 하다 보면 BeanFactoryPostProcessor의 구현체를 빈으로 등록해주어야 하는 경우가 있는데, 대표적인 경우가 @Value를 이용하는 경우이다. @Value를 이용하면 프로퍼티(Property)에 있는 값을 불러와 사용할 수 있다. (@Value에 대한 이해가 부족하다면 여기를 참고하도록 하자.)

예를 들어 우리는 프로퍼티에 있는 값을 다음과 같이 불러와서 사용한다. database.username과 같이 properties에 작성된 키 값을 ${}안에 넣어주면 우리가 원하는 값이 들어가는 것이다.

// properties
database.username = MangKyu
database.className=org.mariadb.jdbc.Driver

public class DatabaseConfig {

    // java code
    @Value("${database.username}")
    private String userName;

    @Value("${database.className}")
    private String className;

}

 

 

위와 같은 코드가 동작하는 방식은 다음과 같다.

  1. 빈 설정정보를 불러오는 빈팩토리 후처리기가 모든 빈 정보를 불러옴
  2. 빈 설정정보를 변경하는 빈팩토리 후처리기(PropertySourcesPlaceHolderConfigurer)가 ${} 값을 프로퍼티 값으로 치환함
  3. 빈 후처리기가 빈 설정정보를 바탕으로 객체를 생성함

 

최근 SpringBoot에는 기본적인 PropertySourcesPlaceholderConfigurer가 등록되어 있지만, @Value를 유연하게 사용하기 위해 추가적인 설정이 필요할 수 있다. 그럴때 우리는 PropertySourcesPlaceholderConfigurer를 재정의하고 빈으로 등록해주어야 한다. 하지만 여기서 주의해야 할 점이 있는데, PropertySourcesPlaceholderConfigurer와 같이 BeanFactoryPostProcessor의 구현체를 빈으로 등록할 경우에는 반드시 static 메소드로 선언해야 한다는 것이다.

@Bean 
public static PropertySourcesPlaceholderConfigurer properties() {
    ... 
}

 

 

 

[ BeanFactoryPostProcessor 타입의 빈(PropertySourcesPlaceholderConfigurer) 을 static 메소드로 선언해야 하는 이유 ]

PropertySourcesPlaceholderConfigurer와 같은 빈팩토리 후처리기(BeanFactoryPostPostProcessor)의 구현체들은 빈 정보를 불러와서 객체로 만들어지기 전에 빈 설정정보 자체를 조작하기 위해 사용된다고 하였다.

그렇기 때문에 후처리기의 동작이 필요한 어노테이션은 반드시 후처리기가 먼저 생성이 되어 있어야 한다. 예를 들어 @Value에 있는 값을 치환 하기 위해서는 PropertySourcesPlaceholderConfigurer 빈이 먼저 등록되어 있어야 하는 것이다.

만약 이러한 빈과 빈 팩토리 후처리기에 대한 생명주기(라이프사이클, LifeCycle)가 제대로 관리되지 않으면 예상치 못한 문제가 발생할 수 있다. 자세한 예시로 BeanFactoryPostProcessor의 구현체를 빈으로 등록하는 메소드를 static으로 선언하지 않았을 경우에 발생할 수 있는 상황을 살펴보도록 하자.

예시로 다음과 같이 PropertySourcesPlaceholderConfigurer 빈을 재정의하는 설정 클래스가 있고, 여기서 외부 서버의 주소를 properties에서 불러와서 다른 빈의 생성에 사용하는 상황이라고 하자.

@Configuration
public class TestConfig {

    @Value("${openapi.uri}")
    private String openApiUri;

    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        System.out.println("Create PropertySourcesPlaceholderConfigurer");
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public CustomHttpInvoker customHttpInvoker() {
        System.out.println("openapi.uri: " + openApiUri);
        return new CustomHttpInvoker(openApiUri);
    }

}

 

 

우리가 작성한 @Value가 동작하기 위해서는 TestConfig 클래스가 생성되기 이전에 PropertySourcesPlaceholderConfigurer 빈이 존재해야 한다. 그래야 스프링이 TestConfig 빈을 만들기 전에 PropertySourcesPlaceholderConfigurer가 TestConfig 빈의 설정정보에서 ${openapi.uri} 의 값을 프로퍼티의 값으로 치환해주고, 그리고 난 다음에 객체가 생성되어야 올바른 값이 openApiUri에 있을 것이기 때문이다.

하지만 현재의 상황은 TestConfig 클래스가 생성된 이후에 PropertySourcesPlaceholderConfigurer이 빈으로 등록되고 있다. 그래서 위의 예시에서 openApiUri는 null이 되고, 다른 네트워크와 통신하는 CustomHttpInvoker에 null이 전달되어 문제가 발생할 것이다.

그래서 우리는 PropertySourcesPlaceholderConfigurer를 등록하는 빈에 static을 붙여주어 TestConfig 클래스가 생성되기 이전에 PropertySourcesPlaceholderConfigurer를 빈으로 등록해주어야 한다. 빈을 등록하는 메소드에 static을 붙여주면 해당 빈들은 스프링 컨테이너의 라이프사이클 매우 초기에 다른 빈들보다 먼저 등록 된다. 그리고 이는 해당 메소드가 static이기 때문에 @Configuration 클래스들이 생성되기 전에 호출이 가능한 것이다.

@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    System.out.println("Create PropertySourcesPlaceholderConfigurer");
    return new PropertySourcesPlaceholderConfigurer();
}

 

 

그래서 우리는 위와 같은 문제를 방지하기 위해 BeanFactoryPostProcessor 타입의 빈을 생성하는 메소드를 static으로 선언하여 다른 빈들이 띄워지기 이전에 해당 빈들을 생성함으로써 이러한 생명주기 문제를 방지하는 것이다. 실제로 static을 붙이고 Create PropertySourcesPlaceholderConfigurer가 출력된 부분을 확인하면 다른 빈들이 등록되기 이전 즉, 컨테이너의 라이프사이클 매우 초기임을 확인할 수 있다.

 

@Value 외에도 @Autowired나 @PostConstruct 등과 같은 어노테이션도 빈팩토리 후처리기(BeanFactoryPostPostProcessor)의 구현체들에 의해 처리되므로 조심해야 한다.

 

 

 

[ 공식 문서에서의 설명 ]

이와 관련해서 Spring의 JavaDoc에는 다음과 같이 정리된 부분이 있다. 해당 페이지를 참고하면 이해에 도움이 될 것이다.

 

Special consideration must be taken for @Bean methods that return Spring BeanFactoryPostProcessor (BFPP) types. Because BFPP objects must be instantiated very early in the container lifecycle, they can interfere with processing of annotations such as @Autowired, @Value, and @PostConstruct within @Configuration classes. To avoid these lifecycle issues, mark BFPP-returning @Bean methods as static.

BeanFactoryPostProcessor(BFPP) 타입 객체는 @Configuration 클래스 내의 @Autowired, @Value, 그리고 @PostConstruct 와 같은 어노테이션들을 처리해야 하므로, 컨테이너의 라이프사이클 매우 초기에 생성되어야 한다. 이러한 라이프사이클 문제를 피하기 위해서는 Spring의 BeanFactoryPostProcessor(BFPP) 타입을 반환하는 @Bean 메소드는 static으로 선언해주어야 한다.

 

 

By marking this method as static, it can be invoked without causing instantiation of its declaring @Configuration class, thus avoiding the above-mentioned lifecycle conflicts.

해당 메소드를 static으로 정의함으로서 @Configuration 클래스의 객체가 생성되기 전에 초기화가 가능하기 때문에 위에서 언급한 라이프 사이클 문제를 회피할 수 있다.

 

 

 

 

위의 내용은 스프링 애플리케이션 컨텍스트의 refresh과정을 알지 못하면 이해하기가 어려울 수 있습니다. 관련 내용에 대해 조금 더 확실히 이해하고 싶다면 이 글을 통해서 빈팩토리 후처리기와 빈 후처리기에 대해 보고 오면 도움이 될 것 같습니다.

감사합니다:)

 

 

 

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함