07 PropertySource

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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值