环境界面 是集成在容器中的抽象,用于对两个键进行建模 应用程序环境的各个方面:配置文件属性

配置文件是要向 仅当给定配置文件处于活动状态时,容器。可以将 Bean 分配给配置文件 无论是在 XML 中定义还是使用注释定义。对象的作用 与配置文件的关系是确定哪些配置文件(如果有)当前处于活动状态, 以及默认情况下应激活哪些配置文件(如果有)。Environment

属性在几乎所有应用中都起着重要作用,可能源于 各种来源:属性文件、JVM 系统属性、系统环境 变量、JNDI、servlet 上下文参数、临时对象、对象等 上。对象与属性相关的作用是提供 用户具有方便的服务界面,用于配置属性源和解析 属性。PropertiesMapEnvironment

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 语句 根据环境的值更改为正确的配置文件路径 变量。Bean 定义配置文件是一个核心容器功能,它提供 解决这个问题。<import/>${placeholder}

如果我们推广前面特定于环境的 Bean 示例中所示的用例 定义,我们最终需要在 某些上下文,但在其他上下文中则不然。你可以说你想注册一个 情况 A 中 Bean 定义的某些配置文件和 A 中的不同配置文件 情况 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 = "")禁用默认销毁方法推理。
如前所述,对于方法,您通常选择使用编程 JNDI 查找,通过使用 Spring 的 / 助手或 前面显示的直接 JNDI 用法,但不是变体,这将强制您将返回类型声明为类型。@BeanJndiTemplateJndiLocatorDelegateInitialContextJndiObjectFactoryBeanFactoryBean

配置文件字符串可以包含简单的配置文件名称(例如,)或 配置文件表达式。配置文件表达式允许更复杂的配置文件逻辑 表示(例如,)。以下运算符在 配置文件表达式:productionproduction & us-east

  • !:配置文件的逻辑NOT

  • &:配置文件的逻辑AND

  • |:配置文件的逻辑OR

如果不使用括号,则不能混合使用 和 运算符。例如,不是一个有效的表达式。它必须表示为 。&|production & us-east | eu-centralproduction & (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 或类 with ,除非 配置文件“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 该方法仅在配置文件中可用。standaloneDataSourcedevelopment
2 该方法仅在配置文件中可用。jndiDataSourceproduction
@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 该方法仅在配置文件中可用。standaloneDataSourcedevelopment
2 该方法仅在配置文件中可用。jndiDataSourceproduction

使用 on 方法时,可能适用特殊情况:如果 相同 Java 方法名称的重载方法(类似于构造函数 重载),一个条件需要一致地声明 重载方法。如果条件不一致,则仅 重载方法中的第一个声明很重要。因此,可以 不用于选择具有特定参数签名的重载方法 另一个。同一豆子的所有工厂方法之间的分辨率遵循 Spring 的 创建时的构造函数解析算法。@Profile@Bean@Bean@Profile@Profile

如果要定义具有不同配置文件条件的替代 Bean, 使用不同的 Java 方法名称,这些方法名称指向相同的 Bean 名称 属性,如前面的示例所示。如果参数签名全部 相同(例如,所有变体都有 no-arg 工厂方法),这是唯一的 首先在有效的 Java 类中表示这种排列的方式 (因为特定名称和参数签名只能有一个方法)。@Bean

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”,如以下示例所示:!

<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="production">
		<beans profile="us-east">
			<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
		</beans>
	</beans>
</beans>

在前面的示例中,如果 和 配置文件都处于活动状态,则会公开 Bean。dataSourceproductionus-east

激活配置文件

现在我们已经更新了配置,我们仍然需要指示 Spring 哪个 配置文件处于活动状态。如果我们现在开始我们的示例应用程序,我们会看到 一个扔,因为容器找不到 名为 .NoSuchBeanDefinitionExceptiondataSource

激活配置文件可以通过多种方式完成,但最直接的是 它以编程方式针对 API 提供,该 API 可通过 .以下示例演示如何执行此操作:EnvironmentApplicationContext

  • 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 上下文参数,甚至作为 JNDI 中的条目(参见 PropertySource Abstraction)。在集成测试中,活动 可以使用模块中的注释来声明配置文件(请参阅 Context configuration with Environment Profiles )。spring.profiles.activeweb.xml@ActiveProfilesspring-test

请注意,配置文件不是一个“非此即彼”的命题。您可以激活多个 配置文件。以编程方式,可以向接受 vararg 的方法提供多个配置文件名称。以下示例 激活多个配置文件:setActiveProfiles()String…​

  • 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

默认配置文件的名称为 。您可以更改名称 默认配置文件,用于 or 以声明方式,通过使用该属性。defaultsetDefaultProfiles()Environmentspring.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-propertyEnvironmentPropertySourceSystem.getProperties()System.getenv()

这些默认属性源存在于 中,用于独立 应用。StandardServletEnvironment 填充了其他默认属性源,包括 servlet config、servlet context 参数,以及 JndiPropertySource(如果 JNDI 可用)。StandardEnvironment

具体而言,当您使用 时,如果系统属性或环境变量存在 ,则 的调用返回 true 运行。StandardEnvironmentenv.containsProperty("my-property")my-propertymy-property

执行的搜索是分层的。默认情况下,系统属性优先于 环境变量。因此,如果属性恰好在两个地方都设置了 对 的调用,系统属性值“获胜”并返回。 请注意,属性值不会合并 而是完全被前面的条目覆盖。my-propertyenv.getProperty("my-property")

对于通用的 ,完整层次结构如下,其中 顶部的最高优先级条目:StandardServletEnvironment

  1. ServletConfig 参数(如果适用,例如,在上下文中)DispatcherServlet

  2. ServletContext 参数(web.xml context-param 条目)

  3. JNDI 环境变量(条目)java:comp/env/

  4. JVM 系统属性(命令行参数)-D

  5. JVM 系统环境(操作系统环境变量)

最重要的是,整个机制是可配置的。也许你有一个自定义源 要集成到此搜索中的属性。为此,请实施 并实例化你自己的实例化,并将其添加到 for 的集合中 当前。以下示例演示如何执行此操作:PropertySourcePropertySourcesEnvironment

  • 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 公开了许多方法,这些方法允许精确操作 属性来源。MyPropertySourcemy-propertymy-propertyPropertySource

@PropertySource

@PropertySource注解提供了一种方便的声明性机制,用于向 Spring 的 .PropertySourceEnvironment

给定一个名为包含键值对的文件, 下面的类以这样一种方式使用 A call to return:app.propertiestestbean.name=myTestBean@Configuration@PropertySourcetestBean.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")!!
	}
}

假设它已经存在于其中一个属性源中 已注册(例如,系统属性或环境变量),占位符为 解析为相应的值。如果不是,则使用 作为默认设置。如果未指定默认值且无法解析属性,则会抛出 an。my.placeholderdefault/pathIllegalArgumentException

@PropertySource可以用作可重复的注释。 也可以用作元注释,以创建自定义组合注释 属性覆盖。@PropertySource

语句中的占位符解析

从历史上看,元素中占位符的值只能针对 JVM 系统属性或环境变量。现在情况已不再如此。因为 抽象是在整个容器中集成的,很容易 通过它路由占位符的解析。这意味着您可以配置 以您喜欢的任何方式解决过程。您可以更改搜索的优先级 系统属性和环境变量,或完全删除它们。您还可以添加您的 酌情拥有自己的财产来源。Environment

具体而言,无论属性在何处定义,以下语句都有效,只要它在 :customerEnvironment

<beans>
	<import resource="com/bank/service/${customer}-config.xml"/>
</beans>