本章将描述一下Spring中针对环境的抽象。

Environment是一个集成到容器之中的特殊抽象,它针对应用的环境建立了两个关键的概念:profileproperties.

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环境或者生产环境,假设应用的数据源是服务器上的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,最直接的方式就是通过编程的方式来直接调用EnvironmentAPI,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并且将其加入到当前EnvironmentPropertySources之中即可:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

在上面的代码之中,MyPropertySource被添加到检索配置的第一优先级之中。如果存在一个foo属性,它将由于其他的PropertySource之中的foo属性优先返回。MutablePropertySourcesAPI提供一些方法来允许精确控制配置源。

@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系统属性或者环境变量来解析的。但是现在不是了,因为环境抽象已经继承到了容器之中,现在很容易将占位符解析。这意味着开发者可以任意的配置占位符:

  • 调整系统变量还有环境变量的优先级
  • 增加自己的属性源信息

具体的说,下面的XML配置不会在意customer属性在哪里定义,只有这个值在Environment之中有效即可:

<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>

Spring核心技术(十三)——环境的抽象的更多相关文章

  1. Spring核心技术(六)——Spring中Bean的生命周期

    前文已经描述了Bean的作用域,本文将描述Bean的一些生命周期作用,配置还有Bean的继承. 定制Bean 生命周期回调 开发者通过实现Spring的InitializeingBean和Dispos ...

  2. Spring核心技术(四)——Spring的依赖及其注入(续二)

    前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置.本文将继续描述玩全部的依赖信息. 使用 depends-on 如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也 ...

  3. Spring核心技术

    这是第二次看关于Spring的资料,由于刚開始学习Spring的时候是边看视频边学习的,所以更注重的是实现代码,可是对宏观的掌握还是不够,这次主要从宏观的角度来分析一下Spring. 什么是Sprin ...

  4. Spring ——简介及环境搭建跑通Hello

    Spring Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.是为了解决企业应用程序开发复杂性而创建的.框架的主要优 ...

  5. Spring核心技术(五)——Spring中Bean的作用域

    前文概述了Spring的容器,Bean,以及依赖的一些信息,本文将描述一下Bean的作用域 Bean的作用域 当开发者定义Bean的时候,同时也会定义了该如何创建Bean实例.这些具体创建的过程是很重 ...

  6. Spring 核心技术(3)

    接上篇:Spring 核心技术(2) version 5.1.8.RELEASE 1.4 依赖 典型的企业应用程序不会只包含单个对象(或 Spring 术语中的 bean).即使是最简单的应用程序也是 ...

  7. Spring 核心技术(4)

    接上篇:Spring 核心技术(3) version 5.1.8.RELEASE 1.4.2 依赖关系及配置详情 如上一节所述,你可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者 ...

  8. Spring 核心技术(6)

    接上篇:Spring 核心技术(5) version 5.1.8.RELEASE 1.5 Bean 作用域 创建 bean 定义时,你创建了一种用于创建 bean 定义中定义的类实例的方法.bean定 ...

  9. Spring 核心技术(7)

    接上篇:Spring 核心技术(6) version 5.1.8.RELEASE 1.6 定制 Bean 的特性 Spring Framework 提供了许多可用于自定义 bean 特性的接口.本节将 ...

随机推荐

  1. shell date 相关使用

    #格式化输出                                                                                $> date +&q ...

  2. js得到当前页面的url信息

    所有的代码都是可用,而且附了图片的,不过是直接用我自己的文章地址,所以有些显示的有点奇怪. 大家可以找个网址试试代码是否可行. 1,设置或获取对象指定的文件名或路径. console.log(wind ...

  3. fileReader 上传图片

    function getImgSrc(target, callback) { if (window.FileReader) { var oPreviewImg = null, oFReader = n ...

  4. MySQL 如何在一个语句中更新一个数值后返回该值 -- 自增长种子竞态问题处理

    什么是竞态问题? 假设有一个计数器,首先当前值自增长,然后获取到自增长之后的当前值.自增长后的值有可能被有些操作用来当做唯一性标识,因此并发的操作不能允许取得相同的值. 为什么不能使用使用UPDATE ...

  5. PHP超全局变量、魔术变量和魔术方法

    PHP超全局变量 $_GET 通过get方式传递的值(通过 URL 参数传递给当前脚本的变量的数组.) $_POST 通过post形式传递的值(当 HTTP POST 请求的 Content-Type ...

  6. GreenDao 数据库升级 连接多个DB文件 或者指定不同的model&dao目录

    相信很多人都用过greenDao 今天 我抽空总结下使用的时候一些小东西吧 废话不多说 下边就GreenDao 的使用遇到的问题以及解决方案记录一下吧. 1.greendao 指定不同的生成目录: S ...

  7. HDU 3001 Travelling (状压DP,3进制)

    题意: 给出n<=10个点,有m条边的无向图.问:可以从任意点出发,至多经过同一个点2次,遍历所有点的最小费用? 思路: 本题就是要卡你的内存,由于至多可经过同一个点2次,所以只能用3进制来表示 ...

  8. LibreOJ #515. 「LibreOJ β Round #2」贪心只能过样例

    题目描述 一共有 nnn个数,第 iii 个数 xix_ix​i​​ 可以取 [ai,bi][a_i , b_i][a​i​​,b​i​​] 中任意值.设 S=∑xi2S = \sum{{x_i}^2 ...

  9. 苹果ATS Win2008 R2 IIS7.5 HTTPS 证书的那些可能遇到的坑

    前言:工作这么多年,每一次要弄https 都和苹果有关,上一次是苹果app的企业安装形式,ios7后 .plist 文件必须在一个https路径. 这一次则是苹果的ATS计划,无疑这是在推动网络安全上 ...

  10. guruguru

    6576: guruguru 时间限制: 1 Sec  内存限制: 128 MB提交: 28  解决: 12[提交] [状态] [讨论版] [命题人:admin] 题目描述 Snuke is buyi ...