Spring源码解析-@PropertySource注解

Spring死磕系列-@PropertySource注解

PropertySource注解介绍

该注解给Environment中添加PropertySource提供了一种便捷且申明式的机制,@PropertySource注解是配合@Configuration标记的配置类一块使用的。

PropertySource注解使用

Demo演示-类布局

image-20201025000615500

Demo演示-主配置类

@Configuration
@PropertySources({
        @PropertySource("classpath:mysql.properties"),
        @PropertySource("classpath:oracle.properties")})
@PropertySource(value = "classpath:redis.properties")
public class DataSourceConfiguration {
}

Demo演示-资源文件定义

mysql.user.name=mysql
mysql.user.password=123456
mysql.driver.class=jdbc:mysql://ip:port/database_name
oracle.user.name=oracle
oracle.user.password=${mysql.user.password}
oracle.driver.class=jdbc:oracle:thin:@ip:port:database_name
reids.name=redis
redis.password=${mysql.user.password}

Demo演示-测试类

@RunWith(SpringJUnit4ClassRunner.class)
@SpringJUnitConfig(classes = DataSourceConfiguration.class)
public class DataSourceConfigurationTest {
    @Autowired
    ApplicationContext context;
    @Test
    public void testUserName() {
        String userName = context.getEnvironment().getProperty("mysql.user.name");
        assertThat(userName, equalTo("mysql"));
    }
    @Test
    public void testPassword() {
        String password = context.getEnvironment().getProperty("oracle.user.password");
        assertThat(password, equalTo("123456"));
    }
    @Test
    public void testRedisName() {
        String redisName = context.getEnvironment().getProperty("reids.name");
        assertThat(redisName, equalTo("redis"));
    }
    @Test
    public void testRedisPassword() {
        String redisPassword = context.getEnvironment().getProperty("redis.password");
        assertThat(redisPassword, equalTo("123456"));
    }
}

Demo演示-运行结果

image-20201025000218965

PropertySource注解定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
	/**
	 * 给该PropertySource起个名。如果没有,factory()属性指定的PropertySource工厂将会根据里面的资源信息生成一个名字
	 */
	String name() default "";
	/**
	 * 代表需要被加载资源的位置。note:资源位置不支持通配符(eg:**//*.properties),每个location必须明确指定一个资源
	 */
	String[] value();
	/**
	 * 如果value属性中指定的资源没有找到是否会忽略,默认false(没有找到会报错)
	 * @since 4.0
	 */
	boolean ignoreResourceNotFound() default false;
	/**
	 * 指定编码集
	 * @since 4.3
	 */
	String encoding() default "";
	/**
	 * 自定义一个PropertySource工厂(职责就是根据给定的resource创建PropertySource对象)
	 * 默认会使用DefaultPropertySourceFactory或ResourcePropertySource
	 * @since 4.3
	 */
	Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}

PropertySource注解解析流程

我们通过Import解析过程中知道,在ConfigurationClassParser类对配置类上所有可能出现的注解进行解析,本文我们只关注PropertySource注解的解析。

ConfigurationClassParser类

protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {

		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass, filter);
		}

		// Process any @PropertySource annotations
    	// 遍历从sourceClass上收集的PropertySources和PropertySource注解
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
                //如果environment是ConfigurableEnvironment的对象才进行属性源的处理
				processPropertySource(propertySource);
			}
			else {
                //否则会被忽略
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}
		// Process any @ComponentScan annotations
		   // 省略处理@ComponentScan逻辑
		// Process any @Import annotations
		   // 省略处理@Import逻辑
		// Process any @ImportResource annotations
		   // 省略处理@ImportResource逻辑
		// Process individual @Bean methods
		   // 省略处理@Bean方法逻辑
		// Process default methods on interfaces
		   // 省略处理接口中定义的default methods
		// Process superclass, if any
		   // 省略处理父类逻辑

		// No superclass -> processing is complete
		return null;
	}

通过查看ConfigurationClassParser类中对@PropertiesSource注解的解析,大致流程:

  1. 获取该配置类上所有的@PropertySources和@PropertySource注解的属性,封装成Set返回。
  2. 进行遍历,其实AnnotationAttributes类本质上是一个Map<String, Object>,就是该注解中 属性->值 的键值对信息。
  3. 然后对遍历元素进行解析,通过@PropertiesSource注解中factory指定的工厂创建PropertySource对象,然后放到Environment中。

如何从配置类上解析注解封装成Set自己查看源码,下面主要看一下后面的流程

image-20201025102654264

通过debug调试,可以清晰看到,已经把我们配置类上所有PropertySource注解解析封装成AnnotationAttributes对象。由于PropertySources注解是PropertySource注解的容器,里面元素全部是PropertySource,在处理容器注解时就是将里面的元素进行遍历处理。

PropertySource属性解析

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));
		//上面都是获取@PropertySource中的属性,并进行默认赋值
		for (String location : locations) {
			try {
                //解析location中的占位符。说明我们的location是可以灵活指定
				String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
                //通过解析后的location创建一个代表该资源的Resource对象,此时资源还没有真正被加载,仅仅是一个引用指向了该资源
				Resource resource = this.resourceLoader.getResource(resolvedLocation);
                //通过上面解析好的属性,交给PropertySourceFactory创建代表该属性源的PropertySource对象
                //然后真正执行给Environment添加PropertySource操作
				addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
			}
			catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) {
				//异常处理,记录日志
			}
		}
	}

image-20201025103638956

走到这步我们的属性源文件仅仅转换成一个Resource对象进行表示,其里面的内容还没有被加载进来

image-20201025104249044

在工厂创建PropertySource对象时,才真正去加载属性文件中的内容(但里面的占位符是没有解析的),而且如果没有指定名称,工厂会创建一个名字。我们可以看到创建一个ResourcePropertySource对象,此时的name属性用来保存资源名称,source属性保存资源文件解析出来的属性键值对。

将属性源添加到Environment

private void addPropertySource(PropertySource<?> propertySource) {
        // 这里propertySource全部都有名字,如果没有指定,在调用该方法之前PropertySourceFactory会生成一个
        // 此时已经加载资源文件中的内容,并在保存在propertySource的source字段,但属性如果存在占位符,此时还没有解析
		String name = propertySource.getName();
        // 1. 从environment中获取可以管理多个PropertySource的对象
		MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
        // 2. 处理已经存在属性源情况
		if (this.propertySourceNames.contains(name)) {
			PropertySource<?> existing = propertySources.get(name);
            // 如果已经存在属性源,再添加,存在一些属性的合并,通过CompositePropertySource对象完成
			if (existing != null) {
				PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
						((ResourcePropertySource) propertySource).withResourceName() : propertySource);
                // 2.1 如果存在的PropertySource是CompositePropertySource类型
				if (existing instanceof CompositePropertySource) {
					((CompositePropertySource) existing).addFirstPropertySource(newSource);
				}
				else {
                    // 2.2 如果存在的PropertySource不是CompositePropertySource类型
					if (existing instanceof ResourcePropertySource) {
						existing = ((ResourcePropertySource) existing).withResourceName();
					}
					CompositePropertySource composite = new CompositePropertySource(name);
					composite.addPropertySource(newSource);
					composite.addPropertySource(existing);
					propertySources.replace(name, composite);
				}
				return;
			}
		}
        // 3. 处理属性源不存在情况
        // 3.1 如果此时还没有任何属性源,则直接添加到最后(这个分支只会执行一次)
		if (this.propertySourceNames.isEmpty()) {
			propertySources.addLast(propertySource);   // 添加属性源
		}
		else {
            // 3.2 此时有属性源,则添加到最后一个元素的前面
			String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
			propertySources.addBefore(firstProcessed, propertySource);  // 添加属性源
		}
        // 4. 最后将该属性源名称添加到名称列表里
		this.propertySourceNames.add(name);
	}

通过上面的分析,从解析配置类开始,收集配置类上PropertySourcesPropertySource的属性,封装成Set返回,然后遍历,对每一个PropertySource的属性进行解析并对未指定的值设置默认值,将解析好的属性交给PropertySourceFactory工厂,工厂根据这些属性创建出PropertySource对象,最后将没有添加过的属性源添加到Environment中统一管理多个属性源的MutablePropertySources对象,将添加过的属性源进行一些属性的合并。

image-20201025105150484

我们第一次进来发现从Environment中获取的MutablePropertySources已经有两个属性源了,分别是保存系统属性的PropertiesPropertySource和系统环境变量的SystemEnvironmentPropertySource

image-20201025105548132

但此时属性源的名称列表还是空的,所有走第一次添加属性源逻辑,在最后添加属性源。

image-20201025105841676

image-20201025105925587

第一次添加后的属性源列表和属性源名称列表如上图所示

image-20201025110350089

image-20201025110436700

往后如果该属性源没有被添加且不是第一次添加的属性源,则始终会在最后一个属性源的前面插入

PropertySource类层次结构

image-20201024153917093

  • PropertySource:该类是代表name/value属性对的抽象基类,其中getSource()方法就是获取任何类型的对象,该对象可以是java.util.Propertiesjava.util.MapServletContextServletConfig,这些对象都是用来封装name/value属性对的。通常该类不是单独使用,而是通过PropertySources对象使用, 该PropertySources对象聚集了所有属性源并与PropertyResolver结合使用,PropertyResolver可以基于优先级进行搜索。

PropertySourceFactoty及其实现类

image-20201024154029416

  • PropertySourceFactory:基于资源创建PropertySource的策略接口
public interface PropertySourceFactory {
	/**
	 * 通过给定的资源(resource)创建PropertySource对象
	 */
	PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException;
}
  • DefaultPropertySourceFactory:提供的默认实现
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));
	}
}

PropertySources类及其实现类

image-20201024232142780

  • PropertySources:该类持有一个或多个封装了属性对的PropertySource对象
public interface PropertySources extends Iterable<PropertySource<?>> {
	/**
	 * Return a sequential {@link Stream} containing the property sources.
	 * @since 5.1
	 */
	default Stream<PropertySource<?>> stream() {
		return StreamSupport.stream(spliterator(), false);
	}
	/**
	 * 是否存在给定名称的属性源
	 */
	boolean contains(String name);
	/**
	 * 返回指定名称的属性源,如果不存在返回null
	 */
	@Nullable
	PropertySource<?> get(String name);
}
  • MutablePropertySources:PropertySources类的默认实现,该类提供了对属性源的一下操作,下面列出主要方法。
    1. addAfter(String relativePropertySourceName, PropertySource<?> propertySource):void 在指定属性源的后面添加
    2. addBefore(String relativePropertySourceName, PropertySource<?> propertySource):void 在指定属性源的前面添加
    3. addFirst(PropertySource<?> propertySource):void 在最前面添加
    4. addLast(PropertySource<?> propertySource):void 在最后面添加

上面类之间的协作关系图

image-20201025005336821

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值