13.环境抽象
Environment接口是集成在容器中的抽象,它对应用程序环境的两个关键方面进行建模:概要文件和属性。
概要文件是一个命名的bean定义逻辑组,只有在给定的概要文件处于活动状态时才向容器注册。bean可以被分配给一个配置文件,无论这个配置文件是用XML定义的还是用注释定义的。与概要文件相关的Environment对象的作用是确定哪些概要文件(如果有的话)当前处于活动状态,以及默认情况下哪些概要文件(如果有的话)应该处于活动状态。
属性在几乎所有应用程序中都起着重要作用,并且可能源自各种来源:属性文件,JVM系统属性,系统环境变量,JNDI,Servlet上下文参数,临时属性对象,Map对象等。 Environment对象与属性相关的作用是为用户提供方便的服务界面,用于配置属性源并从中解析属性。
13.1Bean定义概要文件
ean定义概要文件在核心容器中提供了一种机制,允许在不同环境中注册不同的Bean。对于不同的用户,环境这个词可能有不同的含义,这个特性可以帮助处理许多用例,包括:
- 在开发中针对内存中的数据源进行工作,而不是在进行QA或生产时从JNDI查找相同的数据源。
- 仅在将应用程序部署到性能环境中时才注册监视基础设施。
- 为客户A和客户B部署注册bean的自定义实现。
考虑需要数据源的实际应用程序中的第一个用例。在测试环境中,配置可能类似于以下内容:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
现在考虑如何将该应用程序部署到QA或生产环境中,假设应用程序的数据源已注册到生产应用程序服务器的JNDI目录中。我们的数据源bean现在看起来如下所示:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
- 1
- 2
- 3
- 4
- 5
问题是如何根据当前环境在使用这两种变体之间进行切换。 随着时间的流逝,Spring用户已经设计出多种方法来完成此任务,通常依赖于系统环境变量和包含$ {placeholder}令牌的XML 语句的组合,这些语句根据值解析为正确的配置文件路径 环境变量。 Bean定义配置文件是一项核心容器功能,可提供此问题的解决方案。
如果我们泛化前面环境特定bean定义示例中所示的用例,我们最终需要在某些上下文中注册某些bean定义,而不是在其他上下文中。您可以说,您希望在情形a中注册某个bean定义的概要文件,在情形b中注册一个不同的概要文件。我们首先更新配置以反映这一需求。
当一个或多个指定的概要文件处于活动状态时,@Profile
注解可以指示组件是否有资格注册。使用前面的示例,我们可以重写数据源配置,如下所示:
@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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
@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");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
注意:
- 如前所述,对于
@Bean
方法,通常选择使用程序化JNDI查找,方法是使用Spring的JndiTemplate
/JndiLocatorDelegate
帮助器或前面显示的直接JNDIInitialContext
用法,而不是JndiObjectFactoryBean
变体,这将迫使您将返回类型声明为FactoryBean
类型。
概要文件字符串可以包含简单的概要文件名称(例如,生产)或概要文件表达式。 配置文件表达式允许表达更复杂的配置文件逻辑(例如,生产和美国东部)。 概要文件表达式中支持以下运算符:
- !:配置文件的逻辑“不”
- &:配置文件的逻辑“与”
- |:配置文件的逻辑“或”
注意:
- 您不能将&和 | 混合使用 不使用括号的运算符。 例如,生产与美国东部| eu-central不是有效的表达式。 它必须表示为生产&(美国东部|欧盟中央)。
您可以将@Profile
用作元注解,以创建自定义的组合注释。 以下示例定义了一个自定义@Production
批注,您可以将其用作@Profile(“ production”)
的替代品:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
- 1
- 2
- 3
- 4
- 5
注意:
- 如果
@Configuration
类用@Profile
标记,则除非该类中的一个或多个指定的配置文件处于活动状态,否则所有与该类关联的@Bean
方法和@Import
注解都会被忽略。 如果@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");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
注意:
-
standaloneDataSource
方法只在开发概要文件中可用。 -
jndiDataSource
方法仅在生产配置文件中可用。 -
使用
@Bean
方法上的@Profile
时,可能会出现特殊情况:如果Java方法名称相同的重载@Bean
方法(类似于构造函数重载),则必须在所有重载方法上一致声明@Profile条件。 如果条件不一致,则仅重载方法中第一个声明的条件很重要。 因此,@ Profile
不能用于选择具有另一个参数签名的重载方法。 在创建时,同一bean的所有工厂方法之间的解析都遵循Spring的构造函数解析算法。 -
如果您希望定义具有不同配置文件条件的可选bean,请使用不同的Java方法名称,通过使用
@Bean
name属性指向相同的bean名称,如前面的示例所示。如果参数签名都是相同的(例如,所有的变量都不带参数工厂方法),这是唯一的方式来表示这种安排在第一时间有效的Java类(因为只能有一个方法的名称和参数签名)。
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
<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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
也可以避免在同一文件中拆分和嵌套元素,如以下示例所示:
<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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
-
在前面的示例中,如果生产和用户配置文件都处于活动状态,则将显示
dataSource
bean。
现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件是活动的。如果我们现在启动示例应用程序,我们将看到抛出NoSuchBeanDefinitionException
异常,因为容器无法找到名为dataSource
的Spring bean。
可以通过多种方式来激活配置文件,但是最直接的方法是针对可通过ApplicationContext
获得的Environment
API以编程方式进行配置。 以下示例显示了如何执行此操作:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
- 1
- 2
- 3
- 4
另外,您还可以通过spring.profiles.active
属性以声明方式激活配置文件,该属性可以通过系统环境变量,JVM系统属性,web.xml
中的servlet
上下文参数或什至作为JNDI中的条目来指定(请参阅PropertySource抽象 )。 在集成测试中,可以通过使用spring-test模块中的@ActiveProfiles
批注来声明活动配置文件。
注意,配置文件不是一个非此即彼的命题。您可以同时激活多个配置文件。通过编程,您可以向setActiveProfiles()
方法提供多个概要文件名称,该方法接受字符串可变参数。下面的示例激活多个概要文件
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
- 1
以声明方式,spring.profiles.active
可以接受以逗号分隔的配置文件名称列表,如以下示例所示:
-Dspring.profiles.active="profile1,profile2"
- 1
默认配置文件表示默认情况下启用的配置文件。 考虑以下示例:
@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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
如果没有配置文件是活动的,则创建数据源。您可以将此看作为一个或多个bean提供默认定义的一种方法。如果启用了任何配置文件,则不应用默认配置文件。
您可以通过在环境中使用setDefaultProfiles()
或声明性地使用spring.profiles.default
属性来更改默认配置文件的名称。
13.2PropertySource抽象
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);
- 1
- 2
- 3
- 4
在前面的代码片段中,我们看到了一种高级方法,可以询问Spring是否为当前环境定义了my-property
属性。要回答这个问题,Environment
对象对一组PropertySource
对象执行搜索。PropertySource
是对任何键值对源的简单抽象,Spring的StandardEnvironment
配置了两个PropertySource
对象,一个表示JVM系统属性集(system.getproperties()
),另一个表示系统环境变量集(system .getenv()
)。
注意:
- 这些默认属性源存在于
StandardEnvironment
中,可在独立应用程序中使用。StandardServletEnvironment
填充了其他默认属性源,包括servlet配置和servlet上下文参数。 它可以选择启用JndiPropertySource
具体来说,当您使用StandardEnvironment
时,如果在运行时存在my-property
系统属性或my-property
环境变量,则对env.containsProperty(“ my-property”)
的调用将返回true。
注意:
- 所执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调用
env.getProperty(“my-property”)
期间,恰好在两个位置都设置了my-property
属性,则系统属性值将胜出并被返回。注意,属性值没有被合并,而是被前面的条目完全覆盖。 - 对于常见的
StandardServletEnvironment
,完整层次结构如下,最高优先级条目位于顶部:- ServletConfig参数(如果适用,例如在DispatcherServlet上下文中)
- ServletContext参数(web.xml上下文参数条目
- JNDI环境变量(java:comp / env /条目)
- JVM系统属性(-D命令行参数)
- JVM系统环境(操作系统环境变量)
最重要的是,整个机制是可配置的。也许您有一个想要集成到这个搜索中的自定义属性源。为此,实现并实例化您自己的PropertySource,并将其添加到当前环境的PropertySources集合中。下面的示例展示了如何做到这一点
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
- 1
- 2
- 3
在前面的代码中,在搜索中以最高的优先级添加了MyPropertySource
。如果它包含my-property
属性,则检测并返回该属性,从而有利于任何其他PropertySource
中的任何my-property
属性。MutablePropertySources
API公开了许多允许精确操作属性源集的方法。
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
- 1
- 2
- 3
在前面的代码中,在搜索中以最高的优先级添加了MyPropertySource
。如果它包含my-property
属性,则检测并返回该属性,从而有利于任何其他PropertySource
中的任何my-property
属性。MutablePropertySources
API公开了许多允许精确操作属性源集的方法。
13.3使用@PropertySource
@PropertySource
注解为向Spring环境添加PropertySource
提供了一种方便的声明性机制。
给定一个名为app.properties
的文件,其中包含键值对testbean.name=myTestBean
,下面的@Configuration
类使用@PropertySource
,调用testBean.getName()
会返回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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
@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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
假设my.placeholder
存在于已注册的属性源之一(例如,系统属性或环境变量)中,则将占位符解析为相应的值。 如果不是,则将default / path
用作默认值。 如果未指定默认值并且无法解析属性,则抛出IllegalArgumentException
。
注意:
- 根据Java 8的约定,
@PropertySource
注解是可重复的。但是,所有这样的@PropertySource
注解都需要在同一级别声明,要么直接在配置类上声明,要么作为同一自定义注解中的元注解声明。不推荐混合使用直接注解和元注解,因为直接注解有效地覆盖了元注解。
13.4语句中的占位符解析
过去,元素中的占位符的值只能根据JVM系统属性或环境变量解析。现在情况已经不一样了。因为Environment
抽象集成在整个容器中,所以很容易通过它来解析占位符。这意味着您可以以任何您喜欢的方式配置解析过程。您可以更改搜索系统属性和环境变量的优先级,或者完全删除它们。您还可以在适当的情况下添加您自己的属性源。
具体地说,下面的语句不管客户属性定义在哪里,只要它在环境中可用就可以:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
- 1
- 2
- 3
14.注册一个LoadTimeWeaver
Spring使用LoadTimeWeaver在将类加载到Java虚拟机(JVM)中时对其进行动态转换。
要启用加载时编织,可以将@enableloadtime
织造添加到一个@Configuration
类中,如下面的示例所示
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
- 1
- 2
- 3
- 4
另外,对于XML配置,您可以使用context:load-time-weaver
元素
<beans>
<context:load-time-weaver/>
</beans>
- 1
- 2
- 3
一旦为ApplicationContext
配置好,该ApplicationContext
中的任何bean都可以实现LoadTimeWeaverAware
,从而接收到加载时weaver
实例的引用。这与Spring JPA支持结合起来特别有用,其中加载时编织对于JPA类转换可能是必要的。