#### 每篇一句
> 大师都是偏执的,偏执才能产生力量,妥协是没有力量的。你对全世界妥协了你就是空气。所以若没有偏见,哪来的大师呢

#### 相关阅读
[【小家Spring】详解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等对属性配置文件Properties的加载和使用](https://blog.csdn.net/f641385712/article/details/91444601)
[【小家Spring】Spring中@PropertySource和@ImportResource的区别,以及各自的实现原理解析](https://blog.csdn.net/f641385712/article/details/90636649)

[【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为何它有如此大的“能耐“](https://blog.csdn.net/f641385712/article/details/91043955)

---
对Spring感兴趣可扫码加入wx群:`Java高工、架构师3群`(文末有二维码)

---

#### 前言
写这篇文章的原动力是由于**昨晚深夜**一个小伙伴咨询我的一个问题(这位小伙伴这么晚了还在折腾,也是给个大大的赞),涉及到了如题方面的知识。
当然促使我书写本文最重要原因的是:这种从传统`Spring`项目向`SpringBoot`迁移进阶的case,我个人认为在现阶段的环境下还是有**较大概率**出现的,**因此推荐收藏本文,对你后续或许有所帮助~**

### 情景描述
为了更直观的说明问题所在,截图部分聊天记录如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190716222844516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70)
**这位小伙伴描述的问题还是蛮清晰,所以我还是很愿意跟他一起探讨的~**

勾起兴趣还有一个原因:`Spring`对占位符提供了非常强大的支持,**但基本上新手**都还不能好好利用它和利用好它,更区分不清使用的规范和区别,本文也希望做点努力,能够起到稍微一点的作用~
对此部分内容若需要`热场`,推荐可以先浏览一下这篇文章:[【小家Spring】Spring中@PropertySource和@ImportResource的区别,以及各自的实现原理解析](https://blog.csdn.net/f641385712/article/details/90636649) 可以看到,早在我这篇文章里我就说了这么一句话:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190716223321427.png)
而刚好这个小伙伴的场景(其实我自己还并没有遇到过此场景),就类属于`老项目`到`SpringBoot新项目`的一个迁移case,这时不结合分析,更待何时呢。
> 看到聊天记录,部分小伙伴可能会想:把Bean拿出来配置不就行了吗?或者key就写在原来的属性文件里呗?
> 其实对于职场老兵都能对此种现象给与理解和接受,毕竟有种叫**理想化**,有种叫是叫**实际化**~

---
---
---
因为我不可能贴出该小伙伴的源码(毕竟人家是生产环境的代码,咋可能贴出给大伙看呢?)so,接下来旨在说明这个问题,我就只好采用我的**模拟大法**喽:
#### 传统Spring工程下使用
本处以一个传统的Spring工程为例,模拟这种使用case。`classpath`下有如下两个文件:
**spring-beans.xml:**
```xml

```
可以看到此xml配置Bean中使用了占位符:`${diy.name}`来引用下面属性文件的属性值~

**my.properties:**
```java
diy.name = fsx-fsx
```
使用`@ImportResource`和`@PropertySource`分别把它哥俩导入:
```java
@ImportResource(locations = "classpath:spring-beans.xml")
@PropertySource("classpath:my.properties")
@Configuration
public class RootConfig {
}
```
运行如下测试用例:
```java
@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"));
}

}
```
打印结果为:
```java
Person{name='${diy.name}', age=18}
fsx-fsx
```
从结果中可以至少知道如下两点:

1. 环境变量里是存在`diy.name`这个属性k-v的。并且此处我附上截图如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717202659570.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70)
2. xml中的占位符**并没有**被解析

> 若你对技术有敏感性的话,你会疑问**为何占位符没被解析但并没有报错呢?**
> 这个问题我在这篇文章:[【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为何它有如此大的“能耐“](https://blog.csdn.net/f641385712/article/details/91043955) 里有过解释,有兴趣的可以点开看看(没兴趣的可以略过)

**存在但又没被解析,看似有点矛盾,难道Spring工程不支持这么用,作为职场老兵的你,答案肯定是否定的,那如何破呢?**
其实解决起来非常简单,我们只需要配置上一个`PropertyResourceConfigurer`即可:
```java
@Bean
public PropertyResourceConfigurer propertyResourceConfigurer() {
PropertyResourceConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
// 使用的PropertySourcesPlaceholderConfigurer,不用自己再手动指定亦可处理占位符~~~
// configurer.setLocation(new ClassPathResource("my.properties")); // 加载指定的属性文件
return configurer;
}
```
再次运行,打印如下:
```java
Person{name='fsx-fsx', age=18}
fsx-fsx
```
**完美~**

关于xml配置Bean处理占位符问题,为了加深理解,亦可参考:[【小家Spring】Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的](https://blog.csdn.net/f641385712/article/details/91444601)

> 我想说:此处介绍的是注解版怎么处理占位符问题,若你仍旧是传统的xml配置项目,至于具体使用哪个标签,小伙伴自行寻找咯~
---
我们知道`PropertyResourceConfigurer`它是个抽象类,它的三大实现子类除了上例使用的,还有其余两大实现类:`PropertyOverrideConfigurer`和`PropertyPlaceholderConfigurer`,**若注册它哥俩可行吗???** 行不行试试呗
###### 使用PropertyOverrideConfigurer
`PropertyOverrideConfigurer` 利用属性文件的相关信息,覆盖XML 配置文件中`Bean定义`。它要求配置的属性文件第一个`.`前面是`beanName`来匹配,所以这个子类我看都不用看,它肯定不行(因为它改变了k-v的结构)。

> **其实上面说配置`PropertyResourceConfigurer`的实现类来处理是不太准确的。
> 准确的说应该是配置`PlaceholderConfigurerSupport`的实现子类来处理`Placeholder占位符`更精确,特此纠正哈~**
###### 使用PropertyPlaceholderConfigurer
其实大多数小伙伴对`PropertyPlaceholderConfigurer`比对`PropertySourcesPlaceholderConfigurer`熟悉多了,毕竟前者的`年纪`可大多了~
它哥俩都是`PlaceholderConfigurerSupport`的实现子类有能力能够处理占位符
> `PropertySourcesPlaceholderConfigurer`是Spring 3.1后提供的,希望用来取代`PropertyPlaceholderConfigurer`

```java
@Bean
public PropertyResourceConfigurer propertyResourceConfigurer() {
PropertyResourceConfigurer configurer = new PropertyPlaceholderConfigurer();
//configurer.setLocation(new ClassPathResource("my.properties")); // 加载指定的属性文件
return configurer;
}
```
运行上面用例就报错了:
```java
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...`,此时运行输出如下:
```java
Person{name='fsx-fsx', age=18}
null
```
会发现`environment.getProperty("diy.name")`为null,也就是说该属性值并不会存在应用的环境内了(但是xml的占位符已被成功解析)。从我的这个截图中也能看出来环境里已经没它了:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717214337802.png)
至于这**深处**到底是什么原因,有兴趣的可以轻点这里:[【小家Spring】详解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等对属性配置文件Properties的加载和使用](https://blog.csdn.net/f641385712/article/details/91444601)

==只new一个PropertyPlaceholderConfigurer报错原因分析:==
其实从源代码处一眼就能看出来原因:
```java
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)?==
一样的,从源码处一看便知,非常非常简单:
```java
// @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_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`的截图哦**):
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717215854966.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70)
what?虽然配置时候set了location去加载属性文件,但是上面代码中add进去的属性源`environmentProperties`和`localProperties`
```java
public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties";
public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties";
```
两个`PropertySource`都并没有出现?
关于此,我这里就不再解释了,哈哈。还是那句话,**留给小伙伴们自己思考**,若思考不明白的亦可扫码入群探讨哦~(当然参照上面文章也是可行的)

---
#### SpringBoot工程下使用
关于在`SpringBoot`中使用,简单到令人发指,毕竟`SpringBoot`的使命也是让你使用它能够简单到木有朋友。
so,在`SpringBoot`工程下使用`@ImportResource`和`@PropertySource`啥都不用配,它是能够天然的直接work的~

==原因分析如下:==
一切得益于`SpringBoot`强悍的自动化配置能力,它提供了这样一个配置类:
```java
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) // 最高优先级
public class PropertyPlaceholderAutoConfiguration {

// 注意此处使用的是PropertySourcesPlaceholderConfigurer
// 并且你可以在本容器内覆盖它的默认行为哟~~~~
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}

}
```
`PropertyPlaceholderAutoConfiguration`它被配置在了自动配置包下的`spring.factories`文件里:
```java
# 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()`之前~~~ 部分代码如下:
```java
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`的魅力所在吧~**

#### 关于小伙伴问题的解决
开头提出了问题,肯定得解决问题嘛~~~如下图
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717222424348.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70)
哈哈,虽然最终我并没有**直接的**帮助解决问题,但是此问题给了我写本文的动力,总体还是不错的~
#### 总结
本文通过一个小伙伴咨询的`小问题(真是小问题吗?)`引申比较详细的说了`Spring`在处理占位符这块的内容(其实本并没打算写这么多的,尴尬~)
写本文的目的开头也说了,我认为在`SpringBoot`还并非100%渗透的当下,肯定有人会遇到从传统`Spring`项目向`SpringBoot`过度的一个阶段,而本文的描述或许能给你的迁移提供一种新的思路(特别是时间紧、任务重的时候),希望小伙伴们能有所收获,peace~
#### 知识交流
> **`若文章格式混乱,可点击`**:[原文链接-原文链接-原文链接-原文链接-原文链接](https://blog.csdn.net/f641385712/article/details/96195401)

==The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被`作者本人许可的~`==

**若对技术内容感兴趣可以加入wx群交流:`Java高工、架构师3群`。
若群二维码失效,请加wx号:`fsx641385712`(或者扫描下方wx二维码)。并且备注:`"java入群"` 字样,会手动邀请入群**
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190703175020107.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70,pic_center =300x)

【小家Spring】老项目迁移问题:@ImportResource导入的xml配置里的Bean能够使用@PropertySource导入的属性值吗?的更多相关文章

  1. @ImportResource导入的xml配置里的Bean能够使用@PropertySource导入的属性值吗?

    每篇一句 大师都是偏执的,偏执才能产生力量,妥协是没有力量的.你对全世界妥协了你就是空气.所以若没有偏见,哪来的大师呢 相关阅读 [小家Spring]详解PropertyPlaceholderConf ...

  2. Spring XML配置里的Bean自动装配

    Spring自动装配 这段是我们之前编写的代码,代码中我们使用了P命名空间 并且使用手动装配的方式将car <bean id="address" class="cn ...

  3. 老项目迁移到springboot之后,上线服务器出现404的解决方法

    原因是老项目迁移到springboot之后,已经不再使用web.xml的配置了,但是WEB-INF目录下还有web.xml,所以才导致的404,所以只需要在源码处删除整个WEB-INF重新build即 ...

  4. 【小家Spring】Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的

    #### 每篇一句 > 具备了技术深度,遇到问题可以快速定位并从根本上解决.有了技术深度之后,学习其它技术可以更快,再深入其它技术也就不会害怕 #### 相关阅读 [[小家Spring]聊聊Sp ...

  5. 【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor

    #### 每篇一句 > 千古以来要饭的没有要早饭的,知道为什么吗? #### 相关阅读 [[小家Spring]聊聊Spring中的数据转换:Converter.ConversionService ...

  6. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和 ...

  7. 曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  8. NET Core2.1 WEB老项目迁移

    .NET Core2.1 版本新增功能不在赘述. NET Core2.1更新链接 如果开发需要安装Net Core2.1SDK,及Runtime. .NET Core2.1安装地址. 接下来是WEB ...

  9. spring老项目转springboot项目 笔记

    引入jar包 先不删除老的jar包 <parent> <groupId>org.springframework.boot</groupId> <artifac ...

随机推荐

  1. Color gradient in Delphi FireMonkey

    Introduction to color gradients in Delphi FireMonkey. Video This video covers the basics of color gr ...

  2. delphi中的copy函数和pos函数

    1.copy(‘csdn’,1,2) 返回的结果是 cs 注释: Copy有3个参数,第一个是要处理的字符串,第二个是要截取的开始位置,第三个是截取位数 当第三个参数大于字符长度,那么效果就是取开始位 ...

  3. IT安全军火库-转

    全球有260万信息安全专业人士,渗透测试工具是他们“安全军火库”中最常使用的装备,但直到最近,可用的渗透测试工具才丰富起来,但这也带来一个问题,挑选合适的渗透测试工具成了一件麻烦事,一个最简单的方法就 ...

  4. 如何设计firemonkey的style样式

    您好,在窗体上添加一个 TStyleBook(StyleBook1), 可以载入.编辑.另存这些样式.编辑 StyleBook1 后, 可以把它直接赋给窗体的 StyleBook 属性: proced ...

  5. ASP.NET Web API 直到我膝盖中了一箭【1】基础篇

    蓦然回首,那些年,我竟然一直很二. 小时候,读武侠小说的时候,看到那些猪脚,常常会产生一种代入感,幻想自己也会遭遇某种奇遇,遇到悬崖跳下去是不是有本“武林秘笈”在等着?长大以后也是一样,多少人梦着醒着 ...

  6. PHP/JS中获取当前页面的完整URL

    javascript实现: top.location.href 顶级窗口的地址this.location.href 当前窗口的地址 php实现 PHP实现 #测试网址: http://localhos ...

  7. java关键字-final

    final特点: 1:这个关键字是一个修饰符,可以修饰类,方法,变量. 2:被final修饰的类是一个最终类,不可以被继承. 3:被final修饰的方法是一个最终方法,不可以被覆盖. 4:被final ...

  8. 关于web系统整体优化提速总结

    关于web系统整体优化提速总结 一.背景 随着公司业务的拓展,随之而来就是各种系统横向和纵向的增加,PV.UV也都随之增加,原有的系统架构和模式慢慢遇上了瓶颈,需要逐步的对系统从整体上进行改造升级,通 ...

  9. gitlab安装笔记一_虚拟机中安装Centos7

    (为搭建gitlab环境的准备) 环境:vmware workstation 12 pro 系统: CentOS-7-x86_64-Everything-1804.iso  (CentOS-7-Min ...

  10. 09 audio和vedio标签

    <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8& ...