설명의
Spring Legacy라는 표현은Spring boot환경이 아닌 프로젝트를 의미합니다.
나는 지금 Spring Legacy 프로젝트에서 작업 중이며
Root Application ContextDispatcher 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
PlaceholderConfigurerSupportthat resolves ${...} placeholders within bean definition property values and@Valueannotations against the current SpringEnvironmentand its set ofPropertySources.This class is designed as a general replacement for
PropertyPlaceholderConfigurerintroduced in Spring 3.1. It is used by default to support theproperty-placeholderelement in working against the spring-context-3.1 or higher XSD, whereas spring-context versions <= 3.0 default toPropertyPlaceholderConfigurerto ensure backward compatibility. See the spring-context XSD documentation for complete details.Any local properties (e.g. those added via
setProperties,setLocationset al.) are added as aPropertySource. Search precedence of local properties is based on the value of thelocalOverrideproperty, which is by defaultfalsemeaning that local properties are to be searched last, after all environment property sources.See
org.springframework.core.env.ConfigurableEnvironmentand related javadocs for details on manipulating environment property sources.
2. org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
PlaceholderConfigurerSupportsubclass that resolves ${...} placeholders againstlocalpropertiesand/or system properties and environment variables.As of Spring 3.1,
PropertySourcesPlaceholderConfigurershould be used preferentially over this implementation; it is more flexible through taking advantage of theorg.springframework.core.env.Environmentandorg.springframework.core.env.PropertySourcemechanisms also made available in Spring 3.1.
PropertyPlaceholderConfigureris still appropriate for use when:
- the
spring-contextmodule is not available (i.e., one is using Spring'sBeanFactoryAPI 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.
이 글을 통해 저도 같은 문제를 해결했습니다! 감사합니다~