溜个例子:
@Configuration public class RootConfig { @Bean public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer(); configurer.setOrder(Ordered.HIGHEST_PRECEDENCE); configurer.setLocation(new ClassPathResource("beaninfo.properties")); return configurer; } @Scope("${bean.scope}") // 这里是能够使用占位符的 @Bean public Person person() { Person person = new Person(); return person; } } @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class}) public class TestSpringBean { @Autowired private ApplicationContext applicationContext; @Autowired private ConfigurableBeanFactory beanFactory; // 通过它,可以把生效的配置都拿到 @Autowired private PropertySourcesPlaceholderConfigurer configurer; @Test public void test1() { Environment environment = applicationContext.getEnvironment(); BeanExpressionResolver beanExpressionResolver = beanFactory.getBeanExpressionResolver(); PropertySources appliedPropertySources = configurer.getAppliedPropertySources(); System.out.println(environment.containsProperty("bean.scope")); //false 注意环境里是没有这个key的 System.out.println(beanExpressionResolver); System.out.println(appliedPropertySources); // 获取环境的和我们自己导入的 PropertySource<?> envProperties = appliedPropertySources.get(PropertySourcesPlaceholderConfigurer.ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME); PropertySource<?> localProperties = appliedPropertySources.get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME); System.out.println(envProperties.getSource() == environment); //true 可以看到这个envProperties的source和环境里的是同一个 System.out.println(localProperties.containsProperty("bean.scope"));//true 本地配置里是包含这个属性的 } }
从上面测试结果可知,PropertySourcesPlaceholderConfigurer是一种更加强大的加载配置文件处理占位符的工具。在Spring3.1之后建议使用它来加载配置文件进来,这样我们若运行时真有需要的话也是可以访问的。
思考题:竟然Properties属性最终都不会放进Environment环境抽象里,那为何@Value这个注解能够通过占位符访问到呢?
因为这篇博文:【小家Spring】Spring中@Value注解有多强大?从原理层面去剖析为何它有如此大的“能耐“ 详细讲解了@Value这个注解,所以这个思考题有兴趣的小伙伴可以结合本文思考一番~
PropertyOverrideConfigurer
它是抽象类PropertyResourceConfigurer的子类,它和PlaceholderConfigurerSupport平级。
PropertyOverrideConfigurer类似于PropertyPlaceholderConfigurer,与PropertyPlaceholderConfigurer 不同的是: PropertyOverrideConfigurer 利用属性文件的相关信息,覆盖XML 配置文件中定义。即PropertyOverrideConfigurer允许XML 配置文件中有默认的配置信息。
如果PropertyOverrideConfigurer 的属性文件有对应配置信息,则XML 文件中的配
置信息被覆盖:否则,直接使用XML 文件中的配置信息。
// @since 12.03.2003 public class PropertyOverrideConfigurer extends PropertyResourceConfigurer { // The default bean name separator. public static final String DEFAULT_BEAN_NAME_SEPARATOR = "."; // 下面提供了set方法可以修改 private String beanNameSeparator = DEFAULT_BEAN_NAME_SEPARATOR; private boolean ignoreInvalidKeys = false; // 默认是不忽略非法的key private final Set<String> beanNames = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); // 实现了父类的抽象方法:给每个bean进行覆盖处理 注意:这里不是处理占位符~~~~ @Override protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) throws BeansException { // 按照props有的这些属性值们进行覆盖~~~~ for (Enumeration<?> names = props.propertyNames(); names.hasMoreElements();) { String key = (String) names.nextElement(); try { // 1、拿到beanName 这样拿出来String beanName = key.substring(0, separatorIndex); // 2、根据beanName拿到bean定义信息BeanDefinition // 3、bdToUse.getPropertyValues().addPropertyValue(pv); 显然这样子存在的key就覆盖 否则保持原样 processKey(beanFactory, key, props.getProperty(key)); } } } }
需要注意的是Properties属性文件:
beanName.property=value //第一个.前面一定是beanName
请保证这个beanName一定存在。它会根据beanName找到这个bean,然后override这个bean的相关属性值的。
因为这个类使用得相对较少,但使用步骤基本同上,因此此处就不再叙述了
关于Spring下和SpringBoot下属性配置文件使用${}占位符的说明
比如有这个属性文件;
# 故意把它放在第一位 最顶部 app.full=${app.key} + ${app.myname} app.myname=fsx app.key=${user.home}
在Spring环境下测试使用:
@Slf4j @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class, JdbcConfig.class}) public class TestSpringBean { @Value("${app.full}") private String key; @Test public void test1() throws SQLException { System.out.println(key); //C:\Users\fangshixiang + fsx } }
可以看到在Spring环境下,Properties属性文件是能够使用占位符和直接读取环境中的值的。并且亲测发现不管是使用@PropertySource还是使用配置Bean PropertySourcesPlaceholderConfigurer的方式加载进来,都是能够正常work的。
至于它的原理,为何支持占位符的解析呢?其实上面都说了,此处不再说明了
关于SpringBoot下,就更不用说了。它的application.properties等配置文件里更是能够世界使用占位符和读取环境变量(系统属性值)的。
有个小细节一定要注意:它作为一个PropertySource存在的时候永远是原样。
真正解析占位符是取出来以后:
具体参考类:PropertySourcesPropertyResolver#getProperty方法~ 由它解析这些占位符。可以想到,最终都是委托给PropertyPlaceholderHelper去解析的~~~~
总结
从上面的分析可以看出,PropertyResourceConfigurer自身主要是抽象了对容器中所有bean定义的属性进行处理的一般逻辑,实现在接口BeanFactoryPostProcessor所定义的方法postProcessBeanFactory中。
这样容器启动时,bean容器的后置处理阶段,所有bean定义的属性都会被当前configure进行处理,处理时所使用的属性来源自当前configure基类PropertiesLoaderSupport所约定的那些属性,至于做什么样的处理,由当前configure的具体实现类,也就是PropertyResourceConfigurer的实现子类自己提供实现。