此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
环境抽象
这Environment
接口
是集成在容器中的抽象,它对两个键进行了建模
应用程序环境的各个方面:配置文件和属性。
配置文件是一组命名的逻辑 Bean 定义,要向
仅当给定的配置文件处于活动状态时才容器。可以将 Bean 分配给配置文件
无论是在 XML 中定义还是使用注释定义。的作用Environment
对象替换为
与配置文件的关系在于确定哪些配置文件(如果有)当前处于活动状态,
以及哪些配置文件(如果有)默认应处于活动状态。
属性在几乎所有应用中都起着重要作用,并且可能源自
多种来源:属性文件、JVM 系统属性、系统环境
变量, JNDI, servlet 上下文参数, 临时Properties
对象Map
对象,等等
上。的作用Environment
object 与属性相关的是提供
用户具有方便的服务界面,用于配置属性源和解析
属性。
Bean 定义配置文件
Bean 定义配置文件在核心容器中提供了一种机制,允许 在不同环境中注册不同的 Bean。“环境”这个词, 对不同的用户来说可能意味着不同的事情,此功能可以帮助许多用户 用例,包括:
-
在开发中使用内存中数据源与查找相同的数据源 在 QA 或生产中时,来自 JNDI 的数据源。
-
仅在将应用程序部署到 性能环境。
-
为客户 A 与客户注册 Bean 的定制实现 B 部署。
考虑实际应用程序中的第一个用例,它需要DataSource
.在测试环境中,配置可能类似于以下内容:
-
Java
-
Kotlin
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build()
}
现在考虑如何将此应用程序部署到 QA 或生产中
环境,假设已注册应用程序的数据源
替换为生产应用程序服务器的 JNDI 目录。我们dataSource
豆
现在看起来像以下列表:
-
Java
-
Kotlin
@Bean(destroyMethod = "")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
问题是如何在使用这两种变体之间切换,基于
当前环境。随着时间的推移,Spring 用户设计了多种方法
完成此作,通常依赖于系统环境变量的组合
和 XML<import/>
包含${placeholder}
解析的Tokens
到正确的配置文件路径,具体取决于环境的值
变量。Bean 定义配置文件是一个核心容器功能,它提供了
解决这个问题。
如果我们概括前面特定于环境的 bean 示例中显示的用例 定义,我们最终需要在 某些上下文,但在其他上下文中则不然。您可以说您想注册一个 情况 A 中 Bean 定义的某些配置文件和 情况 B.我们首先更新我们的配置以反映这种需求。
用@Profile
这@Profile
注释允许您指示组件符合注册条件
当一个或多个指定的配置文件处于活动状态时。使用前面的示例,我们
可以重写dataSource
配置如下:
-
Java
-
Kotlin
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("development")
class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
-
Java
-
Kotlin
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod = "") (1)
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
1 | @Bean(destroyMethod = "") 禁用默认的 destroy 方法推理。 |
@Configuration
@Profile("production")
class JndiDataConfig {
@Bean(destroyMethod = "") (1)
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
1 | @Bean(destroyMethod = "") 禁用默认的 destroy 方法推理。 |
如前所述,使用@Bean 方法,您通常选择使用编程
JNDI 查找,通过使用 Spring 的JndiTemplate /JndiLocatorDelegate helpers 或
直 JNDIInitialContext 用法,但未显示JndiObjectFactoryBean variant,这将强制您将返回类型声明为FactoryBean 类型。 |
配置文件字符串可以包含一个简单的配置文件名称(例如,production
) 或
profile 表达式。配置文件表达式允许将更复杂的配置文件逻辑
表达(例如,production & us-east
).以下运算符支持
配置文件表达式:
-
!
:逻辑NOT
的个人资料 -
&
:逻辑AND
的配置文件 -
|
:逻辑OR
的配置文件
不能将 和& | 运算符而不使用括号。例如production & us-east | eu-central 不是有效的表达式。它必须表示为production & (us-east | eu-central) . |
您可以使用@Profile
作为元注释
创建自定义组合注释。以下示例定义了自定义@Production
注释,可用作插入式替换@Profile("production")
:
-
Java
-
Kotlin
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
如果@Configuration class 标记为@Profile ,所有@Bean 方法和@Import 与该类关联的注释将被绕过,除非一个或多个
指定的配置文件处于活动状态。如果@Component 或@Configuration class 被标记为
跟@Profile({"p1", "p2"}) ,除非
配置文件“P1”或“P2”已激活。如果给定配置文件前缀为
NOT 运算符 (! ),仅当配置文件未注册时才会注册带注释的元素
积极。例如,给定@Profile({"p1", "!p2"}) ,如果配置文件
“p1”处于活动状态,或者配置文件“p2”未处于活动状态。 |
@Profile
也可以在方法级别声明以仅包含一个特定的 bean
配置类的(例如,对于特定 bean 的替代变体),作为
以下示例显示:
-
Java
-
Kotlin
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production") (2)
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
1 | 这standaloneDataSource 方法仅在development 轮廓。 |
2 | 这jndiDataSource 方法仅在production 轮廓。 |
@Configuration
class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
fun standaloneDataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
@Bean("dataSource")
@Profile("production") (2)
fun jndiDataSource() =
InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource
}
1 | 这standaloneDataSource 方法仅在development 轮廓。 |
2 | 这jndiDataSource 方法仅在production 轮廓。 |
跟 如果要定义具有不同配置文件条件的替代 Bean,
使用指向相同 bean 名称的不同 Java 方法名称,方法是使用 |
XML Bean 定义配置文件
XML 对应项是profile
属性的<beans>
元素。 我们前面的示例配置可以重写为两个 XML 文件,如下所示:
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
也可以避免这种分裂和嵌套<beans/>
元素,如以下示例所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
这spring-bean.xsd
已被限制为仅允许文件中的最后一个。这应该有助于提供灵活性,而不会造成XML 文件中的混乱。
XML 对应项不支持前面描述的配置文件表达式。可以,但是,要使用
在前面的示例中, |
激活配置文件
现在我们已经更新了配置,我们仍然需要指示 Spring 哪个profile 处于活动状态。如果我们现在启动示例应用程序,我们会看到 一个NoSuchBeanDefinitionException
抛出,因为容器找不到名为dataSource
.
可以通过多种方式激活配置文件,但最直接的方法是执行它以编程方式针对Environment
API 可通过ApplicationContext
.以下示例显示了如何执行此作:
-
Java
-
Kotlin
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
val ctx = AnnotationConfigApplicationContext().apply {
environment.setActiveProfiles("development")
register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
refresh()
}
此外,您还可以通过spring.profiles.active
属性,可以通过系统环境指定
变量、JVM 系统属性、servlet 上下文参数web.xml
,甚至作为
JNDI 中的条目(参见PropertySource
抽象化).在集成测试中,活动
可以使用@ActiveProfiles
注释中的spring-test
模块(请参阅使用环境配置文件进行上下文配置)。
请注意,配置文件不是“非此即彼”的命题。您可以激活多个
配置文件。通过编程方式,您可以向setActiveProfiles()
方法,该方法接受String…
varargs。以下示例
激活多个配置文件:
-
Java
-
Kotlin
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
声明式,spring.profiles.active
可以接受以逗号分隔的配置文件名称列表,
如以下示例所示:
-Dspring.profiles.active="profile1,profile2"
默认配置文件
默认配置文件表示在没有处于活动状态的配置文件时启用的配置文件。考虑 以下示例:
-
Java
-
Kotlin
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
如果没有处于活动状态的配置文件,则dataSource
是
创建。您可以将其视为为一个或多个提供默认定义的一种方式
豆。如果启用了任何配置文件,则默认配置文件不适用。
默认配置文件的名称为default
.您可以更改
默认配置文件,使用setDefaultProfiles()
在Environment
或
声明性地,通过使用spring.profiles.default
财产。
PropertySource
抽象化
Spring的Environment
抽象通过可配置的
属性源的层次结构。请考虑以下列表:
-
Java
-
Kotlin
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")
在前面的代码片段中,我们看到了一种高级方式,用于询问 Spring 是否my-property
属性是
为当前环境定义。要回答这个问题,请Environment
对象执行
搜索一组PropertySource
对象。一个PropertySource
是对任何键值对源的简单抽象,并且
Spring的StandardEnvironment
配置了两个 PropertySource 对象 — 一个表示 JVM 系统属性集
(System.getProperties()
)和一个表示系统环境变量集
(System.getenv()
).
这些默认属性源存在于StandardEnvironment ,用于独立使用
应用。StandardServletEnvironment 填充了其他默认属性源,包括 servlet config、servlet
上下文参数,以及JndiPropertySource 如果 JNDI 可用。 |
具体来说,当您使用StandardEnvironment
,调用env.containsProperty("my-property")
如果my-property
系统属性或my-property
环境变量存在于
运行。
执行的搜索是分层的。默认情况下,系统属性优先于
环境变量。因此,如果 对于一个普通的
|
最重要的是,整个机制是可配置的。也许您有一个自定义源
要集成到此搜索中的属性。为此,请实现
并实例化您自己的PropertySource
并将其添加到PropertySources
对于
当前Environment
.以下示例显示了如何执行此作:
-
Java
-
Kotlin
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())
在前面的代码中,MyPropertySource
已以最高优先级添加到
搜索。如果它包含my-property
属性,则该属性被检测并返回,有利于
任何my-property
属性PropertySource
.这MutablePropertySources
API 公开了许多方法,允许精确作
属性来源。
用@PropertySource
这@PropertySource
注释提供了一种方便的声明性机制,用于添加PropertySource
到 Spring 的Environment
.
给定一个名为app.properties
包含键值对testbean.name=myTestBean
,
以下@Configuration
类用途@PropertySource
以这样一种方式
对testBean.getName()
返回myTestBean
:
-
Java
-
Kotlin
@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;
}
}
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
任何${…}
占位符@PropertySource
资源位置是
针对已针对
环境,如以下示例所示:
-
Java
-
Kotlin
@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;
}
}
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
假设my.placeholder
已存在于其中一个属性源中
已注册(例如,系统属性或环境变量),占位符为
解析为相应的值。如果没有,那么default/path
被使用
作为默认设置。如果未指定默认值并且无法解析属性,则IllegalArgumentException
被抛出。
@PropertySource 可以用作可重复的注释。@PropertySource 也可以用作元注释,以创建自定义组合注释
属性覆盖。 |
语句中的占位符解析
从历史上看,元素中占位符的值只能针对
JVM 系统属性或环境变量。现在情况已不再如此。因为
这Environment
抽象集成在整个容器中,很容易
通过它路由占位符的解析。这意味着您可以配置
以您喜欢的任何方式解决过程。您可以更改搜索的优先级
系统属性和环境变量或完全删除它们。您还可以添加您的
根据需要,自己的财产来源混合在一起。
具体来说,无论customer
属性,只要它在Environment
:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>