Spring 源码解析之@PropertySource注解详解

Spring 源码解析之 @PropertySource 注解详解

 

 

1、简介

使用@PropertySource 注解用于指定资源文件读取的位置,它不仅能读取properties文件,也能读取xml文件,并且通过yaml解析器,配合自定义PropertySourceFactory实现解析yaml文件。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {

    //资源名称,为空则根据资源的描述符生成
    String name() default "";

    /**
     *资源路径
     *classpath:application.properties
     *file:/
     */
    String[] value();

    //是否忽略资源不存在的情况,如果不忽略,当资源不存在时报错
    boolean ignoreResourceNotFound() default false;

    //指定资源文件的编码格式
    String encoding() default "";

    //资源工厂
    Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

}

 

2、源码解析
        在bean实例化之前有一个ConfigurationClassPostProcessor类,会操作BeanDefinition并且加入到BeanDefinitionRegistry中,它的优先级在所有的BeanDefinitionRegistryPostProcessor里面是最低的。从ConfigurationClassPostProcessor 中的 processConfigBeanDefinitions()#parse()直接跳到ConfigurationClassParser类的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicatefilter)方法。

protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {
    //#############省略其他代码################
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), PropertySources.class,
        org.springframework.context.annotation.PropertySource.class)) {
        if (this.propertySourceRegistry != null) {
            this.propertySourceRegistry.processPropertySource(propertySource);
        }
    }
 //#############省略其他代码################
}

可以看到,在ConfigurationClassParser类的doProcessConfigurationClass()方法中,遍历获取到的@PropertySources注解和@PropertySource注解的属性,并且调用propertySourceRegistry对象的processPropertySource()方法解析注解属性的值。

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 > 0, "At least one @PropertySource(value) location is required");
    boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
    Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
    Class<? extends PropertySourceFactory> factorClassToUse = (factoryClass != PropertySourceFactory.class ? factoryClass : null);
    PropertySourceDescriptor descriptor = new PropertySourceDescriptor(Arrays.asList(locations), ignoreResourceNotFound, name, factorClassToUse, encoding);
    this.propertySourceProcessor.processPropertySource(descriptor);
    this.descriptors.add(descriptor);
}

可以看到,在PropertySourceRegistry类的processPropertySource()方法中,解析@PropertySource注解中的属性后,将解析出的属性值封装到PropertySourceDescriptor对象中,调用propertySourceProcessor对象的processPropertySource()方法,并传入PropertySourceDescriptor对象进行进一步处理。

public void processPropertySource(PropertySourceDescriptor descriptor) throws IOException {
    String name = descriptor.name();
    String encoding = descriptor.encoding();
    List<String> locations = descriptor.locations();
    Assert.isTrue(locations.size() > 0, "At least one @PropertySource(value) location is required");
    boolean ignoreResourceNotFound = descriptor.ignoreResourceNotFound();
    PropertySourceFactory factory = (descriptor.propertySourceFactory() != null ? instantiateClass(descriptor.propertySourceFactory()) :
DEFAULT_PROPERTY_SOURCE_FACTORY);
for (String location : locations) { try { String resolvedLocation = this.environment.resolveRequiredPlaceholders(location); Resource resource = this.resourceLoader.getResource(resolvedLocation); addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding))); } catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) { //#########省略其他代码################ } } }

 可以看到,在processPropertySource()方法中,会通过@PropertySource注解的属性值解析出配置文件的内容,并且通过factory对象的createPropertySource()方法来创建PropertySource对象。

回到PropertySourceProcessor类的processPropertySource(PropertySourceDescriptor descriptor)方法。

在PropertySourceProcessor类的processPropertySource()方法中,创建完PropertySource对象后,会调用addPropertySource()方法将获取到的属性值添加到Spring的环境变量中。

 

解析PropertySourceProcessor类的addPropertySource(PropertySource<?> propertySource)方法。

private void addPropertySource(org.springframework.core.env.PropertySource<?> propertySource) {
    String name = propertySource.getName();
    MutablePropertySources propertySources = this.environment.getPropertySources();
    if (this.propertySourceNames.contains(name)) {
        org.springframework.core.env.PropertySource<?> existing = propertySources.get(name);
        if (existing != null) {
            PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?((ResourcePropertySource) propertySource).withResourceName() :
propertySource);
if (existing instanceof CompositePropertySource) { ((CompositePropertySource) existing).addFirstPropertySource(newSource); } else { if (existing instanceof ResourcePropertySource) { existing = ((ResourcePropertySource) existing).withResourceName(); } CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource(newSource); composite.addPropertySource(existing); propertySources.replace(name, composite); } return; } } if (this.propertySourceNames.isEmpty()) { propertySources.addLast(propertySource); } else { String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1); propertySources.addBefore(firstProcessed, propertySource); } this.propertySourceNames.add(name); }

          可以看到,在PropertySourceProcessor类的addPropertySource()方法中,会将解析出的配置文件的内容添加到Spring的环境变量中。具体就是在PropertySourceProcessor类的addPropertySource()方法中,获取到ConfigurableEnvironment中的MutablePropertySources对象,用来存储解析出的配置文件中的配置项内容。如果有相同的配置项内容,将existing对象强转为CompositePropertySource类型,把新旧相同的配置项进行合并,再放到MutablePropertySources对象中。

后续就可以通过Spring的环境变量,来获取到配置文件中的配置项内容。

 

至此,@PropertySource注解在Spring源码中的执行流程分析完毕。

 

posted @ 2023-07-09 23:37  邓维-java  阅读(1450)  评论(0)    收藏  举报