跟着小马哥学系列之 Spring IoC(进阶篇:Environment)

学成路更宽,吊打面试官。 ——小马哥

简介

大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间《小马哥讲Spring核心编程思想》基础上形成的个人一些总结。希望能帮助各位小伙伴, 祝小伙伴早日学有所成。 分为基础篇、进阶篇、源码篇。玩游戏看颜色,学技术看版本,本系列以 Spring 5.2.0.RELEASE 版本为基础进行介绍。 祝小伙伴早日学有所成。

0、类图

跟着小马哥学系列之 Spring IoC(进阶篇:Environment)_第1张图片

从类的继承关系可以看出,Environment 接口继承 PropertyResolver 接口具有属性相关操作功能。ConfigurableEnvironment 具有操作 Profiles 的能力,再往后面就是对 Environment 进行分类:标准环境(非 Web)、传统 Web 环境、Reactive Web 环境。

一、理解 Spring Environment 抽象

Environment 主要有2方面功能:profiles 和 properties

  • profile 是对注册的 Bean 定义进行命名的逻辑分组,比如日常开发环境、线上环境
  • properties 是对属性的占位符处理和类型转换,这里的资源是指广义上的资源不单单是指 Properties,比如 JVM 系统属性,系统环境参数,JNDI,servlet 上下文参数等等。

统一的 Spring 配置属性管理

Spring Framework 3.1 开始引入 Environment 抽象,它统一 Spring 配置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer,而且还支持更丰富的配置源属性(PropertySource)。

条件化 Sprng Bean 装配

通过 Environment Profiles 信息,帮助 Spring 容器提供条件化地装配 Bean。比如开发环境与线上环境的数据源不相同,到时候切换一个 Profile 就会自动切换。

Environment 子接口/类简介

Environment

方法简介
  • getActiveProfiles():获取所有有效的(激活的)profile 列表
  • getDefaultProfiels():获取所有默认的 (没有指定 profile 时)profile 列表
  • acceptsProfiles():给定的 profile 是否在 getActiveProfiles() 或者 getDefaultProfiels() 列表中

ConfigurableEnvironment

  • 大多数(不是全部)Environment 类型都要实现的配置接口。提供用于设置 active 和 default profiles 以及操作底层属性源的工具。允许客户端通过 ConfigurablePropertyResolver 超接口设置和验证所需的属性,定制 ConversionService 等。
  • 操纵属性来源
  1. 可以移除、重新排序或替换属性来源;可以使用 getPropertySources() 返回的 MutablePropertySources 实例添加其他属性源。下面的例子是针对 ConfigurableEnvironmentStandardEnvironment 实现的,但是通常适用于任何实现,尽管特定的默认属性源可能有所不同。
  2. 当 ApplicationContext 使用一个 Environment 时,在调用上下文的 refresh() 方法之前执行任何这样的 PropertySource 操作是很重要的。这确保了在容器引导过程中所有属性源都是可用的,包括属性占位符配置器的使用。
public class EnvironmentDemo {
    public static void main(String[] args) {
        ConfigurableEnvironment environment = new StandardEnvironment();
        MutablePropertySources propertySources = environment.getPropertySources();
        Map<String, Object> firstMap = new HashMap<>();
        firstMap.put("xyz", "firstMap");
        Map<String, Object> secondMap = new HashMap<>();
        secondMap.put("xyz", "secondMap");
        // 添加具有最高搜索优先级的新属性源
        propertySources.addFirst(new MapPropertySource("firstMap", firstMap));
        propertySources.addLast(new MapPropertySource("secondMap", secondMap));
        // 这里获取 xyz 的属性值是 firstMap
        System.out.println(environment.getProperty("xyz"));
        // 删除默认的系统属性属性源
        propertySources = environment.getPropertySources();
        propertySources.remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
    }
}
方法简介
  • setActiveProfiles():设置当前 Environment 中激活的 profiles 集
  • addActiveProfile():向当前激活的 profiles 集中添加 profile
  • setDetaultProfiles():设置默认的 profiles 集
  • getPropertySources():获取当前 Environment 中可变的 PropertySource 集,允许在解析此Environment 对象的属性时搜索 PropertySource 对象集。允许操作 PropertySource 对象的集合,当解析属性针对这个环境对象时,各种 Mutablepropertysource 方法,如 addFirst, addLast, addBefore 和addAfter 允许对属性源排序进行细粒度控制。这很有用,例如,可以确保某些用户定义的属性源的搜索优先级高于默认属性源(如系统属性集或系统环境变量集)。
  • getSystemProperties:如果当前 SecurityManage r允许,则返回 System.getProperties()的值,否则返回一个 Map实现,该实现将尝试使用 System.getProperty(String) 调用访问单个键。请注意,大多数Environment 实现将把这个系统属性映射作为要搜索的默认 PropertySource。因此,建议不要直接使用此方法,除非明确打算绕过其他属性源。在返回的 Map 上调用 Map.get(Object) 永远不会抛出IllegalAccessException;在 SecurityManager 禁止访问某个属性的情况下,将返回 null,并发出一个info 级别的日志消息,记录这个异常。
  • getSystemEnvironment:如果当前 SecurityManager 允许,则返回 System.getenv() 的值,否则返回一个 Map 实现,该实现将尝试使用 System.getenv(String) 调用访问单个键。请注意,大多数 Environment 实现将把这个系统环境映射作为要搜索的默认 PropertySource 包括在内。因此,建议不要直接使用此方法,除非明确打算绕过其他属性源。在返回的Map上调用 Map.get(Object) 永远不会抛出IllegalAccessException,在 SecurityManager 禁止访问某个属性的情况下,将返回 null,并发出一个info级别的日志消息,记录这个异常。
  • merge:将给定父环境的激活的 profies、默认 profies 和属性源附加到这个(子)环境各自的集合中。
    对于任何在父实例和子实例中都存在同名的 PropertySource 实例,子实例将被保留,父实例将被丢弃。这样做的效果是允许子对象覆盖属性源,并避免通过公共属性源类型(例如系统环境和系统属性)进行冗余搜索。激活和默认 profiles 名称也会被筛选,以避免混淆和冗余存储。父环境在任何情况下都保持不变。注意,在调用 merge 之后发生的对父环境的任何更改都不会反映在子环境中。因此,在调用 merge之前,应该注意配置父属性源和 profiles。

AbstractEnvironment

Environmen 实现的抽象基类。

  • 支持保留缺省配置文件默认名称的概念,并通过 ACTIVE_PROFILES_PROPERTY_NAME 和 DEFAULT_PROFILES_PROPERTY_NAME 属性指定激活配置文件和默认配置文件。
  • 具体的子类主要不同于它们默认添加的 PropertySource 对象。AbstractEnvironment 没有任何添加。子类应该通过受保护的 customizePropertySources(MutablePropertySources) 钩子方法提供属性源,而客户端应该使用 ConfigurableEnvironment.getPropertySources()MutablePropertySources API 进行定制
customizePropertySources() 方法详解

自定义当前环境在调用getProperty(String)和相关方法期间搜索的propertsource对象集。
鼓励子类重写此方法,通过 MutablePropertySources#addLast(PropertySource) 添加属性源,以便子类可以进一步的调用 super.customizePropertySources(),并具有可预测的结果

public class Level1Environment extends AbstractEnvironment {
	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
	    super.customizePropertySources(propertySources); 
	    propertySources.addLast(new PropertySourceA(...));
	    propertySources.addLast(new PropertySourceB(...));
	}
}

public class Level2Environment extends Level1Environment {
	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		// 通过父类添加所有
	    super.customizePropertySources(propertySources); 
	    propertySources.addLast(new PropertySourceC(...));
	    propertySources.addLast(new PropertySourceD(...));
	}
}


// 在这种安排中,属性将按照源 A、B、C、D 的顺序进行解析。也就是说,属性源 A 优先于属性源 D。 
// 如果 Level2Environment 子类希望赋予属性源 C 和 D 比 A 和 B 更高的优先级,它可以简单
// 地调用 super.customizePropertySources 在添加它自己的子类之后,而不是在添加之前调用
public class Level2Environment extends Level1Environment {
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new PropertySourceC(...));
        propertySources.addLast(new PropertySourceD(...));
        super.customizePropertySources(propertySources);
    }
}
  • 通过 MutablePropertySources 类提供的方法可以精确排列创建所需的属性源。
    基本实现不注册任何属性源。

  • 注意,任何 ConfigurableEnvironment 的客户端都可以通过 getPropertySources() 访问器进一步定制属性源,通常是在 ApplicationContextInitializer 中。例如:

     ConfigurableEnvironment env = new StandardEnvironment();
     env.getPropertySources().addLast(new PropertySourceX(...));
    
    

StandardEnvironment

  • 适用于标准(即非web)应用程序的环境实现。
  • 除了 ConfigurableEnvironment 的常用功能(如属性解析和与 profile 相关的操作)之外,该实现还配置了两个默认属性源,它们将按照以下顺序进行搜索:
  1. 系统属性(AbstractEnvironment#getSystemProperties())
  2. 系统环境变量(AbstractEnvironment#getSystemEnvironment())
    也就是说,如果 key = “xyz” 既出现在 JVM 系统属性中,也出现在当前进程的环境变量集中,那么系统属性中的 key = “xyz” 的值将从调用 environment.getproperty(“xyz”) 返回。这个顺序是默认选择的,因为系统属性是针对每个 JVM 的,而环境变量在给定系统上的许多 JVM 之间可能是相同的。给予系统属性优先级允许在每个 JVM 的基础上重写环境变量。
  • 可以删除、重新排序或替换这些默认属性源;可以使用 MutablePropertySources 实例添加其他属性源
public class StandardEnvironment extends AbstractEnvironment {

	/** 系统环境变量属性源名称 */
	public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

	/** JVM 系统属性属性源名称 */
	public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";


	/**
	 *  自定义适合于任何标准 Java 环境的属性源集
	 */
	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(
				new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		propertySources.addLast(
				new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}

}

ConfigurableReactiveWebEnvironment

,特殊化的 ConfigurableEnvironment 标记接口,表示响应式应用程序上下文 。

ConfigurableWebEnvironment

特殊化的 ConfigurableEnvironment ,允许在 ServletContext 和(可选) ServletConfig 可用的最早时刻初始化与 servlet 相关的 PropertySource 对象。

方法简介

initPropertySources():使用给定的参数将任何充当占位符的 StubPropertySource 实例替换为真实的servlet context/config 属性源。

StandardServletEnvironment

  • 基于 servle t的 web 应用程序使用的环境实现。所有与 web 相关(基于 servlet)的 ApplicationContext 类默认初始化一个实例。
  • 分配 ServletConfig、ServletContext 和基于JNDI 的 Propertsource 实例。详细信息请参见 customizePropertySources 方法。

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {

	/** ServletContext  初始化参数属性源名称 */
	public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";

	/** ServletConfig  初始化参数属性源名称  */
	public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";

	/** JNDI 属性源名称 */
	public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";


	/**
	 * 使用超类提供的属性和适用于基于 servlet 的标准环境的属性来定制属性源集
	 * servletConfigInitParams 中的属性将优先于 servletContextInitParams 中的属性,
	 * 而上述任何一个中的属性都优先于 jndiProperties 中的属性。
	 * 以上任何一个属性都将优先于StandardEnvironment超类提供的系统属性和环境变量。
	 * 在这个阶段,与 servlet 相关的属性源作为存根添加,一旦实际的 ServletContext 对象可用,
	 * 就会被完全初始化。
	 */
	@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);
	}

	@Override
	public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
		// 初始化 servlet 属性替换 customizePropertySources() 方法里面的 StubPropertySource 对象
		WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
	}

}

StandardReactiveWebEnvironment

由响应式 web 应用程序使用的环境实现。所有与 web 相关的(基于响应式的)ApplicationContext 类默认初始化一个实例。

二、Spring Environment 接口使用场景

  1. 用于属性占位符处理
  2. 用于转换 Spring 配置属性类型
  3. 用于存储 Spring 配置属性源(PropertySource)
  4. 用于 Profiles 状态的维护

三、Environment 占位符处理

Spring 3.1 前占位符处理

  • 组件:org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
  • 接口:org.springframework.util.StringValueResolver

Spring 3.1+ 占位符处理

  • 组件:org.springframework.context.support.PropertySourcesPlaceholderConfigurer
  • 接口:org.springframework.beans.factory.config.EmbeddedValueResolver

类图

跟着小马哥学系列之 Spring IoC(进阶篇:Environment)_第2张图片
从类图上看 PropertyPlaceholderConfigurerPropertySourcesPlaceholderConfigurer 都是PlaceholderConfigurereSupport 的子类。下面一一介绍这些接口/类的作用

PropertiesLoaderSupport

需要从一个或多个资源加载属性的 JavaBean 风格组件的基类。通过配置可覆盖已加载的属性,也支持本地属性。

  • 属性可想而知就是 Properties 文件,支持一个或者多个资源加载属性所以有 Properties[] 类型的字段;
  • 支持本地属性即有基于地址定位的资源 字段 Resource[] locations
  • 支持覆盖有个字段 localOverride 字段标示是不是可以属性覆盖;
  • 属性有编码的支持对应可选字段 fileEncoding
  • 还有存储能力对应字段 propertiesPersister

PropertyResourceConfigurer

允许从属性资源(即属性文件)配置单个 bean 属性值(通过 BeanFactoryPostProcessor)。对于覆盖在应用程序上下文中配置的 bean 属性的系统管理员定制配置文件非常有用。提供了两个具体的实现:

  • PropertyOverrideConfigurer 具有可覆盖的 beanName.property =value 样式(将属性文件中的值推送到 bean 定义中)
  • PropertyPlaceholderConfigurer 替换 ${...} 占位符(从属性文件中提取值到 bean 定义中)

实现接口

从类图上看 PropertyResourceConfigurer 实现了 BeanFactoryPostProcessorPriorityOrdered 接口;

  • BeanFactoryPostProcessor#postProcessBeanFactory 方法主要在应用程序上下文的标准初始化之后修改其内部 bean 工厂。所有 bean 定义都将被加载,但是还没有 bean 被实例化。这允许覆盖或添加属性,甚至对急于初始化的 bean 也是如此;
  • PriorityOrdered 接口表示顺序

PlaceholderConfigurerSupport

  • 解析 bean 定义属性值中的占位符的属性资源配置器的抽象基类。实现将值从属性文件或其他属性源拉入 bean 定义。默认占位符语法遵循Ant / Log4J / JSP EL风格:${...}

XML bean 定义示例:


<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"/>
   <property name="driverClassName" value="${driver}"/>
   <property name="url" value="jdbc:${dbname}"/>
</bean>

Properties 文件示例:

driver=com.mysql.jdbc.Driver
dbname=mysql:mydb
  • 带注释的bean定义可以使用@Value注释利用属性替换:
    @Value("${person.age}")
  • 实现检查 bean 引用中的简单属性值、lists、maps、props 和 bean 名称。此外,占位符值也可以交叉引用其他占位符,比如:
    rootPath = myrootdir
    子路径= ${rootPath}/子目录
  • 与 PropertyOverrideConfigurer 相比,这种类型的子类允许在 bean 定义中填充显式占位符。
  • 如果配置器不能解析占位符,将抛出 BeanDefinitionStoreException。如果要检查多个属性文件,请通过 locations 属性指定多个资源。您还可以定义多个配置,每个配置都有自己的占位符语法。如果占位符不能被解析,使用 ignoreunresolvableplaceholder 有意避免抛出异常。
  • 默认属性值可以通过 setProperties 为每个配置器实例全局定义,或者在逐个属性的基础上使用默认值分隔符,默认情况下是 :,可通过 setValueSeparator(String) 自定义。

使用默认值的XML属性示例:

<property name="url" value="jdbc:${dbname:defaultdb}"/>

PropertySourcesPlaceholderConfigurer

  • PlaceholderConfigurerSupport 的专门解析${…} bean 定义属性值中的占位符和 @Value 注解从当前 Spring 环境及其 PropertySources 集中 。
  • 这个类被设计为 PropertyPlaceholderConfigurer 的通用替代品。默认情况下,它用于支持属性占位符元素,以处理s pring context-3.1或更高版本的 XSD;而 spring-context 版本<= 3.0 默认为PropertyPlaceholderConfigurer ,以确保向后兼容。
  • 任何本地属性(例如那些通过 PropertiesLoaderSupport.setProperties(java.util.Properties), PropertiesLoaderSupport.setLocations(org.springframework.core.io.Resource…) 等添加的属性都被添加为 Propertsource。本地属性的搜索优先级基于 localOverride 属性的值,默认情况下为 false,这意味着在所有环境属性源之后最后搜索本地属性。

PropertyPlaceholderConfigurer

PlaceholderConfigurer 子类用于解析 ${...} 占位符针对本地属性或者系统属性和环境参数。PropertyPlaceholderConfigurer 依旧适用一些情况

  • Spring-context 模块不可用(例如,其中一个使用 Spring 的 BeanFactory API,而不是 ApplicationContext)。
  • 现有的配置使用了 systemPropertiesMode 或 systemPropertiesModeName 属性。我们鼓励用户不再使用这些设置,而是通过容器的 Environment 来配置属性源搜索顺序;但是,可以通过继续使用PropertyPlaceholderConfigurer 来维护功能的确切保存。

四、理解条件配置 Spring Profiles

通过属性 spring.profiles.active = 配置(org.springframework.core.env.AbstractEnvironment#doGetActiveProfiels)

Spring 3.1 条件配置

  • API:org.springframework.core.env.ConfigurableEnviroment

    • 修改:addActiveProfile(String)、setActiveProfile(String…) 和 setDefaultProfiles(String…)
    • 获取:getActiveProfiles() 和 getDefaultProfiles()
    • 匹配:acceptsProfiles(String) 和 acceptsProfiles(Profiles)
  • 注解:@org.springframework.context.annotation.Profile

五、Spirng 4 重构 @Profile

  • 基于 Spring 4 org.springframework.context.annotation.Condition 接口实现
    • org.springframework.context.annotation.ProfileCondition
  • 指示当一个或多个指定的 profile 处于活动状态时,组件有资格注册。
  • profile 用于命名逻辑分组,可能以编程方式被激活。通过 ConfigurableEnvironment.setActiveProfiles() 或声明方式通过设置 spring.profiles.active。活动属性作为一个 JVM 系统属性,作为一个环境变量,或者作为 web 应用程序的 web.xml 中的 Servlet 上下文参数。profile 也可以通过 @ActiveProfiles 注解在集成测试中以声明方式激活。
  • @Profile 注解可以以以下任何一种方式使用:
  1. 作为类型级注释可以标注在任何直接或间接用 @Component(包括@Configuration类) 注解的类
  2. 作为元注释,用于组合自定义原型(模式)注释
  3. 作为任何 @Bean 方法上的方法级注释
  • 如果 @Configuration 类被标记为 @Profile,那么所有与该类关联的 @Bean 方法和 @Import 注释都将被忽略,除非一个或多个指定的 profile 处于活动状态。一个配置文件字符串可以包含一个简单的配置文件名称(例如“p1”)或一个配置文件表达式。一个 profile 表达式允许表达更复杂的 profile 逻辑,例如“p1 & p2”。关于支持格式的更多细节,请参见 Profiles.of(String…) 方法。
  • 这类似于 Spring XML 中的行为:如果提供了 beans 元素的 profile 属性,例如 , bean 元素将不会被解析,除非至少激活了 profile p1 或 p2。同样地,如果 @Component 或 @Configuration 类被标记为 @Profile({“p1”, “p2”}),则除非至少激活了profile p1 或 p2,否则该类将不会被注册或处理。
  • 如果给定的配置文件以 NOT 操作符(!)为前缀,那么如果配置文件未激活,则带注释的组件将被注册——例如,给定@Profile({“p1”, “!p2”}),如果配置文件 p1 激活或 p2 未激活,则注册将发生。
  • 如果省略 @Profile 注释,无论哪个配置文件(如果有的话)处于活动状态,都将进行注册。
  • 注意:对于 @Bean 方法上的 @Profile,可能会应用一个特殊的场景:在具有相同 Java 方法名的重载 @Bean 方法的情况下(类似于构造函数重载),需要在所有重载的方法上一致声明 @Profile 条件。如果条件不一致,则重载方法中只有第一个声明的条件会起作用。因此,@Profile 不能用于选择具有特定参数签名的重载方法;同一个 bean 的所有工厂方法之间的解析在创建时遵循Spring的构造函数解析算法。如果您想用不同的配置文件条件定义替代 bean,则使用指向相同 bean 名称的不同 Java 方法名;参见 @Configuration 的 javadoc 中的 ProfileDatabaseConfig。当通过 XML 定义 Spring bean 时,可以使用 元素的 profile 属性。有关详细信息,请参阅spring-beans XSD(版本3.1或更高版本)中的文档。

解析过程:

  • ConfigurationClassPostProcessor#processConfigBeanDefinitions
    • ConfigurationClassParser#parse
      • ConfigurationClassParser#processConfigurationClass
        • ConditionEvaluator#shouldSkip
          • ProfileCondition#matches

六、依赖注入 Environment

直接注入

  1. 通过 EnvironmentAware 接口回调
  2. 通过 @Autowired/@Resource 注解注入 Environment

间接注入

  1. 通过 ApplicationContextAware 接口回调
  2. 通过 @autowired/@Resource 注解注入 ApplicationContext

七、依赖查找 Environment

直接查找

  • 通过 org.springframework.context.ConfigurableApplicationContext#ENVIRONMENT_BEAN_NAME

间接查找

  • 通过 org.springframework.context.ConfigurableApplicationContext#getEnvironment

八、依赖注入 @Value

  • 通过注入 @Value
    • 实现:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
  • 解析过程:
    • DefaultListableBeanFactory#doResolveDependency
      • ContextAnnotationAutowireCandidateResolver #getSuggestedValue
        • QualifierAnnotationAutowireCandidateResolver#findValue

九、 Spring 类型转换在 Environment 中的运用

  • Environment 底层实现
    • 底层实现:org.springframework.core.env.PropertySourcesPropertyResolver
      • 核心方法:convertValueIfNecessary(Object, Class)
    • 底层服务:org.springframework.core.convert.ConversionService
      • 默认实现:org.springframework.core.convert.support.DefaultConversionService

属性解析

跟着小马哥学系列之 Spring IoC(进阶篇:Environment)_第3张图片

PropertyResolver

解析属性(任何底层源)的接口

  • containsProperty():属性是否存在

  • getProperty()/getRequiredProperty():获取属性值/获取属性值(必须存在,否则抛出 IllegalStateException 异常)

  • resolvePlaceholders():解析${...} 占位符

ConfigurablePropertyResolver

大多数(不是所有)PropertyResolver 类型实现的配置接口。提供用于访问和自定义在将属性值从一种类型转换为另一种类型时使用的 ConversionService 的工具。

方法简介
  • getConversionService():获取 ConversioinService
  • setConversionService():设置 ConversioinService
  • setPlaceholderPrefix()/setPlaceholderSuffix():设置占位符前缀/后缀
  • setValueSeparator():设置属性值分隔符
  • setIgnoreUnresolvableNestedPlaceholders():设置当遇到给定属性的值中嵌套的不可解析占位符时是否抛出异常。false 值表示严格解析,即抛出异常。true 值表示不可解析的嵌套占位符应该在其无法解析的 ${…} 形式。getproperty (String) 及其变体的实现必须检查这里设置的值,以确定当属性值包含不可解析的占位符时的正确行为
    -validateRequiredProperties():验证由 setRequiredProperties(java.lang.String…) 指定的每个属性都存在并解析为一个非空值
  • setRequiredProperties():指定必须存在哪些属性,以便由 validateRequiredProperties() 进行验证。

AbstractPropertyResolver

抽象基类,用于解析针对任何底层源的属性。实现一些通用功能,获取属性调用 getProperty() 方法,解析占位符让 PropertyPlaceholderHelper 处理,类型转换让 ConfigurableConversionService 来完成。

PropertySourcesPropertyResolver

PropertyResolver 实现,它根据一组 PropertSource 解析属性值(PropertSource #getProperty)。

public class PropertySourcesPropertyResolverDemo {
   public static void main(String[] args) {
       MutablePropertySources sources = new MutablePropertySources();
       Properties properties = new Properties();
       properties.setProperty("zhangsan", "张三");
       properties.setProperty("price", "10");
       sources.addFirst(new PropertiesPropertySource("PropertiesPropertySource", properties));
       PropertySourcesPropertyResolver propertyResolver = new PropertySourcesPropertyResolver(sources);
       // 带有默认值的占位符
       System.out.println(propertyResolver.resolvePlaceholders("${name:文海}"));
       // 获取 zhangsan 属性值
       System.out.println(propertyResolver.getProperty("zhangsan"));
       // 获取 price  属性值是 Integer 类型
       System.out.println(propertyResolver.getProperty("price", Integer.class));
       // 获取 wangwu  属性值指定默认值
       System.out.println(propertyResolver.getProperty("wangwu", "王五"));
       // 获取 wangwu  属性值没有会报错
       System.out.println(propertyResolver.getRequiredProperty("wangwu"));
   }
}

类型转换

跟着小马哥学系列之 Spring IoC(进阶篇:Environment)_第4张图片

ConversionService

从 Spring 3.0 开始提供新的用于类型转换的服务接口。这是转换系统的入口点。调用 convert(Object, Class) 方法来执行类型转换(具有线程安全特性)。

方法简介
  • canConvert():源类型能不能转换为目标类型,如果能转换才调用 convert() 方法进行转换。
  • convert():将给定对象转换指定类型

ConverterRegistry

向类型转换系统注册转换器

方法简介
  • addConverter():注册普通、泛型转换器,还提供了重载方法用于指定类型间的转换器注册
  • removeConvertible():删除指定类型间转换器
  • addConverterFactory():注册转换器工厂,通过转换器工厂可以获取转换器进行类型转换

ConfigurableConversionService

大多数(不全是) ConversionService 类型将实现的配置接口。整合由 ConversionService 公开的只读操作和 ConverterRegistry 的可变操作,以便方便地通过它们特别添加和删除转换器。后者在应用程序上下文引导代码中的 ConfigurableEnvironment 实例时特别有用。

GenericConversionService

适合在大多数环境中使用的基本 Convertionservice 实现。通过 ConfigurableConversionService 接口间接实现 ConverterRegistry 作为注册API。主要是处理泛型转换。后续会出一篇 Spring 类型转换的文章,敬请期待。

DefaultConversionService

专门为 GenericConversionService 配置适合大多数环境默认转换器。全局唯一(单例)。暴露静态 addDefaultConverters(ConverterRegistry) 实用方法,以便针对任何 ConverterRegistry 实例特别使用。内部提供了许多默认转换器,比如数组与集合相互转换、String 类型

public class DefaultConversionServiceDemo {
    public static void main(String[] args) {
        DefaultConversionService conversionService = new DefaultConversionService();
        if (conversionService.canConvert(Boolean.class, String.class)) {
            System.out.println("Boolean 对象能转行成 String 类型");
            System.out.printf("Boolean 对象值:%s,转行成 String 类型之后的值:%S\n", Boolean.TRUE, conversionService.convert(Boolean.TRUE, String.class));
        } else {
            System.out.println("Boolean 对象不能转行成 String 类型");
        }
        if (conversionService.canConvert(Boolean.class, Map.class)) {
            System.out.println(conversionService.convert(Boolean.TRUE, Map.class));
        } else {
            System.out.println("Boolean 对象不能转行成 Map 类型");
        }
    }
}

十、Spring 类型转换在 @Value 中的运用

  • @Value 底层实现
    • 底层实现:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
      • org.springframework.beans.factory.support.DefaultLisableFactory#doResolveDependency
    • 底层服务:org.springframework.beans.TypeConverter
      • 默认实现:org.springframework.beans.TypeConverterDelegate
        • java.beans.PropertyEditor
        • org.springframework.core.convert.ConversionService

十一、Spring 配置属性源 PropertySource

API

  • 单配置属性源:org.springframework.core.env.PropertySource
  • 多配置属性源:org.springframework.core.env.PropertySources

注解

  • 单配置属性源:@org.springframework.context.annotation.PropertySource
  • 多配置属性源:@org.springframework.context.annotation.PropertySources

关联

  • 存储对象:org.springframework.core.env.MutablePropertySources
  • 关联方法:org.springframework.core.env.ConfigurableEnvironment#getPropertySources()

API 详解

单配置属性源

PropertySource
  • 表示 名称/值 属性对来源的抽象基类。底层源对象可以是封装属性的任何类型 T。包括 Properties 对象、Map 对象、ServletContext 和 ServletConfig 对象(用于访问 init 参数)。
  • PropertySource 对象通常不是单独使用的,而是通过 PropertySource 对象聚合属性源,并与 PropertyResolver 实现结合使用,该实现可以跨 PropertySource 集合执行基于优先级的搜索。
  • PropertySource 标识不是基于封装属性的内容确定的,而是仅基于 PropertySource 的名称。这对于在集合上下文中操作 Propertsource 对象很有用。详细信息请参见 MutablePropertySources 中的操作以及 named(String) 和 toString() 方法。
  • 注意,在使用 @Configuration 类时,@PropertySource 注释提供了一种方便的声明性方式,可以将属性源添加到封闭的环境中。
EnumerablePropertySource

能够查询其底层源对象以列举所有可能的属性名/值对的 PropertySource 实现。暴露 getPropertyNames() 方法以允许调用方在不必访问底层源对象的情况下内省可用属性。这也促进了containsProperty(String) 更有效的实现,因为它可以调用 getPropertyNames() 并遍历返回的数组,而不是尝试调用 PropertySource.getProperty(String),后者可能更昂贵。

MapPropertySource

从 Map 对象中读取键和值的 PropertySource。为了符合 getProperty(java.lang.String) 和containsProperty(java.lang.String) 语义,底层映射不应该包含任何 null。

PropertiesPropertySource

从 Properties 对象中提取属性的 PropertySource 实现。
注意,因为 Properties 对象在技术上是一个< object, object > Hashtable,它可能包含非字符串的键或值。然而,该实现仅限于访问基于字符串的键和值,其方式与 Properties.getProperty(String) 和 Properties.setProperty(String, String)相同。

多配置属性源

PropertySources

包含一个或多个 PropertySource 对象。继承 Iterable 接口,具有迭代的功能。

跟着小马哥学系列之 Spring IoC(进阶篇:Environment)_第5张图片

MutablePropertySources
  • PropertySources 接口的默认实现。允许操作包含的属性源,并提供复制现有 PropertySources 实例的构造函数。
  • 优先级中提到的方法如 addFirst (org.springframework.core.env.PropertySource < ? >) 和 addLast (org.springframework.core.env.PropertySource < ? >), 它是使用 PropertyResolver 解析属性时搜索属性源顺序
FilteredPropertySources
  • 按名称筛选属性源的 PropertySources 装饰器。
  • 新建 FilteredPropertySources 时传递一个要过滤的名称列表
CompositePropertySources

通过组合一个或多个 PropertySources。对组合中的 PropertySources 元素的修改将自动反映在组合中。

注解详解

单配置属性源

@PropertySource

@PropertySource 注释提供了一种便利的声明性机制向 Spring 的 Environment 中添加一个 PropertySource。与 @Configuration 类一起使用。

示例:


// 给定一个包含键/值对 testbean.name=myTestBean 的 app.properties 文件 ,
// 下面的 @Configuration 类使用 @PropertySource 将 app.properties 
// 加入到环境的 Propertysources 集。
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
    @Autowired
    Environment env;
    
    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}
  • 解析 ${…}@Value 注解中的占位符
    从 PropertySource 来解析${…} 定义或 @Value 注解属性中的占位符 ,你必须确保在 ApplicationContext 使用的 BeanFactory 中注册了适当的嵌入式值解析器.当在 XML 中使用 时,这将自动发生。当使用 @Configuration 类时,可以通过静态 @Bean 方法显式注册 PropertySourcesPlaceholderConfigurer 来实现这一点。但是请注意,通过静态 @Bean 方法显式注册 PropertySourcesPlaceholderConfigurer 通常只在您需要自定义配置(如占位符语法等)时才需要。

  • 解析 ${…} 占位符在 @PropertySource 资源位置

任何 @PropertySource 资源位置中的${…} 占位符将根据已经在环境中注册的属性源集进行解析。例如:


@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

	@Autowired
	Environment env;
	
	@Bean
	public TestBean testBean() {
	    TestBean testBean = new TestBean();
	    testBean.setName(env.getProperty("testbean.name"));
	    return testBean;
	}
}

  • 假设 my.placeholder 出现在一个已经注册的属性源中,例如系统属性或环境变量,占位符将被解析为相应的值。如果不是,那么 default/path 将被用作默认值。表示默认值(以冒号":"分隔)是可选的。如果没有指定默认值,且不能解析属性,将抛出 IllegalArgumentException.
  • 关于使用 @PropertySource 覆盖属性的说明
    如果给定的属性键存在于多个.properties 文件中,则最后一个 @PropertySource 注解将覆盖前面的。
    例如,给定两个属性文件a.properties和b.properties,考虑以下两个使用@PropertySource注释引用它们的配置类:

 @Configuration
 @PropertySource("classpath:/com/myco/a.properties")
 public class ConfigA { }

 @Configuration
 @PropertySource("classpath:/com/myco/b.properties")
 public class ConfigB { }
  • 覆盖顺序依赖于这些类在应用程序上下文中注册的顺序。在上面的场景中,b.properties 中的属性将覆盖存在于 a.properties 中的任何相同的属性,因为 ConfigB 是最后注册的。
    在某些情况下,在使用 @PropertySource 注解时,严格控制属性源排序可能是不可能的,也不实用。例如,如果上面的 @Configuration 类是通过组件扫描注册的,那么排序就很难预测。在这种情况下——如果覆盖很重要——建议用户回退到使用可编程的 PropertySource API。
  • 注意:根据 Java 8 约定,该注解是可重复的。但是,所有这样的 @PropertySource 注解都需要在相同的级别上声明:要么直接在配置类上声明,要么作为同一自定义注解中的元注解声明。不建议混合使用直接注解和元注解,因为直接注解将有效地覆盖元注解。
  • 主要是介绍下注解中的 factory 属性:可以指定 PropertySourceFactory 自定义实现来获取对 PropertySource 进行扩展。
  • ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry 中对注解 @PropertySource 进行解析

多配置属性源

@PropertySources
  • 聚合多个 @PropertSource 注解的容器注解。
  • 可以使用 @PropertySources 注解嵌套几个 @PropertySource 注解。也可以与 Java 8 对可重复注释的支持一起使用,其中 @PropertSource 可以简单地在同一类型上声明多次,隐式地生成这个容器注释。
    ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry 中对注解 @PropertySources 进行解析

十二、基于注解扩展 Spring 配置属性源

  • org.springframework.context.annotation.PropertySource 实现原理
    • 入口:org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
      • org.springframework.context.annotation.ConfigurationClassParse#ProcessPropertySource
    • 4.3 新增语义
      • 配置属性字符编码:encoding
      • org.springframework.core.io.support.PropertySourceFactory
    • 适配对象:org.springframework.core.env.CompositePropertySource

你可能感兴趣的:(Spring,IoC,spring)