Spring中@PropertySource的使用方法和运行原理详解

 更新时间:2023年11月23日 09:31:10   作者:dream21st  
这篇文章主要介绍了Spring中@PropertySource的使用方法和运行原理详解,PropertySource注解可以方便和灵活的向Spring的环境容器(org.springframework.core.env.Environment Environment)中注入一些属性,这些属性可以在Bean中使用,需要的朋友可以参考下

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的使用方法和运行原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Jackson序列化丢失泛型的解决

    Jackson序列化丢失泛型的解决

    这篇文章主要介绍了Jackson序列化丢失泛型的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • SpringBoot工程中Spring Security应用实践记录流程分析

    SpringBoot工程中Spring Security应用实践记录流程分析

    Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。这篇文章主要介绍了SpringBoot工程中Spring Security应用实践,需要的朋友可以参考下
    2021-09-09
  • 剑指Offer之Java算法习题精讲二叉树与斐波那契函数

    剑指Offer之Java算法习题精讲二叉树与斐波那契函数

    跟着思路走,之后从简单题入手,反复去看,做过之后可能会忘记,之后再做一次,记不住就反复做,反复寻求思路和规律,慢慢积累就会发现质的变化
    2022-03-03
  • SpringBoot2零基础到精通之异常处理与web原生组件注入

    SpringBoot2零基础到精通之异常处理与web原生组件注入

    SpringBoot是Spring全家桶的成员之一,基于约定优于配置的思想(即有约定默认值,在不配置的情况下会使用默认值,在配置文件下配置的话会使用配置的值)。SpringBoot是一种整合Spring技术栈的方式(或者说是框架),同时也是简化Spring的一种快速开发的脚手架
    2022-03-03
  • Java 压缩图片并打包成ZIP文件的示例

    Java 压缩图片并打包成ZIP文件的示例

    这篇文章主要介绍了Java 压缩图片并打包成ZIP文件的示例,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2020-12-12
  • Java ForkJoin框架的原理及用法

    Java ForkJoin框架的原理及用法

    这篇文章主要介绍了Java ForkJoin框架的原理及用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • mybatis-plus 关于savebatch,saveorupdatebatch遇到的坑及解决办法

    mybatis-plus 关于savebatch,saveorupdatebatch遇到的坑及解决办法

    本文主要介绍了mybatis-plus 关于savebatch,saveorupdatebatch遇到的坑及解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • SpringBoot如何统一JSON信息返回

    SpringBoot如何统一JSON信息返回

    这篇文章主要介绍了SpringBoot如何统一JSON信息返回问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • Hibernate映射文件id的generator配置方法

    Hibernate映射文件id的generator配置方法

    下面小编就为大家分享一篇Hibernate映射文件id的generator配置方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • Mybatis Plus 大数据游标分页的实现

    Mybatis Plus 大数据游标分页的实现

    使用MyBatis Plus的游标分页,我们可以轻松应对大数据量的场景,本文主要介绍了Mybatis Plus 大数据游标分页的实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-07-07

最新评论