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

#### 相关阅读
[【小家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. ArcGIS for Desktop入门教程_第八章_Desktop学习资源 - ArcGIS知乎-新一代ArcGIS问答社区

    原文:ArcGIS for Desktop入门教程_第八章_Desktop学习资源 - ArcGIS知乎-新一代ArcGIS问答社区 1 学习资源 用户在学习和应用过程中,可以参考的资源如下: 1. ...

  2. So Good They Can't Ignore You

    总体而言,这本书的作者的观点就是,你只有做好了,才会有兴趣,而不是一开始就找可能并不存在的所谓兴趣——好多人就败在不停地找这么一个根本就不存在的兴趣.这个观点简直就是拯救那些乔布斯的粉丝:Follow ...

  3. 详解 Qt 线程间共享数据(使用signal/slot传递数据,线程间传递信号会立刻返回,但也可通过connect改变)

    使用共享内存.即使用一个两个线程都能够共享的变量(如全局变量),这样两个线程都能够访问和修改该变量,从而达到共享数据的目的. Qt 线程间共享数据是本文介绍的内容,多的不说,先来啃内容.Qt线程间共享 ...

  4. FMX 动态创建 和 销毁(释放free) 对象

    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 32 33 34 35 36 3 ...

  5. C++函数不写bool返回值,居然编译运行全部通过,但判断结果就不对了

    bool MyStart::IsCoorectParam(QString strParam) { if (strParam=="-aa" || strParam=="-b ...

  6. Wp8 Popup不随输入法偏移问题解决方案

    在wp中我们经常要实现,浮窗的效果这时我们就会考虑到Popup,但是在用Popup开发我们会遇到一个非常尴尬的问题,由于Popup不在主界面的可视化树内,在Popup显示的位置在输入法面板出现的范围时 ...

  7. IT安全军火库-转

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

  8. python连接数据库(2)——mongodb

    mongodb是近一段时间以来比较流行的非关系数据库之一,由于python和它都对json类型有着很好的支持,因此配合起来可谓天衣无缝. 首先要下载python对mongodb支持的包pymongo ...

  9. Hadoop 三剑客之 —— 集群资源管理器 YARN

    一.hadoop yarn 简介 二.YARN架构         1. ResourceManager         2. NodeManager         3. ApplicationMa ...

  10. 网站压力测试工具 Webbench简单介绍

    Webbech能测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况.Webbench的标准测试可以向我们展示服务器的两项内容:每秒钟相应请求数和每秒钟传输数据量.Webbench不但 ...