설명의
Spring Legacy
라는 표현은Spring boot
환경이 아닌 프로젝트를 의미합니다.
나는 지금 Spring Legacy 프로젝트에서 작업 중이며
Root Application Context
Dispatcher Servlet Application Context
각각 xml 설정 파일이 나뉘어져 있다.
이게 핵심적인 원인을 제공하기 때문에 미리 말하는 것이다.
작업 도중 @Controller
에서 API를 호출하는 부분이 있는데,
이때 *.properties
파일에서 API Key 값을 읽어와야 했다.
스프링이 제공하는 @Value
를 사용해서 아래처럼 읽어오려고 했다.
@Controller
@RequestMapping("/somewhere/you/dont/know")
public class BlaBlaController {
@Value("${api.key}")
private String apiKey;
// 생략...
}
그런데 뭔가 이상하다.
디버거로 찍어서 apiKey 값을 확인하면 아래와 같다.
apiKey =
"${api.key}"
; // resolve 가 안되고 "${~}" 문자열 그대로 준다;;
혹시나 해서 ROOT application context에서 사용하는 xml 설정 파일에 propertyConfigurer 설정을 봤다.
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/work/${spring.profiles.active}.properties</value>
</list>
</property>
</bean>
후우... 일단 공식 문서와 API 정보를 찾아다녔다.
일단 현재 상태는 "${~}"
가 resolve 가 되지는 않지만 private String
필드에 정상적으로 입력되는 상태이다.
그렇다면 나눠서 생각해보자.
@Value
를 필드의 값에 매핑해주는 존재는 누구?@Value("${api.key}")
에 있는 "${~}"
를 resolve 해주는 존재는 누구?@Value
를 필드의 값에 매핑해주는 존재이다.
이 클래스는 BeanPostProcessor
(앞으로는 BPP라고 부르겠다) 인터페이스를 구현하는 bean으로 bean
에 @Autowired
또는 @Value
애노테이션이 있다면 처리를 해준다.
그래서 지금은 비록 "${~}"
에 대한 resolve 는 해결을 못했지만 String 필드에
그 문자열 "${~}"
자체가 잘 들어가는 것을 확인할 수 있다.
일단 이 녀석 문제는 아니라는 것을 알 수 있다.
@Value("${api.key}")
에 있는 "${~}"
를 resolve 해주는 존재이다.
이 클래스는 BeanFactoryPostProcessor
(앞으로 BFPP 라고 부르겠다) 인터페이스를 구현한 bean이다.
이 bean은 Application Context
가 Bean Definition 에 대한 정보를 모두 얻었을 때, Bean Factory 에 대한 참조를 얻어서 조작이 가능하도록 해준다.
이러한 BFPP 는 하나의 인터페이스 메소드를 제공한다.
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
설명: Modify the application context's internal bean factory after its standard initialization. All bean definitions will have been loaded, but no beans will have been instantiated yet. This allows for overriding or adding properties even to eager-initializing beans.
설명을 대충 보면 알겠지만 Application Context 가 내부적으로 갖는 bean factory의 초기화가 완료되고, 해당 bean factory 에 대한 참조를 받을 수 있다.
그리고 그 bean factory로 로딩된 bean definition의 정보를 조작할 수 있다.
참고
아래 그림은PropertyPlaceholderConfigurer
클래스의 부모 클래스인PropertyResourceConfigurer
에 있는postProcessBeanFactory
메소드이다.bean factory 로부터 프로퍼티에 대한 정보를 읽어서 뭔 작업을 해준다는 것을 직감할 수 있다.
분명 PropertyPlaceholderConfigurer 가 범인이라는 느낌이 강하다.
root application context
의 xml 파일 설정에 분명 선언을 했는데 왜 이럴까?
핵심적인 이유는 구글링을 하면서 알아냈다.
이 spring-framwork issues글의 말을 빌리면 아래와 같다.
Also, BeanFactoryPostProcessors are scoped per-container. This is only relevant if you are using container hierarchies. If you define a BeanFactoryPostProcessor in one container, it will only be applied to the bean definitions in that container. Bean definitions in one container will not be post-processed by BeanFactoryPostProcessors in another container, even if both containers are part of the same hierarchy.
참고. BeanPostProcessors are
scoped per-container
...
하나의 BeanFactoryPostProcessors
는 자신이 속한 Application Context에만 영향을 준다는 것이 핵심이다.
나는 root application context
에만 PropertyPlaceholderConfigurer
를 설정해주고, servlet application context
에 PropertyPlaceholderConfigurer
를 설정하지 않아서 이런 일이 발생한 것이다.
Dispatcher Servlet Application Context
의 xml 설정 파일에도 아래처럼 작성하면 된다.
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/work/${spring.profiles.active}.properties</value>
</list>
</property>
</bean>
이러고 나서 다시 테스트하면 정상적으로 프로퍼티 value 값을 읽어온다.
참고로 간단하게 아래처럼 namespace를 사용해서 위 설정을 대체할 수 있다.
<context:property-placeholder
location="classpath:/work/${spring.profiles.active}.properties"/>
context:property-placeholder
설명
참고로 context:property-placeholder
은 PropertySourcesPlaceholderConfigurer
를
사용하고 내 xml 설정에서는 PropertyPlaceholderConfigurer
를 하고 있다.
일단 미리 말해두지만 둘 다 사용해도 지금의 상황에서는 동일하게 작동한다!
둘의 차이가 궁금하다면 아래 보충
를 읽어보자.
참고로 공식 문서에서는 PropertySourcesPlaceholderConfigurer
가 PropertyPlaceholderConfigurer
를 대체하기 위해 만들어졌다고 하니, 특별한 경우가 아니라면 PropertySourcesPlaceholderConfigurer
를 사용하자.😊
1. org.springframework.context.support.PropertySourcesPlaceholderConfigurer
Specialization of
PlaceholderConfigurerSupport
that resolves ${...} placeholders within bean definition property values and@Value
annotations against the current SpringEnvironment
and its set ofPropertySources
.This class is designed as a general replacement for
PropertyPlaceholderConfigurer
introduced in Spring 3.1. It is used by default to support theproperty-placeholder
element in working against the spring-context-3.1 or higher XSD, whereas spring-context versions <= 3.0 default toPropertyPlaceholderConfigurer
to ensure backward compatibility. See the spring-context XSD documentation for complete details.Any local properties (e.g. those added via
setProperties
,setLocations
et al.) are added as aPropertySource
. Search precedence of local properties is based on the value of thelocalOverride
property, which is by defaultfalse
meaning that local properties are to be searched last, after all environment property sources.See
org.springframework.core.env.ConfigurableEnvironment
and related javadocs for details on manipulating environment property sources.
2. org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
PlaceholderConfigurerSupport
subclass that resolves ${...} placeholders againstlocal
properties
and/or system properties and environment variables.As of Spring 3.1,
PropertySourcesPlaceholderConfigurer
should be used preferentially over this implementation; it is more flexible through taking advantage of theorg.springframework.core.env.Environment
andorg.springframework.core.env.PropertySource
mechanisms also made available in Spring 3.1.
PropertyPlaceholderConfigurer
is still appropriate for use when:
- the
spring-context
module is not available (i.e., one is using Spring'sBeanFactory
API as opposed toApplicationContext
).- existing configuration makes use of the
"systemPropertiesMode"
and/or"systemPropertiesModeName"
properties. Users are encouraged to move away from using these settings, and rather configure property source search order through the container'sEnvironment
; however, exact preservation of functionality may be maintained by continuing to usePropertyPlaceholderConfigurer
.
https://github.com/spring-projects/spring-framework/issues/13634
Also, BeanFactoryPostProcessors are scoped per-container. This is only relevant if you are using container hierarchies. If you define a BeanFactoryPostProcessor in one container, it will only be applied to the bean definitions in that container. Bean definitions in one container will not be post-processed by BeanFactoryPostProcessors in another container, even if both containers are part of the same hierarchy.
A final word on the placeholder support. This class is a BeanFactoryPostProcessor and it will process bean definitions and replace the placeholders in them. However it does this only for beans in the same application context!. If you defined this bean in the parent and expect it to replace placeholders in the child context then that isn't going to happen.
이 글을 통해 저도 같은 문제를 해결했습니다! 감사합니다~