墨邪 發表於 2025-8-2 14:27:00

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

<h2 id="案例">案例</h2>
<p><strong>前置条件:</strong><br>
在 <code>resources</code> 目录下有 <code>hello/hello.properties</code> 文件,文件内容如下:</p>
<pre><code class="language-java">hello=nihao
</code></pre>
<p><strong>案例一:</strong><br>
在 <code>HelloController</code> 类中通过 <code>@PropertySource</code> 注解引用 properties 文件的内容,然后就可以通过 <code>@Value</code> 注解引用这个配置文件中的 hello 这个 key 了。</p>
<pre><code class="language-java">@PropertySource({"classpath:hello/hello.properties"})
@RestController
public class HelloController {
    @Value("${hello}")
    private String hello;

    @GetMapping("/hello")
    public String hello() {
      return hello;
    }
}
</code></pre>
<p>案例一执行的结果是返回 <code>nihao</code> 这个字符串。</p>
<p><strong>案例二:</strong><br>
在 <code>AnotherController</code> 类中通过 <code>@PropertySource</code> 注解引用 properties 文件的内容,在 <code>HelloController</code> 中仍然可以通过 <code>@Value</code> 注解引用这个配置文件中的 hello 这个 key 。</p>
<pre><code class="language-java">@RestController
public class HelloController {
    @Value("${hello}")
    private String hello;

    @GetMapping("/hello")
    public String hello() {
      return hello;
    }
}

@RestController
@PropertySource({"classpath:hello/hello.properties"})
public class AnotherController {
        // 省略代码
}
</code></pre>
<p>案例二返回的结果和案例一一致,这说明了只需要一个 Bean 通过 <code>@PropertySource</code> 注解引用了 <code>properties</code> 配置文件后,其它的 Bean 无需再使用<code>@PropertySource</code> 注解引用即可通过 <code>@Value</code> 注入其中的值。</p>
<p><strong>案例三:</strong></p>
<pre><code class="language-java">@Getter
@Setter
public class TestBean {
   private String attributeA;
   
   private String attributeB;
}

@RestController
public class HelloController {
    @Value("${hello}")
    private String hello;

        @Autowired
        private TestBean testBean;

    @GetMapping("/hello")
    public String hello() {
          System.out.println("AttributeA = " + testBean.getAttributeA());
          System.out.println("AttributeB = " + testBean.getAttributeB());
      return hello;
    }
}
</code></pre>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd"&gt;

    &lt;context:property-placeholder location="classpath:testBean/testBean.properties"/&gt;

    &lt;bean id="testBean" class="com.test.TestBean"&gt;
      &lt;property name="attributeA" value="${valueA}"/&gt;
      &lt;property name="attributeB" value="${valueB}"/&gt;
      
      &lt;!-- 省略其它配置 --&gt;
    &lt;/bean&gt;
&lt;/beans&gt;
</code></pre>
<p><code>testBean.properties</code> 配置文件中的值如下:</p>
<pre><code class="language-java">valueA=testA
valueB=testB
</code></pre>
<p>案例三执行的结果是 <code>testBean</code> 中的属性被正确替换为了 <code>testBean.properties</code> 配置文件中的值。<br>
<img src="https://raw.githubusercontent.com/javadaydayup/pictures/main/20250802134125.png" alt="image.png" loading="lazy"></p>
<p><strong>案例四:</strong><br>
在 <code>hello.properties</code> 文件中增加 <code>attributeA</code> 配置项,其它和<strong>案例三</strong>保持一致:</p>
<pre><code class="language-java">valueA=anotherTestA
</code></pre>
<p>案例四执行的结果是 <code>testBean</code> 中的 <code>attributeA</code> 属性被替换为了 <code>hello.properties</code> 中的值,<code>attributeB</code> 中的属性被替换为了 <code>testBean.properties</code> 中的值。<br>
<img src="https://raw.githubusercontent.com/javadaydayup/pictures/main/20250802134339.png" alt="image.png" loading="lazy"></p>
<h2 id="源码分析">源码分析</h2>
<h3 id="propertysource注解">@PropertySource注解</h3>
<p>在 Spring 中提供了 <code>BeanDefinitionRegistryPostProcessor</code> 接口,它提供了一个方法可以注册额外的 Bean 定义。代码如下:</p>
<pre><code class="language-java">public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
</code></pre>
<p>Spring 中提供了 <code>ConfigurationClassPostProcessor</code> 做为实现类,在它的 <code>postProcessBeanDefinitionRegistry()</code> 通过 <code>ConfigurationClassParser</code> 去将 <code>@Configuration</code> 等注解修饰的类解析成 Bean 定义并注册。</p>
<p>而在 <code>ConfigurationClassParser</code> 中的 <code>doProcessConfigurationClass()</code> 方法会解析所有 <code>@PropertySource</code> 注解的配置信息,然后根据配置的路径加载对应路径下的配置文件,然后注册到 <code>Environment</code> 中。代码如下:</p>
<pre><code class="language-java">protected final SourceClass doProcessConfigurationClass(
        ConfigurationClass configClass, SourceClass sourceClass,
        Predicate&lt;String&gt; filter)
        throws IOException {
        // Process any @PropertySource annotations
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
          sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class,
          PropertySources.class, true)) {
          if (this.propertySourceRegistry != null) {
                this.propertySourceRegistry.processPropertySource(propertySource);
          }
          else {
                logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                      "]. Reason: Environment must implement ConfigurableEnvironment");
          }
        }
}
</code></pre>
<p>在 <code>PropertySourceRegistry</code> 的 <code>processPropertySource()</code> 方法中获取到注解配置的文件的位置,然后又委托给了 <code>PropertySourceProcessor</code> 处理。代码如下:</p>
<pre><code class="language-java">void processPropertySource(AnnotationAttributes propertySource) throws IOException {
    String name = propertySource.getString("name");
    if (!StringUtils.hasLength(name)) {
      name = null;
    }
    String encoding = propertySource.getString("encoding");
    if (!StringUtils.hasLength(encoding)) {
      encoding = null;
    }
    // 获取到注解中配置的配置文件的位置
    String[] locations = propertySource.getStringArray("value");
    Assert.isTrue(locations.length &gt; 0, "At least one @PropertySource(value) location is required");
    boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

    Class&lt;? extends PropertySourceFactory&gt; factoryClass = propertySource.getClass("factory");
    Class&lt;? extends PropertySourceFactory&gt; factoryClassToUse =
            (factoryClass != PropertySourceFactory.class ? factoryClass : null);
    PropertySourceDescriptor descriptor = new PropertySourceDescriptor(Arrays.asList(locations),
            ignoreResourceNotFound, name, factoryClassToUse, encoding);
    //
    this.propertySourceProcessor.processPropertySource(descriptor);
    this.descriptors.add(descriptor);
}
</code></pre>
<p>在 <code>PropertySourceProcessor</code> 的 <code>processPropertySource()</code> 方法中遍历每个配置文件位置加载配置文件,然后添加到 <code>Environment</code> 的 <code>propertySources</code> 中。代码如下:</p>
<pre><code class="language-java">public void processPropertySource(PropertySourceDescriptor descriptor) throws IOException {
    String name = descriptor.name();
    String encoding = descriptor.encoding();
    List&lt;String&gt; locations = descriptor.locations();
    boolean ignoreResourceNotFound = descriptor.ignoreResourceNotFound();
    PropertySourceFactory factory = (descriptor.propertySourceFactory() != null ?
            instantiateClass(descriptor.propertySourceFactory()) : defaultPropertySourceFactory);

    for (String location : locations) { // 遍历每个配置文件位置加载配置文件
      try {
            String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
            for (Resource resource : this.resourcePatternResolver.getResources(resolvedLocation)) {
                addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
            }
      } catch (RuntimeException | IOException ex) {
            // 省略点
      }
    }
}

private void addPropertySource(PropertySource&lt;?&gt; propertySource) {
    String name = propertySource.getName();
    MutablePropertySources propertySources = this.environment.getPropertySources();

    if (this.propertySourceNames.contains(name)) {
      // 省略代码
    }

    if (this.propertySourceNames.isEmpty()) {
      propertySources.addLast(propertySource);
    }
    else {
      String lastAdded = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
      // 添加到 propertySources 中
      propertySources.addBefore(lastAdded, propertySource);
    }
    this.propertySourceNames.add(name);
}
</code></pre>
<p>在 <code>AbstractApplicationContext</code> 中的 <code>finishBeanFactoryInitialization()</code> 方法中,会先判断是否有注册 <code>EmbeddedValueResolver</code>,如果没有再注册,如果有的话就不注册了,<strong>这里和 <code>PropertySourcesPlaceholderConfigurer</code> 联动起来了</strong>。代码如下:</p>
<pre><code class="language-java">protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    // Register a default embedded value resolver if no BeanFactoryPostProcessor
    // (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:
    // at this point, primarily for resolution in annotation attribute values.
    if (!beanFactory.hasEmbeddedValueResolver()) {
      beanFactory.addEmbeddedValueResolver(strVal -&gt; getEnvironment().resolvePlaceholders(strVal));
    }
   
    // Instantiate all remaining (non-lazy-init) singletons.
    beanFactory.preInstantiateSingletons();
}
</code></pre>
<h3 id="propertysourcesplaceholderconfigurer">PropertySourcesPlaceholderConfigurer</h3>
<p>而 <code>PropertySourcesPlaceholderConfigurer</code> 实现了 <code>BeanFactoryPostProcessor</code> 接口,它的 <code>postProcessBeanFactory()</code> 方法中,首先以 <code>environment</code> 对象构建一个 <code>PropertySource</code> 对象,添加到 <code>propertySources</code> 中;然后根据它自己配置的 <code>location</code> (<strong>即前面在xml中配置的</strong>)构建一个 <code>PropertySource</code> 对象,添加到 <code>propertySources</code> 中,<strong>默认添加在尾部,这个对于解释场景四很重要</strong>。最后基于 <code>propertySources</code> 构建了一个 <code>ConfigurablePropertyResolver</code> 对象去调用 <code>processProperties()</code> 方法。</p>
<pre><code class="language-java">public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    if (this.propertySources == null) {
      this.propertySources = new MutablePropertySources();
      if (this.environment != null) {
            PropertyResolver propertyResolver = this.environment;
            // If the ignoreUnresolvablePlaceholders flag is set to true, we have to create a
            // local PropertyResolver to enforce that setting, since the Environment is most
            // likely not configured with ignoreUnresolvablePlaceholders set to true.
            // See https://github.com/spring-projects/spring-framework/issues/27947
            if (this.ignoreUnresolvablePlaceholders &amp;&amp;
                  (this.environment instanceof ConfigurableEnvironment configurableEnvironment)) {
                PropertySourcesPropertyResolver resolver =
                        new PropertySourcesPropertyResolver(configurableEnvironment.getPropertySources());
                resolver.setIgnoreUnresolvableNestedPlaceholders(true);
                propertyResolver = resolver;
            }
            // 将environment构建为一个PropertySource对象
            PropertyResolver propertyResolverToUse = propertyResolver;
            this.propertySources.addLast(
                new PropertySource&lt;&gt;(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
                  @Override
                  @Nullable
                  public String getProperty(String key) {
                        return propertyResolverToUse.getProperty(key);
                  }
                }
            );
      }
      try {
            PropertySource&lt;?&gt; localPropertySource =
                  new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
            if (this.localOverride) {
                this.propertySources.addFirst(localPropertySource);
            }
            else { // 默认情况下是将配置加入到最后
                this.propertySources.addLast(localPropertySource);
            }
      }
      catch (IOException ex) {
            throw new BeanInitializationException("Could not load properties", ex);
      }
    }

    processProperties(beanFactory, createPropertyResolver(this.propertySources));
    this.appliedPropertySources = this.propertySources;
}
</code></pre>
<p>在 <code>processProperties()</code> 方法中通过 <code>ConfigurablePropertyResolver</code> 对象又构造了一个 <code>StringValueResolver</code> 对象,然后调用了 <code>doProcessProperties()</code> 方法。代码如下:</p>
<pre><code class="language-java">protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
    final ConfigurablePropertyResolver propertyResolver) throws BeansException {
        propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
        propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
        propertyResolver.setValueSeparator(this.valueSeparator);
        propertyResolver.setEscapeCharacter(this.escapeCharacter);

        // 构造了一个StringValueResolver对象
        StringValueResolver valueResolver = strVal -&gt; {
                String resolved = (this.ignoreUnresolvablePlaceholders ?
                                propertyResolver.resolvePlaceholders(strVal) :
                                propertyResolver.resolveRequiredPlaceholders(strVal));
                if (this.trimValues) {
                        resolved = resolved.trim();
                }
                return (resolved.equals(this.nullValue) ? null : resolved);
        };

        doProcessProperties(beanFactoryToProcess, valueResolver);
}
</code></pre>
<p>在 <code>doProcessProperties()</code> 方法中又通过 <code>StringValueResolver</code> 对象构造了一个 <code>BeanDefinitionVisitor</code> 对象,然后调用它的 <code>visitBeanDefinition()</code> 实现了对 Bean 定义中属性引用的解析。然后调用 <code>BeanFactory</code> 的 <code>addEmbeddedValueResolver()</code> 方法把 <code>StringValueResolver</code> 对象设置给了 <code>BeanFactory</code>,<strong>这里就和前面的<code>AbstractApplicationContext</code> 中的 <code>finishBeanFactoryInitialization()</code> 方法呼应起来了,这里设置了值,那边就不设置了,这里没有设置,那边就会设置</strong>。</p>
<pre><code class="language-java">protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
    StringValueResolver valueResolver) {
    // 构造BeanDefinitionVisitor对象
    BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

    String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
    for (String curName : beanNames) {
      // Check that we're not parsing our own bean definition,
      // to avoid failing on unresolvable placeholders in properties file locations.
      if (!(curName.equals(this.beanName) &amp;&amp; beanFactoryToProcess.equals(this.beanFactory))) {
            BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
            try {
                // 对Bean定义中引用的配置进行解析
                visitor.visitBeanDefinition(bd);
            }
            catch (Exception ex) {
                throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
            }
      }
    }

    // Resolve placeholders in alias target names and aliases as well.
    beanFactoryToProcess.resolveAliases(valueResolver);

    // Resolve placeholders in embedded values such as annotation attributes.
    // 添加到BeanFactory中
    beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
</code></pre>
<p>在之前的文章Spring 中 @Value 注解实现原理中介绍了在&nbsp;<code>DefaultListableBeanFactory</code>&nbsp;的&nbsp;<code>resolveEmbeddedValue()</code>&nbsp;方法中实现了对 <code>@Value</code> 注解的解析,这里实际上就是调用的上面设置的 <code>StringValueResolver</code> 对象的 <code>resolveStringValue()</code> 方法来实现的。</p>
<pre><code class="language-java">public String resolveEmbeddedValue(@Nullable String value) {
    if (value == null) {
      return null;
    }
    String result = value;
    for (StringValueResolver resolver : this.embeddedValueResolvers) {
      result = resolver.resolveStringValue(result);
      if (result == null) {
            return null;
      }
    }
    return result;
}
</code></pre>
<h2 id="案例解答">案例解答</h2>
<p><strong>对于案例二:</strong> 在解析 Bean 定义的时候会把所有 <code>@PropertySource</code> 注解定义配置文件解析到 <code>Environment</code> 集中保存起来,然后在解析 <code>@Value</code> 注解值的时候统一从这个集中的地方去查找。因此只需要有一个类通过 <code>@PropertySource</code> 注解引用这个配置即可。</p>
<p><strong>对于案例三:</strong> 实际上是依赖实现了 <code>BeanFactoryPostProcessor</code> 接口,它的 <code>postProcessBeanFactory()</code> 方法中实现了在 Bean 真正创建之前,对 Bean 定义中引用属性的解析。</p>
<p><strong>对于案例四:</strong> 在默认的情况下解析依赖的配置文件是所有 <code>@PropertySource</code> 引用的配置文件加上 <code>PropertySourcesPlaceholderConfigurer</code> 的 <code>location</code> 属性引用的配置文件,且 <code>@PropertySource</code> 引用的配置文件在它的 <code>location</code> 属性引用的配置文件前面,查找的时候是按照顺序查找的。<code>@PropertySource</code> 引用的配置文件中定义了相同的 key,则直接会获取值返回,不会再继续往后查找了,所以就出现了案例四中 <code>hello.properties</code> 配置文件中的相同配置项覆盖了 <code>testBean.properties</code> 配置文件中的配置项。t</p>
<p>同时 Spring 提供了一个配置项 <code>local-override</code>,当设置为 <code>true</code> 时,才会使用<code>testBean.properties</code> 配置覆盖<code>hello.properties</code> 配置。覆盖的原理就是把配置加到最前面。代码如下:</p>
<pre><code class="language-xml">&lt;context:property-placeholder location="classpath:testBean.properties" local-override="true" /&gt;
</code></pre>
<pre><code class="language-java">try {
        PropertySource&lt;?&gt; localPropertySource =
                        new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
        if (this.localOverride) { // 设置为true的时候将配置加入到最前面
                this.propertySources.addFirst(localPropertySource);
        }
        else { // 默认情况下是将配置加入到最后
                this.propertySources.addLast(localPropertySource);
        }
}
catch (IOException ex) {
        throw new BeanInitializationException("Could not load properties", ex);
}
</code></pre>


</div>
<div id="MySignature" role="contentinfo">
    欢迎大家关注我的公众号【javadaydayup】<br><br>
来源:https://www.cnblogs.com/javadaydayup/p/19018780
頁: [1]
查看完整版本: 搞懂这两个组件,Spring 配置问题少一半!