撸一撸Spring Framework-IOC-配置管理(Environment与PropertySource)

   撸一撸Spring Framework-IoC系列文章目录

Environment集成了两个关键特性,配置管理和profiles管理(本质上还是配置相关的东西)

  • profile:用于决定一个类是否会被装载到spring容器中,在类上添加@Profile注解后,容器在装载对应beanDefinition时,会判断类上指定的profiles是否为active profiles,是则装载、否则忽略(springboot中的application-{profile}.properties机制也是基于这个原理)。适用于多环境差异化的场景
  • 配置管理:对应用来说,配置管理无疑是很重要的,包括配置的增、删、改、查,比如通过@PropertySources和@PropertySource引入properties,通过@Value将配置项注入到bean的属性中,通过environment.getPropertySources().replace方法mock系统环境变量,这都属于配置管理的范畴。jvm参数、系统环境变量、servlet上下文参数、用户自定义的properties文件等各类配置,在spring中都会对应一个PropertySource对象,这些PropertySource对象组成了一个PropertySources对象,被关联在Environment中,对外提供服务。通过下面这个demo,我们大致感受下Environment、PropertySources、PropertySource的关系,以及PropertySource的含义
    @PropertySources(value={
            @PropertySource("classpath:application.properties"),
            @PropertySource("classpath:db.properties")
    })
    @Configuration
    public class EnvironmentDemo {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext();
            applicationContext.register(EnvironmentDemo.class);
            applicationContext.refresh();
    
            //获取Environment中的PropertySources对象,然后遍历其中的PropertySource对象并打印
            MutablePropertySources propertySources=applicationContext.getEnvironment().getPropertySources();
            propertySources.forEach(propertySource -> {
                System.out.println(propertySource);
                System.out.println("==========================");
            });
        }
    }
    控制台输出如下:四个PropertySource分别对应jvm参数、系统环境变量、以及通过@PropertySources注解引入的两个properties文件

下面是相关组件的UML图,包括PropertySource、PropertySources、PropertyResolver、Environment、我们就围绕着这张UML图去解读它们是如何配合完成profiles管理和配置管理功能的

其中配置管理功能与图上的四类组件都息息相关、而profiles管理功能是由Environment自身提供并且实现的

PropertySource:配置管理的基础,是对任何可以提供key/value对的配置源的抽象,比如map、properties、配置中心,都是可以提供key/value对的配置源,都可以看做是PropertySource,它们存储配置数据、并提供配置读取功能(@Value("${jdbc.url}")、Environment.getProperty("jdbc.url")这两种配置解析方式都依赖于PropertySource的配置读取功能 ),PropertySource类的结构如下:

public abstract class PropertySource<T> {
	protected final String name;
	protected final T source;

    //source对应上面说的"可以提供key/value对的配置源"
    //name对应PropertySource的唯一标识,MutablePropertySources在管理众多PropertySource时,就通过name来区分它们
	public PropertySource(String name, T source) {
		Assert.hasText(name, "Property source name must contain at least one character");
		Assert.notNull(source, "Property source must not be null");
		this.name = name;
		this.source = source;
	}

	@SuppressWarnings("unchecked")
	public PropertySource(String name) {
		this(name, (T) new Object());
	}

	public String getName() {
		return this.name;
	}

	public T getSource() {
		return this.source;
	}

	public boolean containsProperty(String name) {
		return (getProperty(name) != null);
	}

	//配置读取,交给子类实现,实际上就是从配置源中获取value值
	public abstract Object getProperty(String name);

    。。。
}

MapPropertySource:以Map为配置源的PropertySource,很好理解,下面是一个简单的demo

public static void main(String[] args) throws IOException {
	Map<String, Object> map = new HashMap<>();
	map.put("name", "bobo");
	map.put("age", "18");
	PropertySource propertySource = new MapPropertySource("myPropertySource", map);
	if (propertySource.containsProperty("name")) {
		System.out.println(propertySource.getProperty("name"));
	}
}

MapPropertySource的核心方法getProperty、containtsProperty如下,如上所述,就是根据配置源去判断key是否存在、获取value值

//source指配置源,在这里就是构建MapPropertySource时传入的Map对象
@Override
public Object getProperty(String name) {
	return this.source.get(name);
}

@Override
public boolean containsProperty(String name) {
	return this.source.containsKey(name);
}

PropertiesPropertySource:继承于MapPropertySource,由于Properties继承于Map,所以在MapPropertySource的基础上,PropertiesPropertySource要做的仅仅是把配置源Properties对象转换成Map对象而已,转换工作是在构造函数中做的,如下:

public class PropertiesPropertySource extends MapPropertySource {

	@SuppressWarnings({"rawtypes", "unchecked"})
	public PropertiesPropertySource(String name, Properties source) {
		super(name, (Map) source);
	}
	。。。
}

ResourcePropertySource:继承于PropertiesPropertySource,在后者的基础上,ResourcePropertySource要做的仅仅是通过一个Resource对象、或者一个诸如"classpath:db.properties"的文件路径加载出Properties对象而已,转换工作同样是在构造函数中做的,如下:

public class ResourcePropertySource extends PropertiesPropertySource {

	private final String resourceName;

	public ResourcePropertySource(String name, Resource resource) throws IOException {
		//从resource中加载properties
		super(name, PropertiesLoaderUtils.loadProperties(new EncodedResource(resource)));
		this.resourceName = getNameForResource(resource);
	}
	
	。。。
}

在文章开头的demo中,@Configuration类上通过@PropertySources(PropertySource[])注解,引入了两个properties文件,你可以回到到开头看看demo的控制台输出,最终它们都被转换成了ResourcePropertySource对象

PropertySources:内容比较简单,PropertySource的复数抽象,维护并管理一组PropertySource对象,我们主要关注其实现类MutablePropertySources,Mutable意味着可变的,它允许我们操作其管理的PropertySource集合,包括:add*(新增PropertySource)、remove(删除PropertySource)、replace(替换PropertySource)等。addFirst、addLast、addBefore这种添加操作可以控制PropertySource的优先级,排在前面的PropertySource优先级高于后面,这里的优先级简而言之就是同一个key如果在多个PropertySource中都存在,以优先级高的为准,你可以参考下下面这个例子:

public static void main(String[] args) {
	AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext();
	applicationContext.register(EnvironmentDemo.class);
	applicationContext.refresh();

	ConfigurableEnvironment environment=applicationContext.getEnvironment();
	MutablePropertySources propertySources=environment.getPropertySources();

	Map<String,Object> map1=new HashMap<>();
	map1.put("name","zhang san");
	MapPropertySource propertySource1=new MapPropertySource("propertySource1",map1);

	Map<String,Object> map2=new HashMap<>();
	map2.put("name","li si");
	MapPropertySource propertySource2=new MapPropertySource("propertySource2",map2);

	propertySources.addFirst(propertySource2);
	propertySources.addFirst(propertySource1);

    //输出zhang san,把两个addFirst调换位置后,输出li si
	System.out.println(environment.getProperty("name"));
}

默认情况下,优先级顺序从高往低是jvm参数>系统环境变量>用户自定义的properties,若用户自定义了多个properties,其优先级和@PropertySource注解被扫描到的顺序有关,如果项目的@PropertySource集中在一个类中,则越靠后的@PropertySource对应的优先级越高,如果分散在多个类中,就不太好确定了。如果你遇到了"诡异"的配置项取值错误问题,可以往优先级方面考虑

replace是一个非常有用的特性,比如在测试时,可以通过该特性mock环境变量(前面说过环境变量会被转换为一个PropertySource对象)

MutablePropertySources propertySources = environment.getPropertySources();
MockPropertySource mockEnvVars = new MockPropertySource().withProperty("xyz", "myValue");
propertySources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars);

PropertyResolver:配置解析的顶层接口,核心方法如下(看起来和PropertySource提供的方法有部分重叠,不过PropertySource抽象的是配置源,PropertyResolver抽象的是配置解析的能力)

//是否包含指定property
boolean containsProperty(String key);
//查询指定property的值
String getProperty(String key);
//将诸如${user.name}的占位符替换为property值
String resolvePlaceholders(String text);
//获取原始的property值后,通过ConversionService将其转换为指定类型
<T> T getProperty(String key, Class<T> targetType);

ConfigurablePropertyResolver:PropertyResolver的子接口,允许开发者对配置解析工作进行干预,核心方法如下:

//设置类型转换服务,getProperty(String key, Class<T> targetType)方法中的类型转换工作,就是通过ConversionService完成的
void setConversionService(ConfigurableConversionService conversionService);
//设置配置占位符前缀,spring中默认的前缀为"${"
void setPlaceholderPrefix(String placeholderPrefix);
//设置配置占位符后缀,spring中默认的后缀为"}"
void setPlaceholderSuffix(String placeholderSuffix);

PropertySourcesPropertyResolver:PropertyResolver的主要实现类,维护一个PropertySources引用,通过遍历它维护的一组PropertySource来实现其配置解析工作,如下:

public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {

	@Nullable
	private final PropertySources propertySources;

	public PropertySourcesPropertyResolver(PropertySources propertySources) {
		this.propertySources = propertySources;
	}

	@Override
	public boolean containsProperty(String key) {
		if (this.propertySources != null) {
			for (PropertySource<?> propertySource : this.propertySources) {
				if (propertySource.containsProperty(key)) {
					return true;
				}
			}
		}
		return false;
	}

	@Nullable
	protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
		if (this.propertySources != null) {
			for (PropertySource<?> propertySource : this.propertySources) {
				if (logger.isTraceEnabled()) {
					logger.trace("Searching for key '" + key + "' in PropertySource '" +
							propertySource.getName() + "'");
				}
				Object value = propertySource.getProperty(key);
				if (value != null) {
					if (resolveNestedPlaceholders && value instanceof String) {
						value = resolveNestedPlaceholders((String) value);
					}
					logKeyFound(key, propertySource, value);
					return convertValueIfNecessary(value, targetValueType);
				}
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Could not find key '" + key + "' in any property source");
		}
		return null;
	}

    。。。
}

Environment:配置管理、profiles管理的的门面,从PropertyResolver那里继承了配置解析的能力,自身提供了profiles解析的能力

public interface Environment extends PropertyResolver {

    //获取activeProfiles,可以用于定制更灵活的多环境差异化机制
	String[] getActiveProfiles();

    //获取默认profiles,如果没有显式设置activeProfiles,则使用defaultProfiles作为activeProfiles
	String[] getDefaultProfiles();
    
    //判断指定profiles是否为activeProfiles,在类上使用@Profile时,会用该方法的判断结果决定是否加载对应的beanDefinition
	boolean acceptsProfiles(Profiles profiles);
}

ConfigurableEnvironment:只有"解析"可谈不上"管理",需要注意的是,不论是配置解析还是profiles解析,"解析"大致可以认为是查询,相比我们说的"管理"还少了一些新增、修改、删除等能力,这些能力就由Environment的子接口ConfigurableEnvironment来提供,具体包含:

  • 设置active profiles、default profiles
  • 它的getPropertySources方法会返回MutablePropertySources实例,允许我们利用MutablePropertySources来操作PropertySource
  • 从ConfigurablePropertyResolver继承来的关于配置解析工作的干预能力

通过ConfigurableApplicationContext#getEnvironment()拿到的就是一个ConfigurableEnvironment实例,其本意就是为了方便开发者对Environment进行配置,本文的代码段中数次通过这种方式使用了配置管理的能力

AbstractEnvironment:Environment的抽象实现,主要实现了两块内容:1.继承于PropertyResolver体系的配置解析及管理能力,实现方式为委托,从文章开头的UML关系图可以看出AbstractEnvironment关联了一个ConfigurablePropertyResolver实例(默认实现为上述的PropertySourcesPropertyResolver),它将相关工作全部委托给ConfigurablePropertyResolver  2.Environment体系自身的profiles管理、其他配置管理能力

下面是第1点相关源码:

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
	private final ConfigurablePropertyResolver propertyResolver;
	private final MutablePropertySources propertySources;
	
	protected AbstractEnvironment(MutablePropertySources propertySources) {
		this.propertySources = propertySources;
		//默认创建一个通过MutablePropertySources构建的PropertySourcesPropertyResolver对象
        this.propertyResolver = createPropertyResolver(propertySources);
		customizePropertySources(propertySources);
	}
	
	protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
		return new PropertySourcesPropertyResolver(propertySources);
	}
	

    //配置解析相关工作统统委托给propertyResolver。。。

	public boolean containsProperty(String key) {
		return this.propertyResolver.containsProperty(key);
	}

	@Override
	public String getProperty(String key) {
		return this.propertyResolver.getProperty(key);
	}
	
	public String resolvePlaceholders(String text) {
		return this.propertyResolver.resolvePlaceholders(text);
	}

    @Override
	public void setConversionService(ConfigurableConversionService conversionService) {
		this.propertyResolver.setConversionService(conversionService);
	}

	@Override
	public void setPlaceholderPrefix(String placeholderPrefix) {
		this.propertyResolver.setPlaceholderPrefix(placeholderPrefix);
	}

	@Override
	public void setPlaceholderSuffix(String placeholderSuffix) {
		this.propertyResolver.setPlaceholderSuffix(placeholderSuffix);
	}
	
	。。。
}

关于第2点,我们主要关注profiles(其他配置管理,参考上述ConfigurableEnvironment的内容即可)。AbstractEnvironment中维护了两个profiles集合,activeProfiles取自配置项spring.profiles.active、如果用户没有指定,则使用defaultProfiles作为activeProfiles,默认情况下,defaultProfiles的值为"default",用户可以通过配置项spring.profiles.default指定

public abstract class AbstractEnvironment implements ConfigurableEnvironment {

	public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
    public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
    protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
	
    //维护activeProfiles
	private final Set<String> activeProfiles = new LinkedHashSet<>();

    //维护defaultProfiles
	private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());

    //配置解析器
    private final ConfigurablePropertyResolver propertyResolver;
    
	//默认从配置项spring.profiles.active中获取activeProfiles
	protected Set<String> doGetActiveProfiles() {
		synchronized (this.activeProfiles) {
			if (this.activeProfiles.isEmpty()) {
				String profiles = doGetActiveProfilesProperty();
				if (StringUtils.hasText(profiles)) {
					setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
							StringUtils.trimAllWhitespace(profiles)));
				}
			}
			return this.activeProfiles;
		}
	}
    
    //默认从配置项spring.profiles.default中获取defaultProfiles
    protected Set<String> doGetDefaultProfiles() {
		synchronized (this.defaultProfiles) {
			if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
				String profiles = doGetDefaultProfilesProperty();
				if (StringUtils.hasText(profiles)) {
					setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
							StringUtils.trimAllWhitespace(profiles)));
				}
			}
			return this.defaultProfiles;
		}
	}
    
    //返回default
    protected Set<String> getReservedDefaultProfiles() {
		return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
	}
	
    //对应配置项spring.profiles.active
	protected String doGetActiveProfilesProperty() {
		return getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
	}

    //对应配置项spring.profiles.default
    protected String doGetDefaultProfilesProperty() {
		return getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
	}
	
    //委托propertyResolver进行配置解析
	public String getProperty(String key) {
		return this.propertyResolver.getProperty(key);
	}

    public void setDefaultProfiles(String... profiles) {
		Assert.notNull(profiles, "Profile array must not be null");
		synchronized (this.defaultProfiles) {
			this.defaultProfiles.clear();
			for (String profile : profiles) {
				validateProfile(profile);
				this.defaultProfiles.add(profile);
			}
		}
	}
	
	。。。
}

通常来说,我们使用profiles机制的方式是指定配置项spring.profiles.active、并且在某些类上打上@Profile注解,根据前面的介绍,我们知道了Spring在装载这种类的beanDefinition时,会判断指定profile是否为activeProfiles,用于决定beanDefinition是否满足加载条件,让我们大致看下相关源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
	String[] value();
}

@Profile派生于@Conditional,后者在使用时,需要指定一个Condition实现类,Condition接口只有一个boolean matches(...)方法,向只有matches方法返回true时,spring才会装载当前类。ProfileCondition源码如下:

class ProfileCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
		if (attrs != null) {
			for (Object value : attrs.get("value")) {
                //调用Environment#acceptsProfiles方法判断是否为activeProfiles
				if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
					return true;
				}
			}
			return false;
		}
		return true;
	}

}

需要注意的是@Conditional不仅可以注解到@Component类上,还可以注解到@Configuration类和@Bean上,如果@Configuration类上@Conditional条件不满足,则类上的@Import、@ComponentScan注解、以及类中的@Bean方法都会被忽略。如果@Bean方法上的@Conditional条件不满足,则方法被忽略

好了,Environment的功能特性就说到这里了,那么如果我们想在Spring中获取Environment实例,做一些定制化的配置管理工作,有哪些方式呢?

  • 通过ApplicationContext获取,在ApplicationContext篇中,我们说过ApplicationContext有一个父接口EnvironmentCapable,其中只有一个getEnvironment()方法,返回一个Environment对象,文章开头列出的demo中,就是利用了这种方式
  • @Autowired Environment environment的方式注入一个Environment实例
  • 实现EnvironmentAware接口的setEnvironment(Environment environment)方法,被回调时传入Environment实例

时机非常重要:需要注意的是,如果你想做的定制化配置管理工作,要在容器开始创建普通的bean之前进行,那么注解的方式就行不通了(此时容器已经开始处理bean了),只能通过接口回调的方式,但是仅仅实现aware是不够的,因为aware接口是bean生命周期中的步骤,此时bean也已开始处理了,所以还要加上BeanFactoryPostProcessor接口,正如我们在BeanDefinition篇结尾处说明的,容器启动的早期就会回调BeanFactoryPostProcessor,此时Spring会为所有实现了BeanFactoryPostProcessor的类创建bean实例,这时当然会走一遍bean的生命周期(除了destory),这里面就包括aware接口的回调。我们通过一个demo看一下这种方式:

package com.example.spring;

import com.example.spring.component.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@ComponentScan(basePackages = "com.example.spring")
@Configuration
public class AnnotationApplicationContextDemo{

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(AnnotationApplicationContextDemo.class);
        applicationContext.refresh();

        FileService fileService=applicationContext.getBean(FileService.class);
        System.out.println(fileService.toString());
    }
}


@Component
public class MyEnvironmentAware implements EnvironmentAware, BeanFactoryPostProcessor {
    @Override
    public void setEnvironment(Environment environment) {
        ConfigurableEnvironment configurableEnvironment=(ConfigurableEnvironment)environment;
        Map<String,Object> map=new HashMap<>();
        map.put("file.server.protocol","ftp");
        map.put("file.server.ip","XX.XX.XXX");
        map.put("file.server.port",23);
        MapPropertySource myPropertySource=new MapPropertySource("myPropertySource",map);
        configurableEnvironment.getPropertySources().addLast(myPropertySource);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("123123");
    }
}

package com.example.spring.aware;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class MyEnvironmentAware implements EnvironmentAware, BeanFactoryPostProcessor {
    @Override
    public void setEnvironment(Environment environment) {
        ConfigurableEnvironment configurableEnvironment=(ConfigurableEnvironment)environment;
        Map<String,Object> map=new HashMap<>();
        map.put("file.server.protocol","ftp");
        map.put("file.server.ip","XX.XX.XXX");
        map.put("file.server.port",23);
        MapPropertySource myPropertySource=new MapPropertySource("myPropertySource",map);
        configurableEnvironment.getPropertySources().addLast(myPropertySource);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //do nothing
    }
}

package com.example.spring.component;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class FileService {

    @Value("${file.server.protocol}")
    private String protocol;

    @Value("${file.server.ip}")
    private String ip;

    @Value("${file.server.port}")
    private Integer port;

    @Override
    public String toString() {
        return "FileService{" +
                "protocol='" + protocol + '\'' +
                ", ip='" + ip + '\'' +
                ", port=" + port +
                '}';
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值