Environment abstraction

##Environment abstraction (环境抽象) Environment是容器重要的抽象,它集成了应用两个重要的方面:profiles和properties;java

一个Profile是一组已命名的有逻辑的bean定义,只有当特定的profile启动时,它们才在容器里注册.Beans能够经过xml或者注解来指定其profile值.Environment对象在profiles中的角色决定目前哪一个profile会被启动,哪一个profile是默认启动.web

Properties在全部的应用中都起重要做用,它能够从如下资源中产生:properties文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,特定的Properties对象,Maps,等.Environment对象与属性的关系是提供使用者一个合适的服务接口来配置属性资源,并从中释放属性. ###7.13.1 Bean definition profiles bean定义中的Profilesspring

bean定义的profiles是一个核心容器的机制,容许在不一样的环境里注册不一样的bean.这个单词"environment"对不一样的用户表明不一样的事物,这个功能在不少场景下给你提供帮助,包括:sql

  • 开发中使用内存数据库 VS 在QA或production中使用从JNDI中查找相同的数据源.
  • 在一个复杂的环境部署项目如何注册监控组件.
  • 部署时,对消费者A或B注册不一样的beans的自定义实现

让我讨论第一个例子要求一个特定的数据源.在一个测试环境中,这个配置可能以下:数据库

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}

如今让咱们思考一下如何在一个QA或生产环境部署应用,说明这个应用的数据源将会注册到生产应用服务器的JNDI目录里.咱们的数据源可能以下:编程

@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

难点在于如何根据当前环境来使用这两种Bean.长久以来,pring用户已经有不少方式来实现它,通常而言是一大堆系统环境变量和包含了${placeholder}令牌的xml的 <import/>元素,这样就能够根据环境变量的值来释放正确的配置文件路径.Bean定义的profile是核心容器的功能,能够为该问题提供解决方案.服务器

若是咱们归纳上面的而特定环境bean定义的使用状况,咱们最终须要在特定的上下文中使注册特定的bean定义,而不是其余的.你能够说你要在A环境中注册一个bean定义的profile,并在B环境中注册另外一个profile.让我看看如何更新咱们的配置来反应这个需求.app

####@Profile Profile注解容许你代表一个逐渐有资格在一个或多个特定的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");
    }
}

像之前提到的,你可使用编程的JNDI查找:可使用JndiTemplate/JndiLocatorDelegate 帮助者或直接使用上文中展现的JNDI InitialContext,但不要使用JndiObjectFactoryBean,由于它强制要求你将返回类型设置为FactoryBean.ui

@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启动.若是一个@Component或@Configuration类被标志为@Profile({"p1","p2"}),那么除非是'p1'或'p2'已经激活,不然这个类不会被注册或处理.若是一个特定profile加上了非操做符(!),那么只要这个特定的profile不激活,该注解下的bean都会被注册.例如,特定的@Profile({"p1","p2"}),当profile'p1'激活或profile'p2'未激活时都会发生注册.

###7.13.2 XML bean definition profiles

在xml部分中profile是<Beans>元素的属性.上面的简单配置能够用如下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已经改造,当出现一个以上的<beans>元素时,是容许这么构造的.这能够在避免xml文件的混乱性并提升其灵活性; ####Activing a profile 启动一个profille 如今咱们已经更新了配置,咱们只须要肯定spring中哪一个profile要启动.若是咱们如今启动咱们的实例应用,咱们会发现一个NoSuchBeanDefinitionException的异常抛出,由于容器没法找到名为dataSource的bean. 能够用好几种方法启动一个profile,最直接的方式就是经过一个ApplicationContext获得的Environment对象的API方法来动态实现:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

另外,profiles能够经过spring.profiles.active属性来申明式启动,它能够经过环境变量,JVM系统属性,web.xml里的servlet上下文参数,或者JNDI里的一个键值.在集成测试里,能够经过@ActiveProfiles来显示的设置一个spring-test模块的激活的profile.

记住,配置文件不是一个是或不是的命题;它能够一次启动多个混合profiles.编程式的,简单的向setActiveProfiles()方法提供混合profile的名称,该方法能够接受任意string值.

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

声明式的,spring.profiles.active也能够接受一个profile名称的标点分割集合.

spring.profiles.active="profile1,profile2"

####Default profile 默认profile default 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的名字可使用setDefaultProfiles()来替换,或者直接使用spring.profiles.default属性来申明.

##@7.13.3 PropertySource abstraction 属性资源抽象 spring的环境抽象经过属性资源的配置结构提供了搜索操做.要详细解释,思考如下例子:

ApplicationContext ctx=new GenericAppliactionContext();
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是全部键值对的简单抽象,而且spring的StandardEnvironment是由两个PropertySource对象决定的,一个是JVM系统属性(如 System.getProperties()),另外一个是系统环境变量(例如 System.getenv()).

对StandardEnvironment展示的这些属性资源,能够用于单独的应用中.StandardServletEnvironment能够操做其余额外的默认属性资源,包括servlet配置和servlet上下文参数.StandardPortletEnvironment一样能够向访问配置资源同样访问portlet配置和portlet上下文参数.两者均可以访问JndiPropertySource.查看文档细节.

具体而言,当使用StandardEnvironment,若是一个foo的系统属性或foo 的 环境变量在运行时存在那么调用env.containsProperty("foo")将返回true;

这个搜索动做有层次性.通常,系统属性的优先级高于环境变量,因此当foo属性在两者都已设置且调用env.getProperty("foo")方法时,这个系统变量值将会获胜并在返回是覆盖掉环境变量.记住属性值不会被合并,只会更加优先级来重写.

对于一个普通的StandardServletEnvironment来讲,如下全部的层次都会查找,高优先级的实体在上面:

  1. ServletConfig parameters (if applicable,e.g. in case of a DispatcherServlet context)
  2. ServletContext parameters(web.xml context-param entries)
  3. JNDI environment variables("java:comp/env/" entries)
  4. JVM system properties ("-D" command-line arguments)
  5. JVM system environment(operating system environment varibales)

最重要的是,整个机制是可配置的.或许你有个自定义属性资源,你打算集成到搜索中.没有任何问题,简单的实现并实例化你本身的PropertySource,并将它加入当前Environment的PropertySource集合中.

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

在上面的代码中,MyPropertySource被加入为搜索中的更高优先级.若是他含有一个foo属性,它将会被检测并在其余PropertySource以前返回.这个MutablePropertySource API暴露了大量的方法容许对属性资源进行操做.

###7.13.4 @PropertySource 属性资源

属性资源注解提供了一个适合的,显示的机制,它能够吧一个PropertySource添加到spring的Environment中.

给定一个文件"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;
    }
}

任何在@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"将会用作一个默认值.若是没有指定的默认值或属性没被转化,那么将抛出 IllegalArgumentException的异常.

###7.13.5 申明中的占位符解决方案 曾经,在元素中的占位符的值只能经过JVM系统属性或环境变量转化.如今不一样了.由于环境抽象已经整合到容器里,这很容易经过它来解决占位符.这意味着你能够以你喜欢的方式配置解决方案:经过系统环境和环境变量的搜索优先级修改或者移除他们;向混合资源适当的添加你本身的属性资源;

具体的,只要customer在Environment中能够得到,那么下面的在申明使用了customer属性的地方固然就会工做.

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>
相关文章
相关标签/搜索