环境抽象

Spring环境变量,方便导入数据。

Environment接口提供两个关于应用环境的数据模型:profiles和properties。

profile是一个有名字的在激活时要被注册的bean定义的组。
既可以用xml定义profile也可以用注解定义。
Environment对象用于确定当前激活的profile,或者默认profile。

Properties在几乎所有的应用中扮演非常重要的角色,而且可以来自不同的地方:
properties文件、JVM系统属性、系统环境变量、JNDI、servlet容器参数等。
Environment对象之间的关系是提供用户便捷的属性源和属性解析接口。

用于bean定义的profile:

使用@Profile,作用在类级别:

@Configuration
@Profile("development")
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表达式:
@Profile(“production & us-east”)

如果要多个运算符一起用,就必须使用圆括号
@Profile(“production & (us-east | eu-central)”)

其它操作符也是支持的:

  • !: 逻辑“not”
  • &: 逻辑“and”
  • |: 逻辑“or”

可以将@Profile作为元注解定制自己的注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

注意:默认不对被注解类的@Bean方法和@Import注解生效,除非一个或多个对应的profile被激活。

@Profile注解也可以用于bean工厂方法:

@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development") 
    public DataSource standaloneDataSource() {
        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("dataSource")
    @Profile("production") 
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

注意:如果bean工厂方法有重载的,则所有重载工厂方法的@Profile注解都要保持一致,
如果不一致就会取第一个声明生效,而且也不能用于选择重载方法。
应该使用不同的bean工厂方法名生成同一个name属性的bean,就像上面的例子。

XML配置里的profile:

<beans>元素的profile属性:

<beans profile="development"
    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>

也可以在同一个xml配置文件里面嵌套<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="development">
        <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>

xml配置不支持profile表达式,但是可以通过嵌套实现逻辑and:

<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="production">
        <beans profile="us-east">
            <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
        </beans>
    </beans>
</beans>

production and us-east同时激活才会暴露dataSource bean。

激活profile:

可以通过ApplicationContext的Environment API编程式激活profile:

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

或者在property里面添加spring.profiles.active属性。

可以同时激活多个profile:

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

或环境变量中通过逗号分隔:

-Dspring.profiles.active="profile1,profile2"

默认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,当有profile被激活时就不创建dataSource。
可以通过Environment API 的setDefaultProfiles()方法设置默认profile名称,
或者声明spring.profiles.default属性。

PropertySource抽象:

Environment对象在一组可配置的层级的PropertySource对象中搜索属性值,PropertySource是对于键值对的简单抽象。
Spring的StandardEnvironment由两个PropertySource对象配置成,一个表示JVM系统属性,另一个表示系统环境变量。

搜索操作具有层级关系,属性变量有优先级,默认系统属性级别大于环境变量,比如my-property属性同时出现在系统属性和环境变量中,则调用env.getProperty(“my-property”)就会返回系统属性中的值。

在StandardServletEnvironment中,完整的优先级顺序是:(1为最高)

  1. ServletConfig parameters (例如: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 (操作系统环境变量)

上面的整个机制都是可以配置的,比如添加自己的PropertySource

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

上面的代码片段中,MyPropertySource被设置成了最高优先级。
MutablePropertySources API暴露了一些精确操作PropertySource的方法。

使用@PropertySource注解:

方便快捷地向Environment添加PropertySource对象的方法,参数是某个含有键值对信息的文件:

@Configuration
@PropertySource("classpath:app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

只要在app.properties文件里添加testbean.name=beanName就可给testBean命名。
任何${…​}占位符都会在读取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,就使用冒号后面的default/path作为值,冒号后面是默认值。
如果不提供默认值,当my.placeholder无法解析时就会抛出IllegalArgumentException异常。

@PropertySource可以重复声明,只是需要在同一等级,只能选直接使用还是用定制的注解。混用不同等级的注解时,直接使用@PropertySource会覆盖定制的注解。

语句中的占位符的解析:

历史上,占位符只能解析系统属性和环境变量。
现在只要是在Environment中存在的值都可以解析,无关属性在什么地方:

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

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注