Spring Environment abstraction

Environment 是 Spring 容器中对于应用环境两个关键因素的一个抽象。它们分别是:Profilesproperties

Profile 是一个 bean 定义命名的逻辑分组。容器中可以配置多个 Profile,但是需要指定 profile 才会把这个 Profile 下的分组 bean 定义注册到容器中。 无论 beans 是定义在 XML 还是 注解里面都可以分配给一个 ProfileEnvironment 对象与 Profile 关系是确定哪些 Profiles(if any)当前是有效的,哪些 Profiles(如果有的话)应该在默认情况下是有效的。

Properties在几乎所有应用程序中都扮演着重要的角色,并且可能来自各种各样的来源:properties 文件JVM系统属性系统环境变量JNDIServlet Context 参数ad-hoc Properties 对象Map 等等。EnvironmentProperties 的关系是为用户提供一个方便的服务接口,用于配置属性源并从它们中解析属性。

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 默认帮我们设置 profiledefault:

@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将不适用。

可以通过EnvironmentsetDefaultProfiles() 或者通过 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,并将其添加到当前EnvironmentPropertySource集合中:

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类使用@propertysourceapp.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

你可能感兴趣的:(Spring Environment abstraction)