Spring核心技术(十三)——环境的抽象
本章将描写叙述一下Spring中针对环境的抽象。
Environment
是一个集成到容器之中的特殊抽象。它针相应用的环境建立了两个关键的概念:profile
和properties
.
profile是命名好的。当中包括了多个Bean的定义的一个逻辑集合,仅仅有当指定的profile被激活的时候,当中的Bean才会激活。不管是通过XML定义的还是通过注解解析的Bean都能够配置到profile之中。
而Environment
对象的角色就是跟profile相关联,然后决定来激活哪一个profile,还有哪一个profile为默认的profile。
properties在差点儿全部的应用当中都有着关键的数据。当然也可能导致多个数据源:property文件,JVM系统property。系统环境变量,JNDI,servlet上下文參数,ad-hoc属性对象,Map等。Environment
对象和property相关联,然后来给开发人员一个方便的服务接口来配置这些数据源,并正确解析。
Bean定义的profile
在容器之中。Bean定义profile是一种同意不同环境注冊不同bean的机制。环境的概念就意味着不同的东西相应不同的开发人员。并且这个特性能够在一下的一些场景非常有效:
- 解决一些内存中的数据源的问题,能够在不同环境訪问不同的数据源,开发环境,QA測试环境,生产环境等。
- 仅仅在开发环境来使用一些监视服务
- 在不同的环境。使用不同的bean实现
以下參考一个样例,以下的应用须要一个DataSource
,在一个測试的环境下,可能相似例如以下代码:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
如今考虑假设应用部署到QA环境或者生产环境,假设应用的数据源是server上的JNDI文件夹的话。我们的DataSource
可能会例如以下:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
问题就是怎样基于当前的环境来使用不同的配置。
过去,Spring的开发人员开发了非常多的方法来解决问题,通常都依赖于系统环境变量和XML中的<import/>
标签以及占位符${placeholder}
等来依据不同的环境解析当前的配置文件。
Bean 的 profile是容器的特性。也是该问题的解决方式。
假设我们泛化了我们一些特殊环境下引用的bean定义,我们能够将当中指定的Bean注入到特定的context之中,而不是全部的context之中。
非常多开发人员就希望能够在一种环境下使用Bean定义A,还有一种情况下使用Bean定义B。
@Profile
注解
@Profile
注解同意开发人员来表示一个组件是否适合在当前环境来进行注冊,仅仅有当在多个Profile之中,当前的Profile是激活的时候才干够进行注冊。使用前面的样例,代码能够进行例如以下调整:
@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");
}
}
假设配置了
@Configuration
的类同一时候配置了@Profile
。那么全部的配置了@Bean
注解的方法和@Import
注解的相关的类都会被传递为该Profile
除非这个Profile
激活了,否则Bean定义都不会激活。假设配置为@Component
或者@Configuration
的类标记了@Profile({"p1", "p2"})
,那么这个类当且仅当Profile
是p1或者p2的时候才会激活。假设某个Profile
的前缀是!
这个否操作符,那么@Profile
注解的类会仅仅有当前的Profile没有激活的时候才干生效。举例来说,假设配置为@Profile({"p1", "!p2"})
。那么注冊的行为会在Profile为p1
或者是Profile为非p2
的时候才会激活。
XML中Bean定义的profile
在XML中相相应配置是<beans/>
中的profile
属性。我们在前面配置的信息能够被重写到XML文件之中例如以下:
<beans profile="dev"
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="dev">
<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文件的配置提供很多其它便利。
激活profile
如今。我们已经更新了配置信息来使用环境抽象,可是我们还须要告诉Spring来激活详细哪一个Profile
。假设我们直接启动应用的话,如今就回抛出NoSuchBeanDefinitionException
异常,由于容器会找不到Spring的BeandataSource
。
有多种方法来激活一个Profile,最直接的方式就是通过编程的方式来直接调用Environment
API,ApplicationContext
中包括这个接口:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
额外的。Profile还能够通过spring.profiles.active
中的属性来通过系统环境变量,JVM系统变量,servlet上下文中的參数,甚至是JNDI的一个參数等来写入。在集成測试中,激活Profile能够通过spring-test
中的@ActiveProfiles
来实现。
须要注意的是,Profile的定义并非一种相互排斥的关系,我们全然能够在同一时间激活多个Profile的。编程上来说,为setActiveProfile()
方法提供多个Profile的名字就可以:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
也能够通过spring.profiles.active
来指定。
逗号分隔的多个Profile的名字:
-Dspring.profiles.active="profile1,profile2"
默认profile
默认的Profile就表示默认启用的Profile。
參考例如以下代码:
@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就不会激活。
默认的Profile的名字能够通过Environment
中的setDefaultProfiles()
方法或者是通过spring.profiles.default
属性来更改。
属性源抽象
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);
在上面的代码片段之中,我们看到一个high-level的查找Spring的foo
属性是否定义的一种方式。为了知道Spring中是否包括这个属性。Environment
对象会针对PropertySource
的集合进行查找。PropertySource
是针对一些key-value
的属性对的简单抽象,而Spring的StandardEnvironment
是由两个PropertySource
对象所组成的,一个代表的是JVM的系统属性(能够通过System.getProperties()
来获取),而还有一种则是系统的环境变量(通过System.getenv()
来获取。
)
这些默认的属性源都是
StandardEnvironment
的代表。能够用在不论什么应用之中。StandardServletEnvironment
则是包括Servlet配置的环境信息,当中会包括非常多Servlet的配置和Servlet上下文參数。StandardPortletEnvironment
相似于StandardServletEnvironment
。能够配置portlet上下文參数。能够參考其Javadoc了解很多其它信息。
详细的说,当使用StandardEnvironment
的时候。调用env.containsProperty("foo")
将返回一个foo
的系统属性,或者是foo
的执行时环境变量。
查询配置属性是按层次来查询的。默认情况下,系统属性优优于系统环境变量,所以假设
foo
属性在两个环境中都有配置的话,那么在调用env.getProperty("foo")
期间,系统属性值会优先返回。须要注意的是,属性的值是不会合并的。而是全然覆盖掉。
在一个普通的StandardServletEnvironment
之中,查找的顺序例如以下。优先查找* ServletConfig
參数(比方DispatcherServlet
上下文),然后是* ServletContext
參数(web.xml中的上下文參数),再然后是* JNDI
环境变量,JVM系统变量(”-D”命令行參数)以及JVM环境变量(操作系统环境变量)。
最重要的是,整个的机制是能够配置的。或许开发人员自己有些定义的配置源信息想集成到配置检索的系统中去。
没问题,仅仅要实现开发人员自己的PropertySource
并且将其加入到当前Environment
的PropertySources
之中就可以:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在上面的代码之中。MyPropertySource
被加入到检索配置的第一优先级之中。假设存在一个foo
属性,它将由于其它的PropertySource
之中的foo
属性优先返回。MutablePropertySources
API提供一些方法来同意精确控制配置源。
@PropertySource
注解
@PropertySource
注解提供了一种方便的机制来将PropertySource
添加到Spring的Environment
之中。
给定一个文件app.properties
包括了key-value对testbean.name=myTestBean
,以下的代码中。使用了@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;
}
}
不论什么的@PropertySource
之中形如${...}
的占位符。都能够被解析成Environment
中的属性资源,比方:
@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
是我们已经注冊到Environment
之中的资源。举例来说,JVM系统属性或者是环境变量的话,占位符会解析成对象的值。假设没有的话,default/path
会来作为默认值。假设没有指定默认值。并且占位符也解析不出来的话,就会抛出IllegalArgumentException
。
占位符解析
从历史上来说,占位符的值是仅仅能针对JVM系统属性或者环境变量来解析的。可是如今不是了,由于环境抽象已经继承到了容器之中,如今非常easy将占位符解析。
这意味着开发人员能够随意的配置占位符:
- 调整系统变量还有环境变量的优先级
- 添加自己的属性源信息
详细的说,以下的XML配置不会在意customer
属性在哪里定义,仅仅有这个值在Environment
之中有效就可以:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>