一、学习指引
@PropertySource注解是用来干啥的呢?
在日常开发中,你有没有遇到过这样一种场景:项目中需要编写很多配置文件,将一些系统信息配置化,此时,往往需要编写专门的工具类或者方法来读取并解析这些配置文件,将配置文件中的配置项内容加载到系统内存中。后续在使用这些配置项时,可以直接通过工具类或者方法获取加载到内存中的配置项。
没错,@PropertySource注解就是Spring中提供的一个可以加载配置文件的注解,并且可以将配置文件中的内容存放到Spring的环境变量中。
二、注解说明
简单介绍下@PropertySource注解吧!
@PropertySource注解是Spring中提供的一个通过指定配置文件位置来加载配置文件的注解,并且可以将配置文件中的内容存放到Spring的环境变量中。除了可以通过Spring的环境变量读取配置项之外,还可以通过@Value注解获取配置项的值。
另外,Spring中还提供了一个@PropertySources注解,在@PropertySources注解注解中,可以引入多个@PropertySource注解。
1、注解源码
Spring中提供了@PropertySource和@PropertySources两个注解来加载配置文件。
(1)@PropertySource注解
@PropertySource注解只能标注到类上,能够通过指定配置文件的位置来加载配置文件,@PropertySource注解除了可以加载properties配置文件外,也可以加载xml配置文件和yml配置文件。如果加载yml配置文件时,可以自定义PropertySourceFactory实现yml配置文件的解析操作。
@PropertySource注解的源码详见:org.springframework.context.annotation.PropertySource。
从源码可以看出,@PropertySource注解是从Spring3.1版本开始提供的注解,注解中各个属性的含义如下所示。
- name:表示加载的资源的名称,如果为空,则会根据加载的配置文件自动生成一个名称。
- value:表示加载的资源的路径,这个路径可以是类路径,也可以是文件路径。
- ignoreResourceNotFound:表示当配置文件未找到时,是否忽略文件未找到的错误。默认值为false,也就是说当未找到配置文件时,Spring启动就会报错。
- encoding:表示解析配置文件使用的字符集编码。
- factory:表示读取对应配置文件的工厂类,默认的工厂类是PropertySourceFactory。
(2)@PropertySources注解
除了@PropertySource注解,Spring中还提供了一个@PropertySources注解。@PropertySources注解中的源码详见:org.springframework.context.annotation.PropertySources。
从源码可以看出,@PropertySources是从Spring4.0版本开始提供的注解,在@PropertySources注解中,只提供了一个PropertySource数组类型的value属性。所以,@PropertySources注解可以引入多个@PropertySource注解。
2、注解使用场景
在基于Spring的注解开发项目的过程中,由于不再使用Spring的XML文件进行配置,如果将配置项直接写到类中,就会造成配置项与类的紧耦合,后续对于配置项的修改操作非常不方便,不利于项目的维护和扩展。此时,可以将这些配置项写到properties文件或者yml文件中,通过@PropertySource注解加载配置文件。
另外,如果项目本身就存在大量的properties配置文件或者yml配置文件,也可以统一由Spring的@PropertySource注解进行加载。
三、使用案例
结合案例学着印象才会更深刻~~
本节,主要实现一个通过@PropertySource注解加载properties配置文件,将properties配置文件中的配置项加载到Spring的环境变量中,获取Spring环境变量中配置项的值,并进行打印。案例的具体实现步骤如下所示。
1、新增test.properties文件
在spring-annotation-chapter-06工程的resources目录下新增test.properties文件,文件内容如下所示。
2、新增PropertySourceConfig类
PropertySourceConfig类的源码详见:spring-annotation-chapter-06工程下的io.binghe.spring.annotation.chapter06.config.PropertySourceConfig。
可以看到,PropertySourceConfig类是Spring的配置类,并且使用@PropertySource注解指定了test.properties配置文件的路径。
3、新增PropertySourceTest类
PropertySourceTest类的源码详见:spring-annotation-chapter-06工程下的io.binghe.spring.annotation.chapter06.PropertySourceTest。
可以看到,在PropertySourceTest类的main()方法中,通过AnnotationConfigApplicationContext类的对象获取到ConfigurableEnvironment类型的环境变量对象environment,然后通过environment对象获取配置文件中的name和age的值并进行打印。
4、运行PropertySourceTest类
运行PropertySourceTest类的main()方法,输出的结果信息如下所示。
可以看到,正确的输出了配置文件中的值。
说明:使用@PropertySource注解可以加载properties配置文件中的配置项,并将配置项加载到Spring的环境变量中,通过Spring的环境变量就可以获取到配置项的值。
四、源码时序图
结合时序图理解源码会事半功倍,你觉得呢?
本节,就以源码时序图的方式,直观的感受下@PropertySource注解在Spring源码层面的执行流程。@PropertySource注解在Spring源码层面的执行流程如图6-1~6-2所示。
图6-1
图6-2
由图6-1~图6-2可以看出,@PropertySource注解在Spring源码层面的执行流程会涉及到PropertySourceTest类、AnnotationConfigApplicationContext类、AbstractApplicationContext类、PostProcessorRegistrationDelegate类、ConfigurationClassPostProcessor类、ConfigurationClassParser类、PropertySourceRegistry类、PropertySourceProcessor类和DefaultPropertySourceFactory类。具体的源码执行细节参见源码解析部分。
五、源码解析
源码时序图整清楚了,那就整源码解析呗!
@PropertySource注解在Spring源码层面的执行流程,结合源码执行的时序图,会理解的更加深刻。
(1)运行案例程序启动类。
案例程序启动类源码详见:spring-annotation-chapter-06工程下的io.binghe.spring.annotation.chapter06.PropertySourceTest,运行PropertySourceTest类的main()方法。
在PropertySourceTest类的main()方法中调用了AnnotationConfigApplicationContext类的构造方法,并传入了PropertySourceConfig类的Class对象来创建IOC容器。接下来,会进入AnnotationConfigApplicationContext类的构造方法。
注意:@PropertySource注解在Spring源码中的执行流程的(2)~(11)步与第5章的@Import注解相同,这里不再赘述,直接跳到ConfigurationClassParser类的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicatefilter)方法。
(2)解析ConfigurationClassParser类的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicatefilter)方法。
源码详见:org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicatefilter),重点关注如下代码片段。
可以看到,在ConfigurationClassParser类的doProcessConfigurationClass()方法中,遍历获取到的@PropertySources注解和@PropertySource注解的属性,并且调用propertySourceRegistry对象的processPropertySource()方法解析注解属性的值。
(3)解析PropertySourceRegistry类的processPropertySource(AnnotationAttributes propertySource)方法。
源码详见:org.springframework.context.annotation.PropertySourceRegistry#processPropertySource(AnnotationAttributes propertySource)。
可以看到,在PropertySourceRegistry类的processPropertySource()方法中,解析@PropertySource注解中的属性后,将解析出的属性值封装到PropertySourceDescriptor对象中,调用propertySourceProcessor对象的processPropertySource()方法,并传入PropertySourceDescriptor对象进行进一步处理。
(4)解析PropertySourceProcessor类的processPropertySource(PropertySourceDescriptor descriptor)方法。
源码详见:org.springframework.core.io.support.PropertySourceProcessor#processPropertySource(PropertySourceDescriptor descriptor)。
可以看到,在processPropertySource()方法中,会通过@PropertySource注解的属性值解析出配置文件的内容,并且通过factory对象的createPropertySource()方法来创建PropertySource对象。
(5)解析DefaultPropertySourceFactory类的createPropertySource(String name, EncodedResource resource)方法。
源码详见:org.springframework.core.io.support.DefaultPropertySourceFactory#createPropertySource(String name, EncodedResource resource)。
createPropertySource()方法的源码比较简单,不再赘述。
(6)回到PropertySourceProcessor类的processPropertySource(PropertySourceDescriptor descriptor)方法。
在PropertySourceProcessor类的processPropertySource()方法中,创建完PropertySource对象后,会调用addPropertySource()方法将获取到的属性值添加到Spring的环境变量中。
(7)解析PropertySourceProcessor类的addPropertySource(PropertySource<?> propertySource)方法。
源码详见:org.springframework.core.io.support.PropertySourceProcessor#addPropertySource(PropertySource<?> propertySource)。
可以看到,在PropertySourceProcessor类的addPropertySource()方法中,会将解析出的配置文件的内容添加到Spring的环境变量中。具体就是在PropertySourceProcessor类的addPropertySource()方法中,获取到ConfigurableEnvironment中的MutablePropertySources对象,用来存储解析出的配置文件中的配置项内容。如果有相同的配置项内容,将existing对象强转为CompositePropertySource类型,把新旧相同的配置项进行合并,再放到MutablePropertySources对象中。
后续就可以通过Spring的环境变量,来获取到配置文件中的配置项内容。
至此,@PropertySource注解在Spring源码中的执行流程分析完毕。
六、总结
@PropertySource注解讲完了,我们一起总结下吧!
本章,首先介绍了@PropertySource注解的源码和使用场景,随后,简单给出了一个@PropertySource注解的使用案例。接下来,详细分析了@PropertySource注解的源码时序图和@PropertySource注解在Spring源码层面的执行流程。
七、思考
既然学完了,就开始思考几个问题吧?
关于@PropertySource注解,通常会有如下几个经典面试题:
- @PropertySource注解的执行流程?
- @PropertySource注解是如何将配置文件加载到环境变量的?
- @PropertySource注解有哪些使用场景?
- Spring中为何会设计一个@PropertySource注解来加载配置文件?
- 从@PropertySource注解的设计中,你得到了哪些启发?