1.13 环境抽象
该Environment接口是集成在容器模型应用环境的两个关键方面的抽象:profiles 和 properties
profile 配置文件是仅在给定配置文件处于活动状态时才向容器注册的bean定义的命名逻辑组。可以将Bean分配给配置文件,无论是以XML还是使用注解定义。Environment与配置文件相关的对象的作用是确定哪些配置文件(如果有)当前处于活动状态,以及默认情况下哪些配置文件(如果有)应处于活动状态。
Properties在几乎所有应用程序中都发挥着重要作用,可能源自各种源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,ad-hoc Properties对象,Map对象等。Environment与属性相关的对象的作用是为用户提供方便的服务接口,用于配置属性源和从中解析属性。
1.13.1. Bean 定义 Profiles
Bean定义配置文件在核心容器中提供了一种机制,允许在不同环境中注册不同的bean。“环境”这个词对不同的用户来说意味着不同的东西,这个功能可以帮助解决许多用例,包括:
- 在QA或生产环境中,针对开发中的内存数据源而不是从JNDI查找相同的数据源。
- 仅在将应用程序部署到性能环境时注册监视基础结构。
- 为客户A和客户B部署注册bean的自定义实施。
考虑实际应用中的第一个用例,它需要一个 DataSource。在测试环境中,配置可能类似于以下内容:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
现在考虑如何将此应用程序部署到QA或生产环境中,假设应用程序的数据源已注册到生产应用程序服务器的JNDI目录。我们的dataSourcebean现在看起来如下:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
问题是如何根据当前环境在使用这两种变体之间切换。随着时间的推移,Spring用户已经设计了许多方法来完成这项工作,通常依赖于系统环境变量和包含${placeholder}令牌的XML 语句的组合,这些令牌根据环境变量的值解析为正确的配置文件路径。Bean定义配置文件是核心容器功能,可为此问题提供解决方案。
如果我们概括了前面特定于环境的bean定义示例中显示的用例,我们最终需要在某些上下文中注册某些bean定义,而在其他上下文中则不需要。您可以说您希望在情境A中注册特定的bean定义配置文件,在情况B中注册不同的配置文件。我们首先更新配置以反映此需求。
运用 @Profile
通过@Profile 注释,您可以指示当一个或多个指定的配置文件处于活动状态时,组件符合注册条件。使用前面的示例,我们可以dataSource按如下方式重写配置:
@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("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
如前所述,使用@Bean方法,您通常选择使用编程JNDI查找,使用Spring的JndiTemplate/ JndiLocatorDelegatehelper或InitialContext前面显示的直接JNDI 用法,但不使用JndiObjectFactoryBean 变量,这会强制您将返回类型声明为FactoryBean类型。
配置文件字符串可以包含简单的配置文件名称(例如production)或配置文件表达式。概要表达式允许表达更复杂的概要逻辑(例如,production & us-east)。配置文件表达式支持以下运算符:
- !:配置文件的逻辑“不”
- &:配置文件的逻辑“和”
- |:配置文件的逻辑“或”
不使用括号, 不能混合使用&和|运算符。例如, production & us-east | eu-central不是有效的表达式。它必须表达为 production & (us-east | eu-central)。
您可以将其@Profile用作元注释,以创建自定义组合注释。以下示例定义了一个自定义 @Production注释,您可以将其用作以下内容的替代品 @Profile(“production”):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
如果@Configuration标记了类,则除非一个或多个指定的配置文件处于活动状态,否则将绕过与该类关联的@Profile所有@Bean方法和 @Import注释。如果a @Component或@Configuration类被标记@Profile({“p1”, “p2”}),则该类未被注册或处理,除非已激活配置文件’p1’或’p2’。如果给定的配置文件以NOT运算符(!)作为前缀,则仅在配置文件未激活时才注册带注释的元素。例如,@Profile({“p1”, “!p2”})如果配置文件“p1”处于活动状态或配置文件“p2”未激活,则会发生注册。
@Profile 也可以在方法级别声明只包含配置类的一个特定bean(例如,对于特定bean的替代变体),如以下示例所示:
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
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")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
该standaloneDataSource方法仅在development配置文件中可用。
该jndiDataSource方法仅在production配置文件中可用。
使用@Profileon @Bean方法,可能会应用特殊方案:对于@Bean相同Java方法名称的重载方法(类似于构造函数重载),@Profile需要在所有重载方法上一致地声明条件。如果条件不一致,则只有重载方法中第一个声明的条件才重要。因此,@Profile不能用于选择具有特定参数签名的重载方法。同一个bean的所有工厂方法之间的分辨率遵循Spring的构造函数解析算法在创建时。
如果要定义具有不同配置文件条件的备用Bean,请使用通过使用@Beanname属性指向同一bean名称的不同Java方法名称,如上例所示。如果参数签名都是相同的(例如,所有变体都具有no-arg工厂方法),这是首先在有效的Java类中表示这种排列的唯一方法(因为只有一个特定名称和参数签名的方法)。
XML Bean定义配置文件
XML对应物是元素的profile属性。我们之前的示例配置可以在两个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>
也可以避免在同一文件中使用split和nest 元素,如下例所示:
<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对应方不支持前面描述的配置文件表达式。但是,可以通过使用!运算符来否定轮廓。也可以通过嵌套配置文件来应用逻辑“和”,如以下示例所示:
<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>
在前面的示例中,dataSource如果两个production和 us-east配置文件都处于活动状态,则会公开Bean 。
Activating 一个Profile
现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件处于活动状态。如果我们现在开始我们的示例应用程序,我们会看到NoSuchBeanDefinitionException抛出,因为容器找不到名为的Spring bean dataSource。
激活配置文件可以通过多种方式完成,但最直接的方法是以编程方式对Environment可通过API提供的API进行操作 ApplicationContext。以下示例显示了如何执行此操作:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
此外,您还可以通过spring.profiles.active属性声明性地激活配置文件,该 属性可以通过系统环境变量,JVM系统属性,servlet上下文参数web.xml或甚至作为JNDI中的条目来指定(请参阅PropertySource抽象)。在集成测试中,可以使用模块中的@ActiveProfiles注释声明活动配置文件spring-test(请参阅使用环境配置文件的上下文配置)。
请注意,配置文件不是“任何 - 或”命题。您可以一次激活多个配置文件。以编程方式,您可以为setActiveProfiles()方法提供多个配置文件名称,该 方法接受String… varargs。以下示例激活多个配置文件:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
声明性地,spring.profiles.active可以接受以逗号分隔的配置文件名称列表,如以下示例所示:
-Dspring.profiles.active="profile1,profile2"
默认配置文件
默认配置文件表示默认启用的配置文件。请考虑以下示例:
@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();
}
}
如果没有激活配置文件,dataSource则创建该配置文件。您可以将此视为一种为一个或多个bean提供默认定义的方法。如果启用了任何配置文件,则默认配置文件不适用。
您可以通过更改默认的配置文件的名称setDefaultProfiles()上Environment,或者声明,通过使用spring.profiles.default属性。
1.13.2 PropertySource抽象化
Spring的Environment抽象提供了对可配置的属性源层次结构的搜索操作。请考虑以下列表:
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);
在前面的代码片段中,我们看到了一种向Spring询问是否my-property为当前环境定义属性的高级方法。要回答此问题,Environment对象将对一组对象执行搜索PropertySource 。A PropertySource是对任何键值对源的简单抽象,Spring StandardEnvironment 配置有两个PropertySource对象 - 一个表示JVM系统属性集(System.getProperties()),另一个表示系统环境变量集(System.getenv()
这些默认属性源StandardEnvironment适用于独立应用程序。StandardServletEnvironment 填充了其他默认属性源,包括servlet配置和servlet上下文参数。它可以选择启用a JndiPropertySource。有关详细信息,请参阅javadoc。
具体来说,当您使用时StandardEnvironment,env.containsProperty(“my-property”) 如果运行时存在my-property系统属性或my-propertyi环境变量,则调用将返回true 。
执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果my-property在调用期间恰好在两个位置都设置了属性env.getProperty(“my-property”),则系统属性值“wins”并返回。请注意,属性值未合并,而是由前面的条目完全覆盖。
对于公共StandardServletEnvironment层次结构,完整层次结构如下,最高优先级条目位于顶部:
- ServletConfig参数(如果适用-例如,在DispatcherServlet上下文的情况下)
- ServletContext参数(web.xml context-param条目)
- JNDI环境变量(java:comp/env/条目)
- JVM系统属性(-D命令行参数)
- JVM系统环境(操作系统环境变量)
最重要的是,整个机制是可配置的。您可能希望将自定义的属性源集成到此搜索中。为此,请实现并实例化您自己的PropertySource并将其添加到PropertySources当前的集合中Environment。以下示例显示了如何执行此操作:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上面的代码中,MyPropertySource在搜索中添加了最高优先级。如果它包含my-property属性,则检测并返回该属性,以支持my-property任何其他属性PropertySource。所述 MutablePropertySources API公开了大量的,其允许该组的属性源的精确操作方法。
1.13.3 运用@PropertySource
该@PropertySource 注解提供便利和声明的机制添加PropertySource 到Spring的Environment。
给定一个app.properties包含键值对的文件testbean.name=myTestBean,以下@Configuration类使用以下@PropertySource方式调用testBean.getName()return myTestBean:
@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;
}
}
资源位置中${…}存在的任何占位符@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则抛出a。
该@PropertySource注释是可重复的,根据Java的8约定。但是,所有这些@PropertySource注释都需要在同一级别声明,可以直接在配置类上声明,也可以在同一自定义注释中作为元注释声明。不建议混合直接注释和元注释,因为直接注释有效地覆盖了元注释。
1.13.4 占位符决议在声明中
从历史上看,元素中占位符的值只能针对JVM系统属性或环境变量进行解析。这已不再是这种情况。因为Environment抽象集成在整个容器中,所以很容易通过它来解决占位符的分辨率。这意味着您可以以任何您喜欢的方式配置解析过程。您可以更改搜索系统属性和环境变量的优先级,或完全删除它们。您也可以根据需要将自己的属性源添加到混合中。
具体而言,无论customer属性在何处定义,以下语句都可以工作,只要它在以下位置可用Environment:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>