1 介绍
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
@Repeatable(PropertySources.class)
public @interface PropertySource {
String name() default "";
String[] value();
boolean ignoreResourceNotFound() default false;
/**
* A specific character encoding for the given resources, e.g. "UTF-8".
* @since 4.3
*/
String encoding() default "";
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
1.1 作用
用于指定读取资源文件的位置,不仅仅支持properties文件,也支持xml文件,并且可以通过yaml解析器,配合自定义PropertySourceFactory实现yml配置文件的解析
1.2 属性介绍
name
指定资源的名称,如果没有指定,将根据基础资源描述生成
value
指定资源位置,可以是类路径,也可以是文件路径
ignoreResourceNotFound
指定是否忽略资源文件有没有,默认是false,也就是说当资源文件不存在的时候,spring启动后将会报错
factory
指定解析工厂, 默认PropertySourceFactory
2 入门案例
依然新建一个moudle: 06-spring-annnotion-propertysource
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>study.wyy</groupId>
<artifactId>00-spring-annnotion-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
需求
读取classpath下的jdbc.properties文件,文件内容如下
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/db
mysql.username=root
mysql.password=heihei
定义一个DataSource
这里不做具体的MySQL连接,也就没有引入相关依赖,自己简单声明一个类,封装我们这些连接信息即可
package study.wyy.spring.anno.propertysource.bean;
import lombok.Data;
import lombok.ToString;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/21 9:06 下午
*/
@Data
@ToString
public class DataSource {
private String driver;
private String url;
private String username;
private String password;
}
定义一个配置类JdbcConfig
package study.wyy.spring.anno.propertysource.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import study.wyy.spring.anno.propertysource.bean.DataSource;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/21 9:05 下午
*/
@Configuration
public class JdbcConfig {
@Value("${mysql.driver}")
private String driver;
@Value("${mysql.url}")
private String url;
@Value("${mysql.username}")
private String username;
@Value("${mysql.password}")
private String password;
@Bean
public DataSource dataSource(){
DataSource dataSource = new DataSource();
dataSource.setDriver(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
测试
package study.wyy.spring.anno.propertysource.test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import study.wyy.spring.anno.propertysource.bean.DataSource;
import study.wyy.spring.anno.propertysource.config.JdbcConfig;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/21 9:14 下午
*/
public class Test {
@org.junit.Test
public void test01(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JdbcConfig.class);
DataSource dataSource = context.getBean(DataSource.class);
System.out.println(dataSource);
}
}
输出
DataSource(driver=null, url=null, username=null, password=null)
可见并没有读取到配置文件的值
使用PropertySource注解指定配置文件的位置
@Configuration
@PropertySource(value = "classpath:jdbc.properties")
public class JdbcConfig {
在测试,这个时候就能读取出来了
DataSource(driver=com.mysql.jdbc.Driver, url=jdbc:mysql://localhost:3306/db, username=root, password=heihei)
测试一下xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="mysql.driver">com.mysql.jdbc.Driver</entry>
<entry key="mysql.url">jdbc:mysql://localhost:3306/db</entry>
<entry key="mysql.username">root</entry>
<entry key="mysql.password">heihei</entry>
</properties>
@org.junit.Test
public void test02(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JdbcConfig2.class);
DataSource dataSource = context.getBean(DataSource.class);
System.out.println(dataSource);
}
DataSource(driver=com.mysql.jdbc.Driver, url=jdbc:mysql://localhost:3306/db, username=root, password=heihei)
3 PropertySourceFactory
3.1 源码分析执行流程
spring 只提供了这么一个唯一的实现
public class DefaultPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
}
}
- 如果name是空: new ResourcePropertySource(resource));构造
- 不是空:new ResourcePropertySource(name, resource)
显而易见:第一个构造本质也是第二个构造,相当于会生成一个默认的name
public ResourcePropertySource(String name, EncodedResource resource) throws IOException {
super(name, PropertiesLoaderUtils.loadProperties(resource));
this.resourceName = getNameForResource(resource.getResource());
}
接下会调用PropertiesLoaderUtils.loadProperties方法
public static Properties loadProperties(EncodedResource resource) throws IOException {
Properties props = new Properties();
fillProperties(props, resource);
return props;
}
- 这里实际也是用了java自己的Properties类来读取配置文件
fillProperties(props, resource);
:这个方法就是在填充我们的属性
public static void fillProperties(Properties props, EncodedResource resource)
throws IOException {
// new DefaultPropertiesPersister PropertiesPersister接口的实现
fillProperties(props, resource, new DefaultPropertiesPersister());
}
private static final String XML_FILE_EXTENSION = ".xml";
static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
throws IOException {
InputStream stream = null;
Reader reader = null;
try {
// 如果是xml文件,则调用persister.loadFromXml(props, stream);
String filename = resource.getResource().getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
stream = resource.getInputStream();
// 加载配置
persister.loadFromXml(props, stream);
}
// 是否通过reader读取(是否指定了encoding)
else if (resource.requiresReader()) {
// 会根据传入编码格式创建reader
reader = resource.getReader();
// 加载配置
persister.load(props, reader);
}
else {
// 如果没有指定encoding,则直接创建输入流
stream = resource.getInputStream();
// 加载配置
persister.load(props, stream);
}
}
finally {
if (stream != null) {
stream.close();
}
if (reader != null) {
reader.close();
}
}
}
public boolean requiresReader() {
// 如果我们指定了encoding就会通过encoding创建reader
return (this.encoding != null || this.charset != null);
}
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
调用PropertiesPersister接口的load方法加载配置
@Override
public void load(Properties props, InputStream is) throws IOException {
props.load(is);
}
这里就是调用的JAVA的Properties的load方法,完成读取properties文件加载配置,只是spring给包装起来而已。
整个properties文件加载流程大致如此,xml的文件也是同样的流程。
4 自定义PropertySourceFactory解析yml
先引入一个依赖
snakeyaml
用于解析yml
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.26</version>
</dependency>
实现PropertySourceFactory接口: YamlPropertySourceFactory
参考spring的DefaultPropertySourceFactory
实现,最终返回的就是一个PropertiesPropertySource
也就是我们需要将yaml文件解析成Properties返回
public class EncodedResource implements InputStreamSource {
private final Resource resource;
@Nullable
private final String encoding;
@Nullable
private final Charset charset;
形参的这个类spring对Resource做了一层包装
package study.wyy.spring.anno.propertysource.spi;
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 org.springframework.core.io.support.ResourcePropertySource;
import java.io.IOException;
import java.util.Properties;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/22 9:41 上午
*/
public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
//参考spring的`DefaultPropertySourceFactory`实现,最终返回的就是一个PropertiesPropertySource
// 也就是我们需要将yaml文件解析成Properties返回
YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
// 形参的这个EncodedResource类spring对Resource做了一层包装,从里面可以获取到Resource
yamlPropertiesFactoryBean.setResources(resource.getResource());
// 解析成Properties
Properties properties = yamlPropertiesFactoryBean.getObject();
return (name != null ? new PropertiesPropertySource(name, properties) :
// 如果没有指定name就使用文件名作为name属性
new PropertiesPropertySource(resource.getResource().getFilename(),properties));
}
}
测试
mysql:
driver: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db
username: root
password: heihei
配置类
package study.wyy.spring.anno.propertysource.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import study.wyy.spring.anno.propertysource.bean.DataSource;
import study.wyy.spring.anno.propertysource.spi.YamlPropertySourceFactory;
/**
* @author by wyaoyao
* @Description
* @Date 2020/11/21 9:05 下午
*/
@Configuration
@PropertySource(value = "classpath:jdbc.yml",factory = YamlPropertySourceFactory.class)
public class JdbcConfig3 {
@Value("${mysql.driver}")
private String driver;
@Value("${mysql.url}")
private String url;
@Value("${mysql.username}")
private String username;
@Value("${mysql.password}")
private String password;
@Bean
public DataSource dataSource(){
DataSource dataSource = new DataSource();
dataSource.setDriver(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
@org.junit.Test
public void test03(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JdbcConfig3.class);
DataSource dataSource = context.getBean(DataSource.class);
System.out.println(dataSource);
}
DataSource(driver=com.mysql.jdbc.Driver, url=jdbc:mysql://localhost:3306/db, username=root, password=heihei)