Environment
是 Spring 容器中对于应用环境两个关键因素的一个抽象。它们分别是:Profiles
和 properties
。
Profile
是一个 bean 定义命名的逻辑分组。容器中可以配置多个 Profile
,但是需要指定 profile
才会把这个 Profile
下的分组 bean 定义注册到容器中。 无论 beans 是定义在 XML 还是 注解里面都可以分配给一个 Profile
。Environment
对象与 Profile
关系是确定哪些 Profiles(if any)
当前是有效的,哪些 Profiles
(如果有的话)应该在默认情况下是有效的。
Properties
在几乎所有应用程序中都扮演着重要的角色,并且可能来自各种各样的来源:properties 文件
、JVM系统属性
、系统环境变量
、JNDI
、Servlet Context 参数
、ad-hoc Properties 对象
、Map
等等。Environment
与 Properties
的关系是为用户提供一个方便的服务接口,用于配置属性源并从它们中解析属性。
1、Bean 定义 profiles
Bean定义 Profiles
是核心容器中的一种机制,它允许在不同的环境中注册不同的 Bean。environment
这个词对不同的用户意味着不同的东西,这个特性可以在很多情况下非常有用,包括:
- 在开发环境中使用内存中的数据源,在 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 目录中注册。我们dataSource
的 bean现在看起来是这样的:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
问题是在当前环境如何在这两个变量之间切换进行切换。随着时间的推移,Spring 用户已经设计了许多方法来完成这项工作。通常依赖于系统环境变量和包含 ${placeholder}[占位符]
的 XML
代码片段的组合。根据一个环境变量的值解析到正确配置文件路径。Bean 定义 Profiles
是一个容器的核心特性,它为这个问题提供了解决方案。
@Profile
@Profile 注解允许你声明把 Component 注册一个或者多个指定的 Profile
里面。使用上面的例子,我们可以重写
dataSource
配置如下:
@Configuration
@Profile("dev")
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");
}
}
@profile
可以用作元注解,目的是创建自定义组合注释。下面的例子定义了一个自定义的@Production
注释,可以用来替换 @Profile("production")
:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@profile
也可以声明在方法上来只包含一个特定的配置 bean :
@Configuration
public class AppConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
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
@Profile("production")
public DataSource productionDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
XML bean 定义 profiles
与XML相应的就是profile
元素。上面的样例配置可以在两个XML文件中重写:
同样也可以避免在不同的文件中定义,可以在同一个文件中使用嵌套的
标签:
spring-bean.xsd
允许一个或者多个
这样的元素, 但是
元素只能和其它
元素平级。
这应该有助于提供灵活性,而不会导致 XML 文件中的混乱。
合法的 Bean 定义
...
...
非法的 Bean 定义
...
激活 Profile
既然我们已经更新了配置,我们仍然需要指示 Spring 哪个Profile
是有效的。如果我们现在开始我们的样例应用程序,我们会看到抛出一个NoSuchBeanDefinitionException
异常,因为容器无法找到名为dataSource
的 Spring bean。
可以通过几种方式进行激活一个Profile
,但是最直接的方法是通过编程的方式对ApplicationContext
里面的 Environment API
进行编程:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
此外,Profile
也可以通过spring.profile.active
以声明的方式被激活。可通过系统环境变量、JVM系统属性、web.xml
里面的 ServletContext 参数,
甚至是JNDI中的一个 Entry(第2节“PropertySource abstraction”)。在集成测试中,可以通过spring-test
模块中的@activeprofiles
注释来激活 profile
。
请注意,Profile
并不是一个“非此即为”的命题;是可以同时激活多个Profile
的。可以以编程的方式,通过调用setActiveProfiles()
方法激活多个 Profile。该方法接收可变参数字符串(String…)
:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
可以通过spring.profiles.active
来声明,可以接收以逗号分隔的 profile
名称列表:
-Dspring.profiles.active="profile1,profile2"
Default profile
一般情况下我们定义 bean 都没有设置 profile
,其实 Spring 默认帮我们设置 profile
为 default
:
@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();
}
}
如果没有 Profile
被激活,则会创建上面的 dataSource
;这可以看作是为一个或多个 bean 提供缺省定义的一种方式。如果启用了其它profile
,则默认的profile
将不适用。
可以通过Environment
的 setDefaultProfiles()
或者通过 spring.profiles.default
属性来改变默认的 profile
的名称。
2、PropertySource abstraction
Spring 的Environment
抽象提供了对属性源的可配置层次结构的搜索操作。为了充分解释,请考虑以下例子:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
在上面的代码片段中,我们看到了一种高级的询问 Spring 的方式,即foo
属性是否为当前环境定义。为了回答这个问题, Environment
对象会对一组 PropertySource
对象进行搜索。PropertySource
是对任何来源的 key-value(键-值)
对的简单抽象.Spring的 StandardEnvironment
对象配置了两个 PropertySource
对象 —— 一个表示 JVM 系统属性变量(通过 system.getproperties() 获取
),另一个代表系统环境变量(通过 system.getenv() 获取)
。
这些默认的属性源存在于
StandardEnvironmen
中,用于独立应用程序。StandardServletEnvironment
中包含了额外的默认属性源,包括servlet配置
和servlet上下文参数
。同样,StandardPortletEnvironment
也可以访问portlet
配置和portlet上下文参数
作为属性来源。两者都可以选择性地启用JndiPropertySource
。详情请见javadocs
。
具体地说,在使用 StandardEnvironment
时,如果系统在运行时系统属性变量或系统环境变量包含foo
属性,那么调用env.containsProperty("foo")
将返回 true
。
属性搜索是有层级结构的。默认情况下,系统属性优先于环境变量,所以如果
foo
属性碰巧在对env.getproperty(“foo”)
的调用中同时设置,系统属性值将“胜出”,并优先于环境变量返回。请注意,属性值不会被合并,而是被前面的条目完全覆盖。
对于一个常见的 StandardServletEnvironment
,完整的层次结构如下所列,优先级是由上而下的:
- ServletConfig parameters (例如在 DispatcherServlet 上下文)
- ServletContext parameters (web.xml 中
标签) - JNDI environment variables ("java:comp/env/" 词目)
- JVM system properties ("-D" 命令行参数)
- JVM system environment (system 环境变量)
最重要的是,整个机制是可配置的。也许你有一个定制的属性源,希望将其集成到这个搜索中。这个是没有问题的——简单地实现并实例化自己的 PropertySource
,并将其添加到当前Environment
的 PropertySource
集合中:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上面的代码中,MyPropertySource
在搜索中被添加了最高优先级。如果它包含一个foo
属性,它将在任何其他 PropertySource
中的任何foo
属性之前被检测并返回。MutablePropertySources API
公开了许多方法,这些方法允许精确地操纵一组属性源。
3、@PropertySource
@PropertySource
注解提供了一种方便的声明机制将 PropertySource
添加到 Spring 的 Environment
中。
给定一个包含键/值对testbean.name=myTestBean
的文件app.properties
.下面的@configuration
类使用@propertysource
把 app.properties
加载到 Spring 上下文中的Environment
里面。然后调用estBean.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;
}
}
在@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
将被用作默认值。如果没有指定默认值,并且解析不到 property,则会抛出一个IllegalArgumentException
异常。
在Java 8特性中
@propertysource
注释是可重复的。然而,所有这些@propertysource
注解都需要在相同的级别上声明:要么直接在配置类上,要么在同一个定制注解中作为元注解。直接注释和元注释的混合是不推荐的,因为直接注释将覆盖元注解。
4、Placeholder resolution in statements
在以前,elements 中的占位符的值只能通过JVM系统属性或环境变量来解决。现在不再是这种情况了。因为环境抽象是集成在整个容器中的,所以很容易通过它来解决占位符的解析。这意味着您可以以任何您喜欢的方式配置决议过程:更改通过系统属性和环境变量进行搜索的优先级,或者完全删除它们;在适当的时候添加您自己的属性源。
具体地说,不管用户属性的定义是什么,只要在环境中可用,下面的语句就可以工作:
原文地址:
- https://docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#beans-environment