撸一撸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的含义
控制台输出如下:四个PropertySource分别对应jvm参数、系统环境变量、以及通过@PropertySources注解引入的两个properties文件@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("=========================="); }); } }
下面是相关组件的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 +
'}';
}
}