学成路更宽,吊打面试官。 ——小马哥
大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间《小马哥讲Spring核心编程思想》基础上形成的个人一些总结。希望能帮助各位小伙伴, 祝小伙伴早日学有所成。 分为基础篇、进阶篇、源码篇。玩游戏看颜色,学技术看版本,本系列以 Spring 5.2.0.RELEASE 版本为基础进行介绍。 祝小伙伴早日学有所成。
从类的继承关系可以看出,Environment 接口继承 PropertyResolver 接口具有属性相关操作功能。ConfigurableEnvironment 具有操作 Profiles 的能力,再往后面就是对 Environment 进行分类:标准环境(非 Web)、传统 Web 环境、Reactive Web 环境。
Environment 主要有2方面功能:profiles 和 properties
- profile 是对注册的 Bean 定义进行命名的逻辑分组,比如日常开发环境、线上环境
- properties 是对属性的占位符处理和类型转换,这里的资源是指广义上的资源不单单是指 Properties,比如 JVM 系统属性,系统环境参数,JNDI,servlet 上下文参数等等。
Spring Framework 3.1 开始引入 Environment 抽象,它统一 Spring 配置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer,而且还支持更丰富的配置源属性(PropertySource)。
通过 Environment Profiles 信息,帮助 Spring 容器提供条件化地装配 Bean。比如开发环境与线上环境的数据源不相同,到时候切换一个 Profile 就会自动切换。
- 大多数(不是全部)Environment 类型都要实现的配置接口。提供用于设置 active 和 default profiles 以及操作底层属性源的工具。允许客户端通过 ConfigurablePropertyResolver 超接口设置和验证所需的属性,定制 ConversionService 等。
- 操纵属性来源
- 可以移除、重新排序或替换属性来源;可以使用
getPropertySources()
返回的MutablePropertySources
实例添加其他属性源。下面的例子是针对ConfigurableEnvironment
的StandardEnvironment
实现的,但是通常适用于任何实现,尽管特定的默认属性源可能有所不同。- 当 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);
}
}
System.getProperties()
的值,否则返回一个 Map实现,该实现将尝试使用 System.getProperty(String) 调用访问单个键。请注意,大多数Environment 实现将把这个系统属性映射作为要搜索的默认 PropertySource。因此,建议不要直接使用此方法,除非明确打算绕过其他属性源。在返回的 Map 上调用 Map.get(Object) 永远不会抛出IllegalAccessException;在 SecurityManager 禁止访问某个属性的情况下,将返回 null,并发出一个info 级别的日志消息,记录这个异常。System.getenv()
的值,否则返回一个 Map 实现,该实现将尝试使用 System.getenv(String)
调用访问单个键。请注意,大多数 Environment 实现将把这个系统环境映射作为要搜索的默认 PropertySource 包括在内。因此,建议不要直接使用此方法,除非明确打算绕过其他属性源。在返回的Map上调用 Map.get(Object) 永远不会抛出IllegalAccessException,在 SecurityManager 禁止访问某个属性的情况下,将返回 null,并发出一个info级别的日志消息,记录这个异常。Environmen 实现的抽象基类。
- 支持保留缺省配置文件默认名称的概念,并通过 ACTIVE_PROFILES_PROPERTY_NAME 和 DEFAULT_PROFILES_PROPERTY_NAME 属性指定激活配置文件和默认配置文件。
- 具体的子类主要不同于它们默认添加的 PropertySource 对象。AbstractEnvironment 没有任何添加。子类应该通过受保护的
customizePropertySources(MutablePropertySources)
钩子方法提供属性源,而客户端应该使用ConfigurableEnvironment.getPropertySources()
和MutablePropertySources
API 进行定制
自定义当前环境在调用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(...));
- 适用于
标准(即非web)
应用程序的环境实现。- 除了 ConfigurableEnvironment 的常用功能(如属性解析和与 profile 相关的操作)之外,该实现还配置了两个默认属性源,它们将按照以下顺序进行搜索:
- 系统属性(AbstractEnvironment#getSystemProperties())
- 系统环境变量(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()));
}
}
,特殊化的 ConfigurableEnvironment 标记接口,表示响应式应用程序上下文 。
特殊化的 ConfigurableEnvironment ,允许在 ServletContext 和(可选) ServletConfig 可用的最早时刻初始化与 servlet 相关的 PropertySource 对象。
initPropertySources():使用给定的参数将任何充当占位符的 StubPropertySource 实例替换为真实的servlet context/config 属性源。
- 基于 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);
}
}
由响应式 web 应用程序使用的环境实现。所有与 web 相关的(基于响应式的)ApplicationContext 类默认初始化一个实例。
从类图上看 PropertyPlaceholderConfigurer
和 PropertySourcesPlaceholderConfigurer
都是PlaceholderConfigurereSupport
的子类。下面一一介绍这些接口/类的作用
需要从一个或多个资源加载属性的 JavaBean 风格组件的基类。通过配置可覆盖已加载的属性,也支持本地属性。
- 属性可想而知就是 Properties 文件,支持一个或者多个资源加载属性所以有
Properties[]
类型的字段;- 支持本地属性即有基于地址定位的资源 字段
Resource[] locations
;- 支持覆盖有个字段
localOverride
字段标示是不是可以属性覆盖;- 属性有编码的支持对应可选字段
fileEncoding
;- 还有存储能力对应字段
propertiesPersister
允许从属性资源(即属性文件)配置单个 bean 属性值(通过 BeanFactoryPostProcessor)。对于覆盖在应用程序上下文中配置的 bean 属性的系统管理员定制配置文件非常有用。提供了两个具体的实现:
- PropertyOverrideConfigurer 具有可覆盖的
beanName.property =value
样式(将属性文件中的值推送到 bean 定义中)- PropertyPlaceholderConfigurer 替换
${...}
占位符(从属性文件中提取值到 bean 定义中)
从类图上看 PropertyResourceConfigurer 实现了
BeanFactoryPostProcessor
和PriorityOrdered
接口;
BeanFactoryPostProcessor#postProcessBeanFactory
方法主要在应用程序上下文的标准初始化之后修改其内部 bean 工厂。所有 bean 定义都将被加载,但是还没有 bean 被实例化。这允许覆盖或添加属性,甚至对急于初始化的 bean 也是如此;PriorityOrdered
接口表示顺序
- 解析 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}"/>
- 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,这意味着在所有环境属性源之后最后搜索本地属性。
PlaceholderConfigurer 子类用于解析
${...}
占位符针对本地属性或者系统属性和环境参数。PropertyPlaceholderConfigurer 依旧适用一些情况
- Spring-context 模块不可用(例如,其中一个使用 Spring 的 BeanFactory API,而不是 ApplicationContext)。
- 现有的配置使用了 systemPropertiesMode 或 systemPropertiesModeName 属性。我们鼓励用户不再使用这些设置,而是通过容器的 Environment 来配置属性源搜索顺序;但是,可以通过继续使用PropertyPlaceholderConfigurer 来维护功能的确切保存。
通过属性 spring.profiles.active = 配置(org.springframework.core.env.AbstractEnvironment#doGetActiveProfiels)
API:org.springframework.core.env.ConfigurableEnviroment
注解:@org.springframework.context.annotation.Profile
- 指示当一个或多个指定的 profile 处于活动状态时,组件有资格注册。
- profile 用于命名逻辑分组,可能以编程方式被激活。通过 ConfigurableEnvironment.setActiveProfiles() 或声明方式通过设置 spring.profiles.active。活动属性作为一个 JVM 系统属性,作为一个环境变量,或者作为 web 应用程序的 web.xml 中的 Servlet 上下文参数。profile 也可以通过 @ActiveProfiles 注解在集成测试中以声明方式激活。
- @Profile 注解可以以以下任何一种方式使用:
- 作为类型级注释可以标注在任何直接或间接用 @Component(包括@Configuration类) 注解的类
- 作为元注释,用于组合自定义原型(模式)注释
- 作为任何 @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或更高版本)中的文档。
解析过程:
解析属性(任何底层源)的接口
containsProperty():属性是否存在
getProperty()/getRequiredProperty():获取属性值/获取属性值(必须存在,否则抛出 IllegalStateException
异常)
resolvePlaceholders():解析${...}
占位符
大多数(不是所有)PropertyResolver 类型实现的配置接口。提供用于访问和自定义在将属性值从一种类型转换为另一种类型时使用的
ConversionService
的工具。
false
值表示严格解析,即抛出异常。true
值表示不可解析的嵌套占位符应该在其无法解析的 ${…}
形式。getproperty (String)
及其变体的实现必须检查这里设置的值,以确定当属性值包含不可解析的占位符时的正确行为setRequiredProperties(java.lang.String…)
指定的每个属性都存在并解析为一个非空值validateRequiredProperties()
进行验证。抽象基类,用于解析针对任何底层源的属性。实现一些通用功能,获取属性调用
getProperty()
方法,解析占位符让PropertyPlaceholderHelper
处理,类型转换让ConfigurableConversionService
来完成。
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 3.0 开始提供新的用于类型转换的服务接口。这是转换系统的入口点。调用
convert(Object, Class)
方法来执行类型转换(具有线程安全特性)。
convert()
方法进行转换。向类型转换系统注册转换器
大多数(不全是) ConversionService 类型将实现的配置接口。整合由 ConversionService 公开的只读操作和 ConverterRegistry 的可变操作,以便方便地通过它们特别添加和删除转换器。后者在应用程序上下文引导代码中的
ConfigurableEnvironment
实例时特别有用。
适合在大多数环境中使用的基本 Convertionservice 实现。通过 ConfigurableConversionService 接口间接实现 ConverterRegistry 作为注册API。主要是处理泛型转换。后续会出一篇 Spring 类型转换的文章,敬请期待。
专门为 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 类型");
}
}
}
- 表示
名称/值
属性对
来源的抽象基类。底层源对象可以是封装属性的任何类型 T。包括 Properties 对象、Map 对象、ServletContext 和 ServletConfig 对象(用于访问 init 参数)。- PropertySource 对象通常不是单独使用的,而是通过 PropertySource 对象聚合属性源,并与 PropertyResolver 实现结合使用,该实现可以跨 PropertySource 集合执行基于优先级的搜索。
- PropertySource 标识不是基于封装属性的内容确定的,而是仅基于 PropertySource 的名称。这对于在集合上下文中操作 Propertsource 对象很有用。详细信息请参见 MutablePropertySources 中的操作以及 named(String) 和 toString() 方法。
- 注意,在使用 @Configuration 类时,@PropertySource 注释提供了一种方便的声明性方式,可以将属性源添加到封闭的环境中。
能够查询其底层源对象以列举所有可能的
属性名/值
对的 PropertySource 实现。暴露 getPropertyNames() 方法以允许调用方在不必访问底层源对象的情况下内省可用属性。这也促进了containsProperty(String) 更有效的实现,因为它可以调用 getPropertyNames() 并遍历返回的数组,而不是尝试调用 PropertySource.getProperty(String),后者可能更昂贵。
从 Map 对象中读取键和值的 PropertySource。为了符合 getProperty(java.lang.String) 和containsProperty(java.lang.String) 语义,底层映射不应该包含任何 null。
从 Properties 对象中提取属性的 PropertySource 实现。
注意,因为 Properties 对象在技术上是一个< object, object > Hashtable,它可能包含非字符串的键或值。然而,该实现仅限于访问基于字符串的键和值,其方式与 Properties.getProperty(String) 和 Properties.setProperty(String, String)相同。
包含一个或多个 PropertySource 对象。继承 Iterable 接口,具有迭代的功能。
- 按名称筛选属性源的 PropertySources 装饰器。
- 新建 FilteredPropertySources 时传递一个要过滤的名称列表
通过组合一个或多个 PropertySources。对组合中的 PropertySources 元素的修改将自动反映在组合中。
@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
进行解析
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
中对注解 @PropertySources
进行解析