@PropertySource属性源注解的原理

我们都知道spring默认读取bootstrap、application的配置文件,如果想让spring读取我们自定义的配置文件呢,例如我们的工程名my.propertie配置文件,其实可以通过@PropertySource注解实现,用起来很简单,通过注解指定配置文件的类路径即可。

java 复制代码
@PropertySource("classpath:my.properties")
@Component
public class PropertySourceTest {
}

会用以后开始看它的原理吧,还是在扫描bean阶段,发现bean上存在PropertySourcesPropertySource注解时,就会进入这个注解的处理逻辑。

kotlin 复制代码
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				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");
			}
		}

先处理@PropertySource注解,获取属性文件的类路径,一般为classpath:my.properties格式,然后读取生成属性源的工厂,用来根据属性文件Resource创建属性源PropertySource,之后再加入sping的环境配置的属性源头,加入的过程可以参加我之前写的一文看懂spring配置原理

java 复制代码
	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 {
                //根据路径加载Resource
				String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
				Resource resource = this.resourceLoader.getResource(resolvedLocation);
                //添加到环境配置的属性源供spring使用
				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;
				}
			}
		}
	}

如果发现环境中已经存在同名属性源了,就合并之后再保存到spring的环境配置中。否则如果是第一次处理@PropertySource,则添加到最后,没有处理过添加到上次处理过的属性源之前,即保存到配置中的属性源和加载顺序相反的,原因是spring默认读取最后加载的配置文件,即最新的配置文件。

java 复制代码
	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);
                //同名属性源为CompositePropertySource直接合并
				if (existing instanceof CompositePropertySource) {
					((CompositePropertySource) existing).addFirstPropertySource(newSource);
				}
                //转化成CompositePropertySource再合并
				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;
			}
		}
        //之前没有通过`@PropertySource`注解添加属性源
		if (this.propertySourceNames.isEmpty()) {
			propertySources.addLast(propertySource);
		}
        //通过`@PropertySource`注解添加属性源了,就往前放
		else {
			String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
			propertySources.addBefore(firstProcessed, propertySource);
		}
		this.propertySourceNames.add(name);
	}

总结下,spring处理@PropertySource注解,会根据注解读取配置文件,保存到spring的环境配置中,为了保证能读取到最新的配置,spring会将后加载的属性源放到前面。有不对的地方请大神指出,欢迎大家一起讨论交流,共同进步,更多请关注微信公众号 葡萄开源

相关推荐
追逐时光者5 小时前
精选 4 款基于 .NET 开源、功能强大的 Windows 系统优化工具
后端·.net
TF男孩5 小时前
ARQ:一款低成本的消息队列,实现每秒万级吞吐
后端·python·消息队列
AAA修煤气灶刘哥6 小时前
别让Redis「歪脖子」!一次搞定数据倾斜与请求倾斜的捉妖记
redis·分布式·后端
AAA修煤气灶刘哥6 小时前
后端人速藏!数据库PD建模避坑指南
数据库·后端·mysql
你的人类朋友6 小时前
什么是API签名?
前端·后端·安全
昵称为空C8 小时前
SpringBoot3 http接口调用新方式RestClient + @HttpExchange像使用Feign一样调用
spring boot·后端
架构师沉默9 小时前
设计多租户 SaaS 系统,如何做到数据隔离 & 资源配额?
java·后端·架构
RoyLin9 小时前
TypeScript设计模式:适配器模式
前端·后端·node.js
该用户已不存在10 小时前
Mojo vs Python vs Rust: 2025年搞AI,该学哪个?
后端·python·rust
Moonbit10 小时前
MoonBit 正式加入 WebAssembly Component Model 官方文档 !
前端·后端·编程语言