大师都是偏执的,偏执才能产生力量,妥协是没有力量的。你对全世界妥协了你就是空气。因此若没有偏见,哪来的大师呢
【小家Spring】详解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等对属性配置文件Properties的加载和使用
【小家Spring】Spring中@PropertySource和@ImportResource的区别,以及各自的实现原理解析java
【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为什么它有如此大的“能耐“spring
<center>对Spring感兴趣可扫码加入wx群:Java高工、架构师3群
(文末有二维码)</center>架构
写这篇文章的原动力是因为昨晚深夜一个小伙伴咨询个人一个问题(这位小伙伴这么晚了还在折腾,也是给个大大的赞),涉及到了如题方面的知识。
固然促使我书写本文最重要缘由的是:这种从传统Spring
项目向SpringBoot
迁移进阶的case,我我的认为在现阶段的环境下仍是有较大几率出现的,所以推荐收藏本文,对你后续或许有所帮助~app
为了更直观的说明问题所在,截图部分聊天记录以下:
这位小伙伴描述的问题仍是蛮清晰,因此我仍是很愿意跟他一块儿探讨的~ide
勾起兴趣还有一个缘由:Spring
对占位符提供了很是强大的支持,但基本上新手都还不能好好利用它和利用好它,更区分不清使用的规范和区别,本文也但愿作点努力,可以起到稍微一点的做用~
对此部份内容若须要热场
,推荐能够先浏览一下这篇文章:【小家Spring】Spring中@PropertySource和@ImportResource的区别,以及各自的实现原理解析 能够看到,早在我这篇文章里我就说了这么一句话:
而恰好这个小伙伴的场景(其实我本身还并无遇到过此场景),就类属于老项目
到SpringBoot新项目
的一个迁移case,这时不结合分析,更待什么时候呢。post
看到聊天记录,部分小伙伴可能会想:把Bean拿出来配置不就好了吗?或者key就写在原来的属性文件里呗?
其实对于职场老兵都能对此种现象给与理解和接受,毕竟有种叫 理想化,有种叫是叫 实际化~
由于我不可能贴出该小伙伴的源码(毕竟人家是生产环境的代码,咋可能贴出给大伙看呢?)so,接下来旨在说明这个问题,我就只好采用个人模拟大法喽:测试
本处以一个传统的Spring工程为例,模拟这种使用case。classpath
下有以下两个文件:
spring-beans.xml:this
<bean id="myPerson" class="com.fsx.bean.Person"> <property name="name" value="${diy.name}"/> <property name="age" value="18"/> </bean>
能够看到此xml配置Bean中使用了占位符:${diy.name}
来引用下面属性文件的属性值~spa
my.properties:.net
diy.name = fsx-fsx
使用@ImportResource
和@PropertySource
分别把它哥俩导入:
@ImportResource(locations = "classpath:spring-beans.xml") @PropertySource("classpath:my.properties") @Configuration public class RootConfig { }
运行以下测试用例:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class}) public class TestSpringBean { @Autowired private ApplicationContext applicationContext; @Autowired private Environment environment; @Test public void test1() { Person bean = (Person) applicationContext.getBean("myPerson"); System.out.println(bean); System.out.println(environment.getProperty("diy.name")); } }
打印结果为:
Person{name='${diy.name}', age=18} fsx-fsx
从结果中能够至少知道以下两点:
diy.name
这个属性k-v的。而且此处我附上截图以下:若你对技术有敏感性的话,你会疑问 为什么占位符没被解析但并无报错呢?
这个问题我在这篇文章: 【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为什么它有如此大的“能耐“ 里有过解释,有兴趣的能够点开看看(没兴趣的能够略过)
存在但又没被解析,看似有点矛盾,难道Spring工程不支持这么用,做为职场老兵的你,答案确定是否认的,那如何破呢?
其实解决起来很是简单,咱们只须要配置上一个PropertyResourceConfigurer
便可:
@Bean public PropertyResourceConfigurer propertyResourceConfigurer() { PropertyResourceConfigurer configurer = new PropertySourcesPlaceholderConfigurer(); // 使用的PropertySourcesPlaceholderConfigurer,不用本身再手动指定亦可处理占位符~~~ // configurer.setLocation(new ClassPathResource("my.properties")); // 加载指定的属性文件 return configurer; }
再次运行,打印以下:
Person{name='fsx-fsx', age=18} fsx-fsx
完美~
关于xml配置Bean处理占位符问题,为了加深理解,亦可参考:【小家Spring】Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的
我想说:此处介绍的是注解版怎么处理占位符问题,若你仍旧是传统的xml配置项目,至于具体使用哪一个标签,小伙伴自行寻找咯~
咱们知道PropertyResourceConfigurer
它是个抽象类,它的三大实现子类除了上例使用的,还有其他两大实现类:PropertyOverrideConfigurer
和PropertyPlaceholderConfigurer
,若注册它哥俩可行吗??? 行不行试试呗
PropertyOverrideConfigurer
利用属性文件的相关信息,覆盖XML 配置文件中Bean定义
。它要求配置的属性文件第一个.
前面是beanName
来匹配,因此这个子类我看都不用看,它确定不行(由于它改变了k-v的结构)。
**其实上面说配置PropertyResourceConfigurer
的实现类来处理是不太准确的。
准确的说应该是配置PlaceholderConfigurerSupport
的实现子类来处理Placeholder占位符
更精确,特此纠正哈~**
其实大多数小伙伴对PropertyPlaceholderConfigurer
比对PropertySourcesPlaceholderConfigurer
熟悉多了,毕竟前者的年纪
可大多了~
它哥俩都是PlaceholderConfigurerSupport
的实现子类有能力可以处理占位符
PropertySourcesPlaceholderConfigurer
是Spring 3.1后提供的,但愿用来取代PropertyPlaceholderConfigurer
@Bean public PropertyResourceConfigurer propertyResourceConfigurer() { PropertyResourceConfigurer configurer = new PropertyPlaceholderConfigurer(); //configurer.setLocation(new ClassPathResource("my.properties")); // 加载指定的属性文件 return configurer; }
运行上面用例就报错了:
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'diy.name' in value "${diy.name}" at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:172) at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:124)
what?看打印结果,明明environment.getProperty("diy.name")
从环境里能拿到这个key呀,怎么会报错呢???
这就是为什么Spring3.1
要提供一个PropertySourcesPlaceholderConfigurer
旨在想代替掉此类的缘由了。
可是,可是,可是把上配置中注掉的那行代码放开(也就是说本身设置location从而把属性文件加载进来),就能正常work了。
关于使用这种方式我还有必要再说明一点:若本身设置了location加载属性文件,@PropertySource("classpath:my.properties")
这句代码对此种场景就没有必要了,xml
配置的占位符也是可以读取到的。可是可是可是,若注掉这句@PropertySource...
,此时运行输出以下:
Person{name='fsx-fsx', age=18} null
会发现environment.getProperty("diy.name")
为null,也就是说该属性值并不会存在应用的环境内了(可是xml的占位符已被成功解析)。从个人这个截图中也能看出来环境里已经没它了:
至于这深处究竟是什么缘由,有兴趣的能够轻点这里:【小家Spring】详解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等对属性配置文件Properties的加载和使用
==只new一个PropertyPlaceholderConfigurer报错缘由分析:==
其实从源代码处一眼就能看出来缘由:
public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport { ... // 是否能被解析到值,重点在于入参的这个Properties props是否有这个key,而这个参数须要追溯它的父类~ @Override protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props); doProcessProperties(beanFactoryToProcess, valueResolver); } ... } // 从父类中看看props的传值是啥~~~ public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor, PriorityOrdered { ... @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { try { Properties mergedProps = mergeProperties(); // Convert the merged properties, if necessary. convertProperties(mergedProps); // Let the subclass process the properties. // 抽象方法,交给子类~~~这里传入的mergedProps,所有来自location~~~ processProperties(beanFactory, mergedProps); } catch (IOException ex) { throw new BeanInitializationException("Could not load properties", ex); } } protected Properties mergeProperties() throws IOException { ... loadProperties(result); ... } // 从配置里的location里把属性值都读出来~~~~~ protected void loadProperties(Properties props) throws IOException { if (this.locations != null) { for (Resource location : this.locations) { ... PropertiesLoaderUtils.fillProperties(props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister); ... } } } ... }
因而可知,若上述@Bean
配置使用的是PropertyPlaceholderConfigurer
,那必须手动的把属性文件设置location加载进去才行,不然是读取不到滴~
==那么问题来了,为什么使用PropertySourcesPlaceholderConfigurer,只须要简单的new一个就成了勒(并不须要手动设置location)?==
同样的,从源码处一看便知,很是很是简单:
// @since 3.1 直接实现了EnvironmentAware,说明此Bean能够拿到当前环境Environment public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware { ... @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { ... // 把环境属性都放进来~ if (this.environment != null) { this.propertySources.addLast( new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) { @Override @Nullable public String getProperty(String key) { return this.source.getProperty(key); } } ); } // 把配置的属性放进来~~~ PropertySource<?> localPropertySource = new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties()); this.propertySources.addFirst(localPropertySource); ... } }
相信不用我作过多的解释,就知道为什么不用本身设置location,直接使用注解@PropertySource("classpath:my.properties")
就好使了吧。这个时候环境截图以下(注意:此处我截图是基于已经set了location
的截图哦):
what?虽然配置时候set了location去加载属性文件,可是上面代码中add进去的属性源environmentProperties
和localProperties
public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties"; public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties";
两个PropertySource
都并无出现?
关于此,我这里就再也不解释了,哈哈。仍是那句话,留给小伙伴们本身思考,若思考不明白的亦可扫码入群探讨哦~(固然参照上面文章也是可行的)
关于在SpringBoot
中使用,简单到使人发指,毕竟SpringBoot
的使命也是让你使用它可以简单到木有朋友。
so,在SpringBoot
工程下使用@ImportResource
和@PropertySource
啥都不用配,它是可以自然的直接work的~
==缘由分析以下:==
一切得益于SpringBoot
强悍的自动化配置能力,它提供了这样一个配置类:
@Configuration @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) // 最高优先级 public class PropertyPlaceholderAutoConfiguration { // 注意此处使用的是PropertySourcesPlaceholderConfigurer // 而且你能够在本容器内覆盖它的默认行为哟~~~~ @Bean @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } }
PropertyPlaceholderAutoConfiguration
它被配置在了自动配置包下的spring.factories
文件里:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ ... org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ ...
所以它会随着工程启动而自动生效。有了上面对Spring
工程下的使用分析,此处就不用再花笔墨解释了~
另外附加说明一点:哪怕你的属性不使用@PropertySource
导入,而是写在SB
自带的application.properties
文件里,依旧是没有问题的。
==缘由分析以下:==
其实这个涉及到的是SpringBoot
对application.properties
的加载时机问题,由于本文对SB
的介绍并非重点,所以此处我直接简单的说出结论即止:
SpringBoot
经过事件监听机制加载不少东西,加载此属性文件用的是ConfigFileApplicationListener
这个监听器ApplicationEnvironmentPreparedEvent
(环境准备好后)事件后开始加载application.properties
等文件ApplicationEnvironmentPreparedEvent
的事件发出是发生在createApplicationContext()
以前~~~ 部分代码以下:public class SpringApplication { ... public ConfigurableApplicationContext run(String... args) { ... ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 此步骤 会发出ApplicationEnvironmentPreparedEvent事件 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); // 开始建立,初始化容器~ context = createApplicationContext(); ... } ... }
so,在SB
环境下已经早早把属性都放进环境内了,借助它默认配置好的PropertySourcesPlaceholderConfigurer
来处理的,那可不能正常work吗。一切都是这么天然,这或许就是SB
的魅力所在吧~
开头提出了问题,确定得解决问题嘛~~~以下图
哈哈,虽然最终我并无直接的帮助解决问题,可是此问题给了我写本文的动力,整体仍是不错的~
本文经过一个小伙伴咨询的小问题(真是小问题吗?)
引伸比较详细的说了Spring
在处理占位符这块的内容(其实本并没打算写这么多的,尴尬~)
写本文的目的开头也说了,我认为在SpringBoot
还并不是100%渗透的当下,确定有人会遇到从传统Spring
项目向SpringBoot
过分的一个阶段,而本文的描述或许能给你的迁移提供一种新的思路(特别是时间紧、任务重的时候),但愿小伙伴们能有所收获,peace~
若文章格式混乱,可点击
:
原文连接-原文连接-原文连接-原文连接-原文连接
==The last:若是以为本文对你有帮助,不妨点个赞呗。固然分享到你的朋友圈让更多小伙伴看到也是被做者本人许可的~
==
**若对技术内容感兴趣能够加入wx群交流:Java高工、架构师3群
。
若群二维码失效,请加wx号:fsx641385712
(或者扫描下方wx二维码)。而且备注:"java入群"
字样,会手动邀请入群**