Spring中@PropertySource的使用方法和运行原理详解
作者:dream21st
1 @PropertySource使用方法和运行原理机制详解
1.1 @PropertySource简要说明
PropertySource注解可以方便和灵活的向Spring的环境容器(org.springframework.core.env.Environment Environment)中注入一些属性,这些属性可以在Bean中使用。
1.2 @PropertySource基本使用
首先,我们用idea构建一个Springboot项目,项目的pom.xml具体内容如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.11.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>springboot-code</artifactId> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
项目构建完毕后,我们打开@PropertySource类,可以看到其基本内容如下:
package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.io.support.PropertySourceFactory; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented /** * @PropertySource可以在PropertySources中复用 **/ @Repeatable(PropertySources.class) public @interface PropertySource { /** * 该PropertySource的名字,如果不指定会用文件名按照一定的方式生成 **/ String name() default ""; /** * 指定扫描文件的路径,可以配置多个 **/ String[] value(); /** * 是否忽略文件,如果文件没有找到,默认否,就是如果没有文件会报错 **/ boolean ignoreResourceNotFound() default false; /** * 指定文件编码 **/ String encoding() default ""; /** * 指定文件解析工厂 **/ Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class; }
1.2.1 读取.properties结尾的配置文件
首先在resources资源目录下面建一个文件名为women.properties的文件,文件的具体内容如下:
women.name=lili women.age=18
接着我们编写一个Women的类,并在上面通过@PropertySource的方式加载women.properties文件,同时通过@Value注入的方式把配置文件中的值注入到Women类的相关属性上面,最终在Women类上加上@Component注解交给容器管理。相关代码的内容如下:
package com.dream21th.service; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; @Component @PropertySource(value = "classpath:women.properties") public class Women { @Value("${women.name}") private String name; @Value("${women.age}") private String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return "Women{" + "name='" + name + '\'' + ", age='" + age + '\'' + '}'; } }
1.2.2 读取.xml结尾的配置文件
首先在resources资源目录下面建一个文件名为men.properties的文件,文件的具体内容如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <entry key="man.name">李白</entry> <entry key="man.hobby">写诗</entry> </properties>
接着我们编写一个Men的类,并在上面通过@PropertySource的方式加载men.xml文件,同时通过@Value注入的方式把配置文件中的值注入到Men类的相关属性上面,最终在Men类上加上@Component注解交给容器管理。相关代码的内容如下:
package com.dream21th.service; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; @Component @PropertySource(value = "classpath:men.xml") public class Men { @Value("${man.name}") private String name; @Value("${man.hobby}") private String hobby; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getHobby() { return hobby; } public void setHobby(String hobby) { this.hobby = hobby; } @Override public String toString() { return "Men{" + "name='" + name + '\'' + ", hobby='" + hobby + '\'' + '}'; } }
1.2.3 读取.yml或者.yaml结尾的配置文件
由于默认的DefaultPropertySourceFactory资源工厂并不能解析.yml或者.yaml结尾的配置文件。在此,我们先要自定义一个YAML解析的资源工厂,具体代码实现如下:
package com.dream21th.config; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.PropertySourceFactory; import java.io.IOException; import java.util.Properties; public class YAMLPropertySourceFactory implements PropertySourceFactory { @Override public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException { //1,创建一个YAML文件的解析工厂。 YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); //2,设置资源。 factory.setResources(encodedResource.getResource()); //3,获取解析后的Properties对象 Properties properties = factory.getObject(); //4返回。此时不能像默认工厂那样返回ResourcePropertySource对象 ,要返回他的父类PropertiesPropertySource对象。 return name != null ? new PropertiesPropertySource(name, properties) : new PropertiesPropertySource(encodedResource.getResource().getFilename(),properties); } }
接着在resources资源目录下面建一个文件名为person.yml的文件,文件的具体内容如下:
person: name: zhangsan address: shanghaihsi
接着我们编写一个Person的类,并在上面通过@PropertySource的方式加载person.yml文件,在配置文件的同时要指定资源文件的解析工厂类,这里我们指定为YAMLPropertySourceFactory.class。同时通过@Value注入的方式把配置文件中的值注入到Person类的相关属性上面,最终在Person类上加上@Component注解交给容器管理。相关代码的内容如下:
package com.dream21th.service; import com.dream21th.config.YAMLPropertySourceFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; @Component @PropertySource(value = "classpath:person.yml",factory = YAMLPropertySourceFactory.class) public class Person { @Value("${person.name}") private String name; @Value("${person.address}") private String address; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", address='" + address + '}'; } }
1.2.4 读取其他方式结尾的配置文件
严格意义上面来讲,默认的DefaultPropertySourceFactory属性读取工厂,不仅仅只能读取.properties和xml结尾的配置文件。基本意义上能读取所有后缀的文件,只要文件里面的内容按照.properties的方式拼写。
我们在resources资源目录下面建一个文件名为other.xxx(xxx代表任意文件的后缀)的文件,文件的具体内容如下:
other.name=异类 other.home=火星
接着我们编写一个Other的类,并在上面通过@PropertySource的方式加载other.xxx文件,在配置文件的同时要指定资源文件的编码,这里我们指定为utf-8。同时通过@Value注入的方式把配置文件中的值注入到Person类的相关属性上面,最终在Other类上加上@Component注解交给容器管理。相关代码的内容如下:
package com.dream21th.service; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; @Component @PropertySource(value = "classpath:other.xxx",encoding = "utf-8") public class Other { @Value("${other.name}") private String name; @Value("${other.home}") private String home; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getHome() { return home; } public void setHome(String home) { this.home = home; } @Override public String toString() { return "Other{" + "name='" + name + '\'' + ", home='" + home + '\'' + '}'; } }
1.2.5 测试
在启动类下面注入上面例子中的Bean,在容器启动成功后打印相关数据,具体实现代码如下:
package com.dream21th; import com.dream21th.service.Men; import com.dream21th.service.Other; import com.dream21th.service.Person; import com.dream21th.service.Women; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringbootSourceApplication implements CommandLineRunner { @Autowired private Person person; @Autowired private Women women; @Autowired private Men men; @Autowired private Other other; public static void main(String[] args) { SpringApplication.run(SpringbootSourceApplication.class,args); } @Override public void run(String... args) throws Exception { System.out.println(person); System.out.println(women); System.out.println(men); System.out.println(other); } }
启动成功后,控制台的输出内容如下,代表我们通过四种文件类型的属性读取成功。
Person{name='zhangsan', address='shanghaihsi} Women{name='lili', age='18'} Men{name='李白', hobby='写诗'} Other{name='异类', home='火星'}
1.3 @PropertySource使用小结
通过上面的例子,我们可以读取不同后缀的文件中的内容到容器中去。同时也要说明,虽然上面我们是一个配置文件一个类,并且也是在对应类里面使用对应属性。但并不代表只能这样用,@PropertySource属性中的value属性是可以输入多个路径的,我们可以在一个文件中加载所有的文件,然后再其他的实例中使用相关的属性。当然也可以在@PropertySources中使用多个@PropertySource的方式来读取文件。
1.4 @PropertySource运行原理机制
源码的启动过程比较复杂和繁琐,由于篇幅有限,在这里,我们就不追代码,直接定位到对应的处理逻辑代码上。
我们定位到org.springframework.context.annotation.ConfigurationClassParser类的doProcessConfigurationClass方法上面来。
@Nullable protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { //此处省略一些代码 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( /** * 类上面有PropertySources.class **/ sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { //处理逻辑 processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } //此处省略一些代码 }
private 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"); //文件解析工厂 PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ? DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass)); 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) { // Placeholders not resolvable or resource not found when trying to open it if (ignoreResourceNotFound) { if (logger.isInfoEnabled()) { logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage()); } } else { throw ex; } } } }
//加到Environment中 private void addPropertySource(PropertySource<?> propertySource) { String name = propertySource.getName(); MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources(); if (this.propertySourceNames.contains(name)) { // We've already added a version, we need to extend it 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); }
到此这篇关于Spring中@PropertySource的使用方法和运行原理详解的文章就介绍到这了,更多相关@PropertySource的使用方法和运行原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!