由于掘金文章存在字数限制,本文拆开了各个章节分开发布,全文可点击以下链接进行阅读:blog.omghaowan.com/archives/sp...
Environment
Environment
(接口)表示当前应用环境的抽象,在Spring
定义中它包含两大模块:Profiles
和Properties
。
Profiles
相当于逻辑bean
定义分组的抽象(bean
在定义时可被分配一个profile
(通过XML
或者Java
注解)),只有profile
被激活分组才能将它所包含的bean
定义注册到容器中。我们可以使用(与Profiles
所关联的)Environment
决定默认的profile
和激活的profile
。Properties
相当于应用配置属性的抽象,其中属性来源可源于:properties files
、JVM system properties
、system environment variables
、JNDI
、servlet context parameters
、ad-hoc Properties objects
、Map objects
等。我们可以使用Environment
获取、解析和配置与其所关联的Properties
属性。
Profiles
通过Profiles
机制我们可以在不同环境Environment
中将不同的bean
注册到容器,例如:
- 对开发、测试和生产环境分别使用不同的数据源进行工作。
- 在生产环境注册监控设施,而在开发和测试环境下忽略监控设施的注册。
在使用上,我们仍然可以使用XML
和Java
注解的方式进行配置,不过由于篇幅的原因这里就不对XML
的方式展示了,有兴趣的读者可以查阅相关资料。那么,下面我们将围绕着Java
注解的方式展示说明。
在基于Java
注解的配置方式中,我们可以将@Profile
注解标注在类上或者@Bean
方法上,使得它们只有在对应的profile
被激活时才注册到容器中。
-
将
@Profile
注解标注在配置类如果将
@Profile
注解标注在@Configuration
类,那么它所包含的所有@Bean
方法和通过@Import
引入的类都只能在对应的profile
被激活时才能注册到容器中。java@Configuration @Profile("development") public class DevConfig { @Bean public DataSource dataSource() { return ...; } } @Configuration @Profile("production") public class ProConfig { @Bean public DataSource dataSource() throws Exception { return ...; } }
-
将
@Profile
注解标注在@Bean
方法java@Configuration public class AppConfig { @Bean("dataSource") @Profile("development") public DataSource devDataSource() { return ...; } @Bean("dataSource") @Profile("production") public DataSource proDataSource() throws Exception { return ...; } }
除了直接使用
@Profile
注解外,我们还能通过将@Profile
变成元注解来衍生出各种自定义的组合注解使其生效,例如直接使用如下的@Production
来代替@Profile("production")
:
java@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Profile("production") public @interface Production { }
其中所设置的profile
值,我们可以将它设置为简单profile
名字(例如development
或者production
),也可以将它设置为profile
表达式。其中,通过profile
表达式我们就可以设置更多复杂的逻辑profile
去表达我们想要指定的注册范围,具体如下所示:
!
:profile
值的逻辑"否"(NOT
)&
:profile
值的逻辑"与"(AND
)|
:profile
值的逻辑"或"(OR
)
需要注意,我们不能在不使用括号的情况下直接使用混合
&
和|
,例如production & us-east | eu-central
是无效表达式,要将它改成production & (us-east | eu-central)
才能成立。
在完成上述对bean
的分组后我们就可以向Spring
指定我们需要激活的profile
了。而对激活profile
的指定存在几种不同的方式,最直接方式的就是使用Environment
接口(通过ApplicationContext
获取)来设置,即:
java
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
另外,我们还可以通过spring.profiles.active
属性来指定需要激活的profile
(可以在system environment variables
或者JVM system properties
等配置中指定),例如:
java
-Dspring.profiles.active="development"
对于激活
profile
的设置完全是可以多选的,例如:
通过
Environment
接口设置多个激活profile
(用逗号(,
)隔开):
javactx.getEnvironment().setActiveProfiles("profile1", "profile2");
通过
spring.profiles.active
属性设置多个激活profile
(用逗号(,
)隔开):
java-Dspring.profiles.active="profile1,profile2"
需要注意,如果我们没有设置激活profile
,那么可能会由于某些bean
的缺失导致Spring
在启动时或者在运行时发生错误。对于这种情况,我们也可以这是默认的profile
,即如果没有显式设置激活profile
,那么默认profile
组内的bean
就会被创建;否则默认profile
组内的bean
就会被忽略。默认情况下,对于默认profile
的值为default
,我们也可以使用Environment#setDefaultProfiles
方法或者设置spring.profiles.default
属性对其进行修改。
java
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return ...;
}
}
更多详情可阅读:
Properties
在Properties
模型中会将不同的属性来源(PropertySource
)组合为层级结构(可配置的),但在访问时我们不需要管每个配置在具体哪个层级,而是使用Spring
的Environment
接口(抽象)来方法,即:
java
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
会将每个属性来源构造为PropertySource
,也就是说在查询属性时实际上就是使用Environment
是在一系列PropertySource
中寻找出对应的属性。例如,Spring
中的StandardEnvironment
就包含了两个PropertySource
,其一为JVM system properties
属性集合(可通过System.getProperties()
获取);其二为system environment variables
属性集合(可通过System.getenv()
获取)。如果在JVM system properties
或者在system environment variables
中包含my-property
属性,那么env.containsProperty("my-property")
的调用就会返回true
。
对于
PropertySource
,我们可以将它看作是一个属性来源中的键值对(集合)。
需要注意,实际上Environment
是按照层级从上往下查询的,如果在上层PropertySource
中查询到结果则会立刻返回(顶层优先法则),而不会继续往下查询并执行类似于合并的操作(也可以看作是顶层的属性将底层的覆盖了)。
默认情况下,
JVM system properties
属性优先级高于system environment variables
属性。对于常见的
StandardServletEnvironment
,它完整的层级优先级如下所示(顶层是最高优先级):
- ServletConfig parameters (if applicable --- for example, in case of a DispatcherServlet context)
- ServletContext parameters (web.xml context-param entries)
- JNDI environment variables (java:comp/env/ entries)
- JVM system properties (-D command-line arguments)
- JVM system environment (operating system environment variables)
除此之外,这种层级结构也是可配置的。比如,如果我们想将自定义的属性来源加入到Environment
的查询中(类似于配置中心),那么我们只需将自定义的PropertySource
实例加入到当前Environment
的PropertySources
集合即可。
java
ConfigurableApplicationContext ctx = new GenericApplicationContext();
// The MutablePropertySources API exposes a number of methods that allow for precise manipulation of the set of property sources.
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上述代码中,MyPropertySource
属性来源就已经被设置为Environment
查询的最高优先级。
或者我们也可以采用一种更简单的方式,即使用@PropertySource
注解将PropertySource
添加到Spring
的Environment
中。
java
/**
* Given a file called app.properties that contains the key-value pair testbean.name=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
注解中的资源路径中的占位符${...}
,Spring
也可以使用已经注册到Environment
中的属性来源(PropertySource
)来进行解析。
java
@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
属性已经随着优先级更高的属性来源(PropertySource
)被注册到Environment
中,它将会得到解析;否则my.placeholder
属性会使用默认值default/path
(如果不存在默认值,则会抛出IllegalArgumentException
异常)。
更多详情可阅读: