Spring Environment


Environment 概念

Environment 接口抽象出2个关键概念:profilesproperties.

profile是在XML和注解定义的bean定义元数据的逻辑组,Environment能决定当前使用哪一个逻辑组的bean定义元数据,那些是默认使用的。

Properties在所有的应用程序中占有非常重要的地位,它可以来自多种来源:属性文件,JVM系统属性,系统环境变量,JNDI,Servlet上下文参数,临时属性对象,Map对象等。Environment提供了方便的接口来供开发者来配置属性源以及从中获取属性。

BeanDefinition Profiles

Beandefinition profiles 提供了一种能够在不同环境注册不同的bean的机制,Environment对不同的用户来说可以是不同的意义:

  • 在开发环境注入本地数据源
  • 在性能检测开发环境注入监控组件
  • 为客户A和客户B部署注册bean的自定义实现

使用 @Profile

@Profile注解能够在多环境下指定那些bean需要注入,比如,在开发环境注入内嵌数据源,生产环境注入其他数据源:

@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的名称可能包含环境名称(比如:production),同时还支持复杂的表达式:production & us-east,支持以下表达式:

  • !:逻辑非
  • &: 逻辑与
  • |: 逻辑或

混合使用&|必须要用括号区分,比如:production & us-east | eu-central不能通过验证,需要使用如下:production & (us-east | eu-central)

可以使用@Profile作为原注解来开发组合注解,一下案例开发了一个@Production注解,其功能和@Profile("production")一致:

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

}

@Configuration标注的配置类也被@Profile标注,这个配置类下的所有@Bean以及配置类上的@Import注解倒入的bean都只会在指定的环境中生效,如果@Component@Configuration@Profile({"p1", "p2"})标注,则只有在p1p2环境被启用的时候它们才会被注入,如果被@Profile({"p1", "!p2"})标注,则只有当p1激活或p2不被激活时被注入。

@Profile 也能被标注到配置类的方法级别:

@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");
    }
}

激活Profile

激活当前应用配置有许多方式:但是最直接的方式是通过ApplicationContext获取Environment通过api来直接指定Profile

public void start(){
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles("development");
        ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
        ctx.refresh();
}

还可以通过spring.profiles.active属性来指定,还能通过系统的环境变量、JVM系统属性等方式配置该属性。如果在单元测试中,需要使用@ActiveProfiles来指定

tip:profile可以指定多个,它们并不是互相冲突的,比如可以通过setActiveProfiles()一次注册多个环境:

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

通过运行时参数:

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

默认的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();
    }
}

如果没有指定环境,则上述默认的DataSource将会被注入,指定则不会注入。

通过setDefaultProfiles()方法可以指定默认的环境,或者通过spring.profiles.default属性指定

PropertySource

Environment提供获取属性的api:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

上述代码通过Environment查询是否包含my-property属性,其内部是通过遍历 PropertySource 集合,PropertySource是key-value的简单抽象,Spring’的StandardEnvironment 配置了2个PropertySource对象–一个是JVM系统属性(System.getProperties()),另一个是操作系统环境变量(System.getenv())。

对于独立的应用,默认的PropertySource实现类是StandardEnvironment,StandardServletEnvironment可以获取到额外的一些熟悉,比如servlet配置和servlet上下文参数

当使用StandardEnvironment时,当调用env.containsProperty("my-property")时,如果系统属性或者环境变量包含my-property属性则返回true

查询属性的过程时:系统属性优先于环境变量,所以。如果my-property同时在系统属性和环境变量中设置,会优先使用系统属性,而不是合并相同的属性

如果需要自定义属性源可以实现PropertySource并添加到Environment中:

public void addPropertiySource(){
        ConfigurableApplicationContext ctx = new GenericApplicationContext();
        MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
        sources.addFirst(new MyPropertySource());
}

MutablePropertySources提供了一系列的API对PropertySource进行操作

使用@PropertySource注解

@PropertySource提供一种声明式的方式添加PropertySourceEnvironment

如果有一个叫app.properties的文件包含:testbean.name=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;
    }
}

通过上述配置之后,调用testBean.getName()返回myTestBean

@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需要预先注册到Environment中,如果未找到则默认使用default/path

@PropertySource是可重复使用的。


文章作者: Ubi-potato
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Ubi-potato !
评论
  目录