找回密码
 立即注册
首页 业界区 业界 搞懂这两个组件,Spring 配置问题少一半! ...

搞懂这两个组件,Spring 配置问题少一半!

纪睐讦 8 小时前
案例

前置条件:
在 resources 目录下有 hello/hello.properties 文件,文件内容如下:
  1. hello=nihao
复制代码
案例一:
在 HelloController 类中通过 @PropertySource 注解引用 properties 文件的内容,然后就可以通过 @Value 注解引用这个配置文件中的 hello 这个 key 了。
  1. @PropertySource({"classpath:hello/hello.properties"})
  2. @RestController
  3. public class HelloController {  
  4.     @Value("${hello}")  
  5.     private String hello;  
  6.   
  7.     @GetMapping("/hello")  
  8.     public String hello() {  
  9.         return hello;  
  10.     }  
  11. }
复制代码
案例一执行的结果是返回 nihao 这个字符串。
案例二:
在 AnotherController 类中通过 @PropertySource 注解引用 properties 文件的内容,在 HelloController 中仍然可以通过 @Value 注解引用这个配置文件中的 hello 这个 key 。
  1. @RestController
  2. public class HelloController {  
  3.     @Value("${hello}")  
  4.     private String hello;  
  5.   
  6.     @GetMapping("/hello")  
  7.     public String hello() {  
  8.         return hello;  
  9.     }  
  10. }
  11. @RestController
  12. @PropertySource({"classpath:hello/hello.properties"})
  13. public class AnotherController {
  14.         // 省略代码
  15. }
复制代码
案例二返回的结果和案例一一致,这说明了只需要一个 Bean 通过 @PropertySource 注解引用了 properties 配置文件后,其它的 Bean 无需再使用@PropertySource 注解引用即可通过 @Value 注入其中的值。
案例三:
  1. @Getter  
  2. @Setter  
  3. public class TestBean {  
  4.    private String attributeA;  
  5.      
  6.    private String attributeB;  
  7. }
  8. @RestController
  9. public class HelloController {  
  10.     @Value("${hello}")  
  11.     private String hello;
  12.         @Autowired
  13.         private TestBean testBean;
  14.   
  15.     @GetMapping("/hello")  
  16.     public String hello() {  
  17.             System.out.println("AttributeA = " + testBean.getAttributeA());
  18.             System.out.println("AttributeB = " + testBean.getAttributeB());
  19.         return hello;  
  20.     }  
  21. }
复制代码
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.        xmlns:context="http://www.springframework.org/schema/context"
  5.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  6.                            http://www.springframework.org/schema/beans/spring-beans.xsd
  7.                            http://www.springframework.org/schema/context
  8.                            http://www.springframework.org/schema/context/spring-context.xsd">
  9.     <context:property-placeholder location="classpath:testBean/testBean.properties"/>
  10.     <bean id="testBean" >
  11.         <property name="attributeA" value="${valueA}"/>
  12.         <property name="attributeB" value="${valueB}"/>
  13.         
  14.         
  15.     </bean>
  16. </beans>
复制代码
testBean.properties 配置文件中的值如下:
  1. valueA=testA
  2. valueB=testB
复制代码
案例三执行的结果是 testBean 中的属性被正确替换为了 testBean.properties 配置文件中的值。
1.png

案例四:
在 hello.properties 文件中增加 attributeA 配置项,其它和案例三保持一致:
  1. valueA=anotherTestA
复制代码
案例四执行的结果是 testBean 中的 attributeA 属性被替换为了 hello.properties 中的值,attributeB 中的属性被替换为了 testBean.properties 中的值。
2.png

源码分析

@PropertySource注解

在 Spring 中提供了 BeanDefinitionRegistryPostProcessor 接口,它提供了一个方法可以注册额外的 Bean 定义。代码如下:
  1. public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {  
  2.     void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
  3. }
复制代码
Spring 中提供了 ConfigurationClassPostProcessor 做为实现类,在它的 postProcessBeanDefinitionRegistry() 通过 ConfigurationClassParser 去将 @Configuration 等注解修饰的类解析成 Bean 定义并注册。
而在 ConfigurationClassParser 中的 doProcessConfigurationClass() 方法会解析所有 @PropertySource 注解的配置信息,然后根据配置的路径加载对应路径下的配置文件,然后注册到 Environment 中。代码如下:
  1. protected final SourceClass doProcessConfigurationClass(
  2.         ConfigurationClass configClass, SourceClass sourceClass,
  3.         Predicate<String> filter)
  4.         throws IOException {
  5.         // Process any @PropertySource annotations
  6.         for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
  7.             sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class,
  8.             PropertySources.class, true)) {
  9.             if (this.propertySourceRegistry != null) {
  10.                 this.propertySourceRegistry.processPropertySource(propertySource);
  11.             }
  12.             else {
  13.                 logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
  14.                         "]. Reason: Environment must implement ConfigurableEnvironment");
  15.             }
  16.         }
  17. }
复制代码
在 PropertySourceRegistry 的 processPropertySource() 方法中获取到注解配置的文件的位置,然后又委托给了 PropertySourceProcessor 处理。代码如下:
  1. void processPropertySource(AnnotationAttributes propertySource) throws IOException {
  2.     String name = propertySource.getString("name");
  3.     if (!StringUtils.hasLength(name)) {
  4.         name = null;
  5.     }
  6.     String encoding = propertySource.getString("encoding");
  7.     if (!StringUtils.hasLength(encoding)) {
  8.         encoding = null;
  9.     }
  10.     // 获取到注解中配置的配置文件的位置
  11.     String[] locations = propertySource.getStringArray("value");
  12.     Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
  13.     boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
  14.     Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
  15.     Class<? extends PropertySourceFactory> factoryClassToUse =
  16.             (factoryClass != PropertySourceFactory.class ? factoryClass : null);
  17.     PropertySourceDescriptor descriptor = new PropertySourceDescriptor(Arrays.asList(locations),
  18.             ignoreResourceNotFound, name, factoryClassToUse, encoding);
  19.     //
  20.     this.propertySourceProcessor.processPropertySource(descriptor);
  21.     this.descriptors.add(descriptor);
  22. }
复制代码
在 processProperties() 方法中通过 ConfigurablePropertyResolver 对象又构造了一个 StringValueResolver 对象,然后调用了 doProcessProperties() 方法。代码如下:
  1. public void processPropertySource(PropertySourceDescriptor descriptor) throws IOException {
  2.     String name = descriptor.name();
  3.     String encoding = descriptor.encoding();
  4.     List<String> locations = descriptor.locations();
  5.     boolean ignoreResourceNotFound = descriptor.ignoreResourceNotFound();
  6.     PropertySourceFactory factory = (descriptor.propertySourceFactory() != null ?
  7.             instantiateClass(descriptor.propertySourceFactory()) : defaultPropertySourceFactory);
  8.     for (String location : locations) { // 遍历每个配置文件位置加载配置文件
  9.         try {
  10.             String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
  11.             for (Resource resource : this.resourcePatternResolver.getResources(resolvedLocation)) {
  12.                 addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
  13.             }
  14.         } catch (RuntimeException | IOException ex) {
  15.             // 省略点
  16.         }
  17.     }
  18. }
  19. private void addPropertySource(PropertySource<?> propertySource) {
  20.     String name = propertySource.getName();
  21.     MutablePropertySources propertySources = this.environment.getPropertySources();
  22.     if (this.propertySourceNames.contains(name)) {
  23.         // 省略代码
  24.     }
  25.     if (this.propertySourceNames.isEmpty()) {
  26.         propertySources.addLast(propertySource);
  27.     }
  28.     else {
  29.         String lastAdded = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
  30.         // 添加到 propertySources 中
  31.         propertySources.addBefore(lastAdded, propertySource);
  32.     }
  33.     this.propertySourceNames.add(name);
  34. }
复制代码
在 doProcessProperties() 方法中又通过 StringValueResolver 对象构造了一个 BeanDefinitionVisitor 对象,然后调用它的 visitBeanDefinition() 实现了对 Bean 定义中属性引用的解析。然后调用 BeanFactory 的 addEmbeddedValueResolver() 方法把 StringValueResolver 对象设置给了 BeanFactory,这里就和前面的AbstractApplicationContext 中的 finishBeanFactoryInitialization() 方法呼应起来了,这里设置了值,那边就不设置了,这里没有设置,那边就会设置
  1. protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
  2.     // Register a default embedded value resolver if no BeanFactoryPostProcessor
  3.     // (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:
  4.     // at this point, primarily for resolution in annotation attribute values.
  5.     if (!beanFactory.hasEmbeddedValueResolver()) {
  6.         beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
  7.     }
  8.    
  9.     // Instantiate all remaining (non-lazy-init) singletons.
  10.     beanFactory.preInstantiateSingletons();
  11. }
复制代码
在之前的文章Spring 中 @Value 注解实现原理中介绍了在 DefaultListableBeanFactory 的 resolveEmbeddedValue() 方法中实现了对 @Value 注解的解析,这里实际上就是调用的上面设置的 StringValueResolver 对象的 resolveStringValue() 方法来实现的。
  1. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  2.     if (this.propertySources == null) {
  3.         this.propertySources = new MutablePropertySources();
  4.         if (this.environment != null) {
  5.             PropertyResolver propertyResolver = this.environment;
  6.             // If the ignoreUnresolvablePlaceholders flag is set to true, we have to create a
  7.             // local PropertyResolver to enforce that setting, since the Environment is most
  8.             // likely not configured with ignoreUnresolvablePlaceholders set to true.
  9.             // See https://github.com/spring-projects/spring-framework/issues/27947
  10.             if (this.ignoreUnresolvablePlaceholders &&
  11.                     (this.environment instanceof ConfigurableEnvironment configurableEnvironment)) {
  12.                 PropertySourcesPropertyResolver resolver =
  13.                         new PropertySourcesPropertyResolver(configurableEnvironment.getPropertySources());
  14.                 resolver.setIgnoreUnresolvableNestedPlaceholders(true);
  15.                 propertyResolver = resolver;
  16.             }
  17.             // 将environment构建为一个PropertySource对象
  18.             PropertyResolver propertyResolverToUse = propertyResolver;
  19.             this.propertySources.addLast(
  20.                 new PropertySource<>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
  21.                     @Override
  22.                     @Nullable
  23.                     public String getProperty(String key) {
  24.                         return propertyResolverToUse.getProperty(key);
  25.                     }
  26.                 }
  27.             );
  28.         }
  29.         try {
  30.             PropertySource<?> localPropertySource =
  31.                     new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
  32.             if (this.localOverride) {
  33.                 this.propertySources.addFirst(localPropertySource);
  34.             }
  35.             else { // 默认情况下是将配置加入到最后
  36.                 this.propertySources.addLast(localPropertySource);
  37.             }
  38.         }
  39.         catch (IOException ex) {
  40.             throw new BeanInitializationException("Could not load properties", ex);
  41.         }
  42.     }
  43.     processProperties(beanFactory, createPropertyResolver(this.propertySources));
  44.     this.appliedPropertySources = this.propertySources;
  45. }
复制代码
案例解答

对于案例二: 在解析 Bean 定义的时候会把所有 @PropertySource 注解定义配置文件解析到 Environment 集中保存起来,然后在解析 @Value 注解值的时候统一从这个集中的地方去查找。因此只需要有一个类通过 @PropertySource 注解引用这个配置即可。
对于案例三: 实际上是依赖实现了 BeanFactoryPostProcessor 接口,它的 postProcessBeanFactory() 方法中实现了在 Bean 真正创建之前,对 Bean 定义中引用属性的解析。
对于案例四: 在默认的情况下解析依赖的配置文件是所有 @PropertySource 引用的配置文件加上 PropertySourcesPlaceholderConfigurer 的 location 属性引用的配置文件,且 @PropertySource 引用的配置文件在它的 location 属性引用的配置文件前面,查找的时候是按照顺序查找的。@PropertySource 引用的配置文件中定义了相同的 key,则直接会获取值返回,不会再继续往后查找了,所以就出现了案例四中 hello.properties 配置文件中的相同配置项覆盖了 testBean.properties 配置文件中的配置项。t
同时 Spring 提供了一个配置项 local-override,当设置为 true 时,才会使用testBean.properties 配置覆盖hello.properties 配置。覆盖的原理就是把配置加到最前面。代码如下:
  1. [/code][code]protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
  2.     StringValueResolver valueResolver) {
  3.     // 构造BeanDefinitionVisitor对象
  4.     BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
  5.     String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
  6.     for (String curName : beanNames) {
  7.         // Check that we're not parsing our own bean definition,
  8.         // to avoid failing on unresolvable placeholders in properties file locations.
  9.         if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
  10.             BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
  11.             try {
  12.                 // 对Bean定义中引用的配置进行解析
  13.                 visitor.visitBeanDefinition(bd);
  14.             }
  15.             catch (Exception ex) {
  16.                 throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
  17.             }
  18.         }
  19.     }
  20.     // Resolve placeholders in alias target names and aliases as well.
  21.     beanFactoryToProcess.resolveAliases(valueResolver);
  22.     // Resolve placeholders in embedded values such as annotation attributes.
  23.     // 添加到BeanFactory中
  24.     beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
  25. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册