springIOC源码接口分析(九):Environment
阅读原文时间:2023年07月10日阅读:1

先贴一下接口继承关系图,spring容器启动的时候会初始化环境,所以此接口相关接口非常有必要进行了解:

Environment继承了该接口,PropertyResolver接口用于在properties文件,yml文件,xml文件,甚至是一些nosql等文件解析一系列的属性值,

以及解析字符串中的属性表达式的接口,如${foo}/abc,foo对应的属性值为123,解析后为123/abc。

接口方法定义:

public interface PropertyResolver {
/**
* 判断是够存在key对应的属性值
*/
boolean containsProperty(String key);

/\*\*  
 \* 获取key对应的属性值  
 \*/  
@Nullable  
String getProperty(String key);

/\*\*  
 \* 获取key对应的属性值,如果没有则返回传递的默认值  
 \*/  
String getProperty(String key, String defaultValue);

/\*\*  
 \* 获取key对应的属性值,并解析为对应类型:  
 \* 例如:  
 \*    Environment environment = applicationContext.getEnvironment();  
 \*    Integer age = environment.getProperty("ages", Integer.class,5);  
 \*/  
@Nullable  
<T> T getProperty(String key, Class<T> targetType);

/\*\*  
 \* 如果没有返回一个默认的对应类型值  
 \*/  
<T> T getProperty(String key, Class<T> targetType, T defaultValue);

/\*\*  
 \* 获取不到会抛异常  
 \*/  
String getRequiredProperty(String key) throws IllegalStateException;

/\*\*  
 \* 获取不到或者获取到类型无法解析会抛异常  
 \*/  
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

/\*\*  
 \* 根据传入的字符串将其中${}解析成对应key的属性值,比如:  
 \*   ${age}/test   解析为  24/test 并返回  
 \*/  
String resolvePlaceholders(String text);

/\*\*  
 \* 无法解析会报错  
 \*/  
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;  

}

1 ConfigurablePropertyResolver

该接口定义了如何对组件本身进行配置。如:刚刚提到获取value时可以指定任意类型,这依赖于ConversionService进行类型转换,当前接口就提供了对ConversionService的设置和获取。

另外,可以配置属性占位符的格式,包括:占位符前缀(默认为"${")、占位符后缀(默认为"}")、占位符值分隔符(默认为":",用于分隔propertyName和defaultValue)。

组件还可以设置哪些属性是必须存在的,还可以校验必须存在的属性是否真的存在(不存在的话会抛出异常)。具体接口如下:

public interface ConfigurablePropertyResolver extends PropertyResolver {
/**
* 获取ConversionService,主要用于类型转换
*/
ConfigurableConversionService getConversionService();

/\*\*  
 \* 设置获取ConversionService  
 \*/  
void setConversionService(ConfigurableConversionService conversionService);

/\*\*  
 \* 设置占位符前缀格式(默认为"${")、占位符后缀(默认为"}")  
 \*/  
void setPlaceholderPrefix(String placeholderPrefix);

/\*\*  
 \* 设置占位符后缀格式(默认为"}")  
 \*/  
void setPlaceholderSuffix(String placeholderSuffix);

/\*\*  
 \* 设置占位符分隔符有(默认为":") 用于分隔propertyName和defaultValue 比如 ${age:25}  
 \*/  
void setValueSeparator(@Nullable String valueSeparator);

/\*\*  
 \* 设置遇到无法解析的占位符时是否引发异常,即是否忽略  
 \*/  
void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);

/\*\*  
 \* 设置哪些属性必须存在  
 \*/  
void setRequiredProperties(String... requiredProperties);

/\*\*  
 \* 校验设置了必须存在的属性是否真的存在,只要有一个不存在会抛出异常  
 \*/  
void validateRequiredProperties() throws MissingRequiredPropertiesException;  

}

AbstractPropertyResolver

上述两个接口的抽象实现类。它实现了ConfigurablePropertyResolver接口的所有方法。

关于PropertyResolver接口方法,还有1个getPropertyAsRawString方法需要子类实现.

这个方法用于直接返回获取到的value值(不进行占位符替换),``一般的getProperty方法默认都会替换掉value值中的占位符后返回

PropertySourcesPropertyResolver

该类是体系中唯一的完整实现类。它以PropertySources属性源集合(内部持有属性源列表List)为属性值的来源,

按序遍历每个PropertySource,获取到一个非null的属性值则返回,实现了getPropertyAsRawString方法,并继承了父抽象类已经实现好的方法

PropertySources接口定义是保存一个或者多个PropertySource对象,继承自Iterable>,定义了多个PropertySource的迭代接口。

只有一个默认的实现类MutablePropertySources,通过CopyOnWriteArrayList保存多个PropertySource对象,并增加了维护PropertySource在列表中的具体位置的接口

接口定义了三个方法:

public interface PropertySources extends Iterable<PropertySource<?>> {

    /\*\*  
     \* 获取泛型PropertySource的stream流  
     \*/  
    default Stream<PropertySource<?>> stream() {  
        return StreamSupport.stream(spliterator(), false);  
    }

    /\*\*  
     \* 是否含有给定的key  
     \*/  
    boolean contains(String name);

    /\*\*  
     \* 根据给定key获取PropertySource  
     \*/  
    @Nullable  
    PropertySource<?> get(String name);  
}

其只有一个实现类MutablePropertySources:

public class MutablePropertySources implements PropertySources {
//PropertySource对象集合
private final List> propertySourceList = new CopyOnWriteArrayList<>();

@Override  
@Nullable  
public PropertySource<?> get(String name) {  
    // 此处用indexOf迭代,因为PropertySource重写了equals方法,使得只比较key就可以,named返回一个指定  
    //key的空实现,value默认是new Object,来进行查找索引  
    int index = this.propertySourceList.indexOf(PropertySource.named(name));  
    return (index != -1 ? this.propertySourceList.get(index) : null);  
}  

  //……一些迭代方法的实现
}

PropertySource是抽象类,表示一个键值对,用于存放(key-value)键值对的抽象,value可以是任意类型,注意该类重写了equals()和hashCode()方法,

提供了一个named(String name)方法用于构造基于name的PropertySource的空实现,从而便于PropertySource 集合中查找指定属性名的PropertySource:

public abstract class PropertySource {
protected final Log logger = LogFactory.getLog(getClass());
protected final String name; // key
protected final T source; // value
//构造器
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());
}

/\*\*  
 \*  获取key  
 \*/  
public String getName() {  
    return this.name;  
}

/\*\*  
 \* 获取value  
 \*/  
public T getSource() {  
    return this.source;  
}

/\*\*  
 \* 是否包含给定key的属性  
 \*/  
public boolean containsProperty(String name) {  
    return (getProperty(name) != null);  
}

/\*\*  
 \* 获取key对应的value  
 \*/  
@Nullable  
public abstract Object getProperty(String name);

/\*\*  
 \* 重写equals,如果两个属性的key,即name相等,也返回true  
 \*  ObjectUtils.nullSafeEquals:传入的对象都不等于null,则返回equals方法结果  
 \*  此处如果name字符串都不等于null,且相等则返回true  
 \*/  
@Override  
public boolean equals(Object other) {  
    return (this == other || (other instanceof org.springframework.core.env.PropertySource &&  
            ObjectUtils.nullSafeEquals(this.name, ((org.springframework.core.env.PropertySource<?>) other).name)));  
}

@Override  
public int hashCode() {  
    return ObjectUtils.nullSafeHashCode(this.name);  
}/\*\*  
 \* 根据给定的name构造一个子类ComparisonPropertySource的实现,value默认是一个new Object()  
 \*/  
public static org.springframework.core.env.PropertySource<?> named(String name) {  
    return new org.springframework.core.env.PropertySource.ComparisonPropertySource(name);  
}

/\*\*  
 \* value是new Object()的子类  
 \*/  
public static class StubPropertySource extends org.springframework.core.env.PropertySource<Object> {  
    public StubPropertySource(String name) {  
        super(name, new Object());  
    }  
    /\*\*  
     \* 子类对象如果获取属性,永远返回null  
     \*/  
    @Override  
    @Nullable  
    public String getProperty(String name) {  
        return null;  
    }  
}

/\*\*  
 \* 继承StubPropertySource的子类,调用其他方法都抛异常  
 \*/  
static class ComparisonPropertySource extends org.springframework.core.env.PropertySource.StubPropertySource {  
    private static final String USAGE\_ERROR =  
            "ComparisonPropertySource instances are for use with collection comparison only";  
    public ComparisonPropertySource(String name) {  
        super(name);  
    }  
    @Override  
    public Object getSource() {  
        throw new UnsupportedOperationException(USAGE\_ERROR);  
    }  
    @Override  
    public boolean containsProperty(String name) {  
        throw new UnsupportedOperationException(USAGE\_ERROR);  
    }  
    @Override  
    @Nullable  
    public String getProperty(String name) {  
        throw new UnsupportedOperationException(USAGE\_ERROR);  
    }  
}  

}

PropertySource还有许多实现类,增加获取各种属性的方法,比如获取系统属性,jvm启动属性,map类型属性等等,这里就不写了,可查看源码

Environment实现扩展了PropertyResolver接口,定义了代表当前应用程序所处的运行环境的方法,接口方法定义如下:

/\*\*  
 \* 返回激活的profile配置文件环境后缀,比如 dev,test  
 \*/  
String\[\] getActiveProfiles();

/\*\*  
 \* 返回默认的profile配置环境,默认是default  
 \*/  
String\[\] getDefaultProfiles();

@Deprecated  
boolean acceptsProfiles(String... profiles);

/\*\*  
 \* 判断给定环境是否存在,有一个存在返回true,比如:environment.acceptsProfiles(Profiles.of("dev","test"))  
 \*/  
boolean acceptsProfiles(Profiles profiles);

该接口继承关系如图:

1 ConfigurableEnvironment

ConfigurableEnvironment接口实现了Environment, ConfigurablePropertyResolver,主要是对Environment接口进行扩展,

Environment接口中有获取激活的默认的配置环境等方法,在此接口中添加了设置这些环境等方法,并增加了

getPropertySources方法来获取PropertySources属性,以便于获取属性集合,还添加了一些获取系统属性,合并(merger)属性集合等方法

此接口方法定义如下:

/**
* 根据环境后缀设置激活的profile文件
*/
void setActiveProfiles(String… profiles);

/\*\*  
 \* 添加激活的配置文件  
 \*/  
void addActiveProfile(String profile);

/\*\*  
 \* 如果没有其他文件就设置默认的激活配置文件  
 \*/  
void setDefaultProfiles(String... profiles);

/\*\*  
 \* 获取PropertySources接口,用来操作PropertySource属性集合  
 \*/  
MutablePropertySources getPropertySources();

/\*\*  
 \* 获取系统环境设置,获取这个System类的getProperties()方法,如果获取不到就使用一个有效key再次尝试获取,下面方法也是一样的

 \*/   
 Map<String, Object> getSystemProperties(); 

 /\*\* \* 获取系统环境 \*/   
 Map<String, Object> getSystemEnvironment(); 

 /\*\* \* 将给定父环境的活动配置文件、默认配置文件合并到此(子)环境各自的集合的属性源。 \* 如果有相同名称的,丢弃父环境的使用当前子环境的 \*/   
 void merge(ConfigurableEnvironment parent);

2 AbstractEnvironment

AbstractEnvironment抽象类实现了ConfigurableEnvironment接口,并且实现了接口定义的所有方法,并且在构造器里定义了一个初始化的protected

方法,空实现,以供子类进行各自初始化:

构造器:

/\*\*  
 \* Create a new {@code Environment} instance, calling back to  
 \* {@link #customizePropertySources(MutablePropertySources)} during construction to  
 \* allow subclasses to contribute or manipulate {@link PropertySource} instances as  
 \* appropriate.  
 \* @see #customizePropertySources(MutablePropertySources)  
 \*/  
public AbstractEnvironment() {  
    customizePropertySources(this.propertySources);  
}

初始化方法:

protected void customizePropertySources(MutablePropertySources propertySources) {  
}

3 StandardEnvironment

StandardEnvironment类继承了上面的抽象类AbstractEnvironment,标准环境,普通Java应用时使用,会自动注册System.getProperties() 和 System.getenv()到环境,

覆写了父类定义的初始化方法进行系统属性的初始化,MapPropertySource和SystemEnvironmentPropertySource都是属性类PropertySource的子类实现,都是对资源的抽象:

@Override  
protected void customizePropertySources(MutablePropertySources propertySources) {  
    propertySources.addLast(new MapPropertySource(SYSTEM\_PROPERTIES\_PROPERTY\_SOURCE\_NAME, getSystemProperties()));  
    propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM\_ENVIRONMENT\_PROPERTY\_SOURCE\_NAME, getSystemEnvironment()));  
}

4 ConfigurableWebEnvironment

此接口实现了ConfigurableEnvironment接口,只定义了一个方法,用来对web环境的属性进行初始化的,定义的环境在web项目启动时会 进行调用:

public interface ConfigurableWebEnvironment extends ConfigurableEnvironment {

void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig);  

}

比如springboot项目启动web环境调用地方:

    @Override  
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {  
        ConfigurableEnvironment environment = event.getEnvironment();  
        if (environment instanceof ConfigurableWebEnvironment) { // 判断是否是web环境的接口  
            ((ConfigurableWebEnvironment) environment)  
                    .initPropertySources(this.servletContext, null); //调用初始化方法  
        }  
    }

5 StandardServletEnvironment

StandardServletEnvironment继承了StandardEnvironment类,还实现了ConfigurableWebEnvironment接口定义的初始化方法用来初始化注册ServletConfig(DispatcherServlet)、ServletContext

标准的servlet环境,在web项目环境时使用,初始化时候除了调用父类初始化方法进行系统属性的初始化之外还会自动注册JNDI实例到环境:

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
//调用父类的初始化方法
super.customizePropertySources(propertySources);
}
//此方法在web项目启动的时候会调用
@Override
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}

还有一个MockEnvironment实现类也继承了抽象类,是测试的时候模拟使用的

6 自定义环境配置

因为上面抽象类构造器里面提供了一个空实现的初始化方法,我们可以看到初始化属性都是在子类里面完成的,

所以spring提供了这个接口来方方便我们进行自定义环境配置,只需要实现抽象父类,然后在上面提到的初始化方法里面进行自定义设置即可,spring是真心强大!

ApplicationContext等容器虽然没有直接继承Environment接口,但是继承了一个EnvironmentCapable接口:

该接口只定义了一个方法,即获取容器方法:

从而在容器实现类中通过该方法返回Environment对象(实现类中有引用属性),来使用此接口功能:

开发中使用:

public static void main(String\[\] args) {  
    ConfigurableApplicationContext application = SpringApplication.run(Application.class, args);  
    ConfigurableEnvironment environment = application.getEnvironment();  
    Integer age = environment.getRequiredProperty("age",Integer.class);  
    String str = environment.resolvePlaceholders("/${age}/hou");  
    System.out.println(str);  
    System.out.println(Arrays.toString(environment.getActiveProfiles()));  
    System.out.println(Arrays.toString(environment.getDefaultProfiles()));  
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章