对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
环境抽象
Environment
界面
是集成在容器中的抽象,它对两个键进行建模
应用程序环境的各个方面:配置文件和属性。
配置文件是要注册到
容器。可以将 Bean 分配给配置文件
无论是在 XML 中定义还是使用注释定义。对象的角色
relation to profiles 用于确定当前处于活动状态的用户档案(如果有),
以及默认情况下应处于活动状态的配置文件(如果有)。Environment
属性在几乎所有应用程序中都起着重要作用,可能源自
多种来源:属性文件、JVM 系统属性、系统环境
变量、JNDI、servlet 上下文参数、临时对象、对象等
上。对象与属性相关的角色是提供
具有便捷服务界面的用户,用于配置属性源和解析
属性。Properties
Map
Environment
Bean 定义配置文件
Bean 定义配置文件在核心容器中提供了一种机制,该机制允许 在不同环境中注册不同的 bean。“环境”这个词 对不同的用户可能意味着不同的事情,而此功能可以帮助解决许多问题 使用案例,包括:
-
在开发中处理内存中数据源与查找相同的数据源 datasource 的 JNDI 的 QA 或生产环境。
-
仅在将应用程序部署到 性能环境。
-
为客户 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 目录。我们的 bean
现在如下面的清单所示: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 用户已经设计出了许多方法来
完成此操作,通常依赖于系统环境变量的组合
以及包含解析
添加到正确的配置文件路径,具体取决于环境的值
变量。Bean 定义配置文件是一个核心容器功能,它提供了一个
解决这个问题。<import/>
${placeholder}
如果我们概括前面特定于环境的 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 = "") 禁用默认销毁方法推理。 |
@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 = "") 禁用默认销毁方法推理。 |
如前所述,对于方法,您通常会选择使用 Programmatic
JNDI 查找,通过使用 Spring 的 / helpers 或
直接 JNDI 用法,而不是变体,这将强制您将返回类型声明为类型。@Bean JndiTemplate JndiLocatorDelegate InitialContext JndiObjectFactoryBean FactoryBean |
配置文件字符串可以包含简单的配置文件名称(例如,)或
profile 表达式。配置文件表达式允许更复杂的配置文件逻辑
表示(例如,)。支持以下运算符
配置文件表达式:production
production & us-east
-
!
:配置文件的逻辑NOT
-
&
:配置文件的逻辑AND
-
|
:配置文件的逻辑OR
不能在不使用括号的情况下混合 and 运算符。例如,不是有效的表达式。它必须表示为 。& | 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
如果一个类标有 ,则与该类关联的所有方法和注释都将被绕过,除非一个或多个
指定的配置文件处于活动状态。如果 a 或 类被标记为
使用 ,则不会注册或处理该类,除非
配置文件“P1”或“P2”已激活。如果给定配置文件的前缀为
NOT 运算符 (),则仅当配置文件未
积极。例如,给定 ,如果配置文件
“p1”处于活动状态,或者配置文件“p2”未处于活动状态。@Configuration @Profile @Bean @Import @Component @Configuration @Profile({"p1", "p2"}) ! @Profile({"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 |
使用 on 方法时,可能会应用特殊情况:如果
重载方法(类似于构造函数
overloading),则需要在 all 上一致地声明一个 condition
重载方法。如果条件不一致,则仅
重载方法中的第一个声明很重要。因此,可以
不用于选择具有特定参数签名的重载方法
另一个。同一 bean 的所有工厂方法之间的解析遵循 Spring 的
构造函数解析算法。 如果要定义具有不同性能分析条件的替代 bean,
使用指向同一 bean 名称的不同 Java 方法名称,方法是使用名称
属性,如前面的示例所示。如果参数签名全部为
相同(例如,所有变体都有 no-arg 工厂方法),这是唯一的
首先在有效的 Java 类中表示这种排列的方式
(因为只能有一个特定名称和参数签名的方法)。 |
XML Bean 定义配置文件
XML 对应项是元素的属性。我们前面的示例
配置可以重写为两个 XML 文件,如下所示:profile
<beans>
<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>
已限制为仅允许
文件中的最后一个。这应该有助于提供灵活性,而不会产生
XML 文件中的混乱。spring-bean.xsd
XML 对应项不支持前面描述的配置文件表达式。有可能,
但是,要使用运算符 .也可以应用逻辑
“and” 嵌套配置文件,如下例所示:
在前面的示例中,如果 和 配置文件都处于活动状态,则会公开 Bean。 |
激活配置文件
现在我们已经更新了配置,我们仍然需要指示 Spring 哪个
配置文件处于活动状态。如果我们现在启动示例应用程序,我们将看到
a thrown,因为容器找不到
名为 的 Spring Bean 。NoSuchBeanDefinitionException
dataSource
可以通过多种方式激活配置文件,但最直接的是
它以编程方式针对 API,该 API 可通过 .以下示例显示了如何执行此操作:Environment
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()
}
此外,您还可以通过属性以声明方式激活用户档案,该属性可通过系统环境指定
变量、JVM 系统属性、servlet 上下文参数,甚至作为
条目(请参阅 PropertySource
Abstraction)。在集成测试中,active
可以使用模块中的 Comments 声明 profiles (请参阅 Context Configuration with Environment profiles )。spring.profiles.active
web.xml
@ActiveProfiles
spring-test
请注意,用户档案不是“非此即彼”的命题。您可以激活多个
配置文件。以编程方式,您可以向接受 varargs 的方法提供多个配置文件名称。以下示例
激活多个配置文件:setActiveProfiles()
String…
-
Java
-
Kotlin
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
声明性地,可以接受逗号分隔的配置文件名称列表,
如下例所示:spring.profiles.active
-Dspring.profiles.active="profile1,profile2"
默认配置文件
default 配置文件表示默认启用的配置文件。考虑一下 以下示例:
-
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()
}
}
如果没有配置文件处于活动状态,则会创建 。你可以看到这个
作为为一个或多个 bean 提供默认定义的一种方式。如果有
profile 时,默认配置文件不适用。dataSource
默认配置文件的名称为 。您可以更改
默认配置文件,在 OR 上使用,
以声明方式,通过使用属性。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 属性是否为
为当前环境定义。为了回答这个问题,对象执行
搜索一组 PropertySource
对象。A 是对任何键值对源的简单抽象,而
Spring 的 StandardEnvironment
配置了两个 PropertySource 对象——一个表示 JVM 系统属性集
() 和一个表示系统环境变量集
().my-property
Environment
PropertySource
System.getProperties()
System.getenv()
这些默认属性源可用于 ,以便在独立环境中使用
应用。StandardServletEnvironment 中填充了其他默认属性源,包括 servlet config、servlet
context 参数和 JndiPropertySource (如果 JNDI 可用)。StandardEnvironment |
具体来说,当您使用 时,如果系统属性或环境变量位于
运行。StandardEnvironment
env.containsProperty("my-property")
my-property
my-property
执行的搜索是分层的。默认情况下,系统属性优先于
环境变量。因此,如果在
对 的调用,系统属性值 “wins” 并返回。
请注意,属性值不会合并
而是完全被前面的条目覆盖。 对于通用 ,完整的层次结构如下,其中
topest-precedence 条目:
|
最重要的是,整个机制是可配置的。也许您有一个自定义源
要集成到此搜索中的属性。为此,请实现
并实例化你自己的 Git,并将其添加到
当前。以下示例显示了如何执行此操作: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())
在前面的代码中,已在
搜索。如果它包含属性,则检测并返回该属性,优先使用
任何其他 .MutablePropertySources
API 公开了许多方法,这些方法允许对
property 源。MyPropertySource
my-property
my-property
PropertySource
用@PropertySource
@PropertySource
注解提供了一种方便的声明性机制,用于将 a 添加到 Spring 的 .PropertySource
Environment
给定一个包含键值对的 called 文件 ,则
下面的类以
对 return 的调用 :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")!!
}
}
资源位置中存在的任何占位符都是
针对已针对
environment,如下例所示:${…}
@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")!!
}
}
假设 它已经存在于其中一个属性源中
registered (例如,系统属性或环境变量),则占位符为
resolved 的值。如果不是,则使用
作为默认值。如果未指定 default 且无法解析属性,则会引发 an。my.placeholder
default/path
IllegalArgumentException
根据 Java 8 约定,注释是可重复的。
但是,所有这些注解都需要在同一
级别,可以直接在配置类上,也可以作为
相同的自定义注释。混合直接注释和元注释不是
推荐,因为直接注释可以有效地覆盖元注释。@PropertySource @PropertySource |
语句中的占位符解析
从历史上看,元素中占位符的值只能针对
JVM 系统属性或环境变量。现在情况已不再如此。因为
抽象集成在整个容器中,很容易
通过它的 route resolution of placeholders 进行路由解析。这意味着您可以配置
解决过程。您可以更改搜索的优先级
系统属性和环境变量,或者完全删除它们。您还可以添加
自己的属性源添加到组合中。Environment
具体来说,无论在何处定义属性,只要它在 :customer
Environment
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>