Spring Core之 Environment Abstraction(环境抽象)


The Environment interface is an abstraction integrated in the container that models two key aspects of the application environment: profiles and properties.A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or with annotations. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default.Properties play an important role in almost all applications and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Map objects, and so on. The role of the Environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.

Enviroment接口是整合于容器中的一个抽象,它对应用程序环境的两个方面进行建模:profiles和properties。一个profile是一个在相应profile激活时被注册到容器中的命名bean定义逻辑组,不管是定义在XML中还是注解定义的bean都可以分配在一个profile中。Environment对象的和profiles之间扮演的角色就是决定哪一个是当前激活的profile,以及哪一个profile是默认激活的。Properties是一个非常重要的角色,它可能来自各种源文件,比如properties文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,等等。Environment对象与properties之间的作用是为用户提供一个便利的服务接口,能够配置属性源文件并且解析它们。

一、Bean Definition Profiles(Bean 定义Profiles)

Bean definition profiles provide a mechanism in the core container that allows for registration of different beans in different environments. The word, “environment,” can mean different things to different users, and this feature can help with many use cases, including:

  • Working against an in-memory datasource in development versus looking up that same datasource from JNDI when in QA or production.
  • Registering monitoring infrastructure only when deploying an application into a performance environment.
  • Registering customized implementations of beans for customer A versus customer B deployments.

Consider the first use case in a practical application that requires a DataSource. In a test environment, the configuration might resemble the following:

Bean定义Profiles提供了一种根据不同环境在核心容器中注册不同bean的机制。环境可以理解为为不同的用户做不同的事,该功能在许多使用场景都有帮助,包括:

  • 在开发环境中使用内存数据库,而不是和QA、生产环境使用同样的数据库;
  • 仅在将应用程序部署到演示环境中时才注册监控基础设施;
  • 为客户A使用自定义实现bean而不是客户B。

参考第一个使用案例,一个需要数据源的实际应用。在测试环境中,配置或许是类似这样的:

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

Now consider how this application can be deployed into a QA or production environment, assuming that the datasource for the application is registered with the production application server’s JNDI directory. Our dataSource bean now looks like the following listing:

现在考虑如何将此应用程序部署到 QA 或生产环境中,假设应用程序的数据源已在生产应用程序服务器的 JNDI 目录中注册。我们的 dataSource bean 现在看起来像下面的代码:

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

The problem is how to switch between using these two variations based on the current environment.

现在的问题是我们如何根据当前的环境在这两种数据源配置中进行切换:

Using @Profile
The @Profile annotation lets you indicate that a component is eligible for registration when one or more specified profiles are active. Using our preceding example, we can rewrite the dataSource configuration as follows:

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

The profile string may contain a simple profile name (for example, production) or a profile expression. A profile expression allows for more complicated profile logic to be expressed (for example, production & us-east). The following operators are supported in profile expressions:

  • !: A logical “not” of the profile
  • &: A logical “and” of the profiles
  • |: A logical “or” of the profiles

@Profile接收的值是一个描述profile名称的简单字符串(例如production)或者profile表达式。一个profile表达式可以表达更复杂的profile逻辑(例如,production & us-east),profile表达式支持以下操作符:

  • !:代表不在该profile下注册;
  • & :逻辑与,例如production & performence,表示在production和performence都激活的情况想才注册;
  • | :逻辑或

You can use @Profile as a meta-annotation for the purpose of creating a custom composed annotation. The following example defines a custom @Production annotation that you can use as a drop-in replacement for @Profile(“production”):

你可以使用 @Profile 作为元注解来创建自定义组合注解。以下示例定义了一个自定义 @Production 注释,你可以将其用作 @Profile(“production”) 的替代品:

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

@Profile can also be declared at the method level to include only one particular bean of a configuration class (for example, for alternative variants of a particular bean), as the following example shows:

@Profile 也可以在方法级别声明以仅包含配置类的一个特定 bean(例如,对于特定 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");
    }
}

Activating a Profile
Activating a profile can be done in several ways, but the most straightforward is to do it programmatically against the Environment API which is available through an ApplicationContext. The following example shows how to do so:

可以通过多种方式激活一个profile,但最直接的是针对环境 API 以编程方式进行,该 API 可通过 ApplicationContext 获得。以下示例显示了如何执行此操作:

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

In addition, you can also declaratively activate profiles through the spring.profiles.active property.Note that profiles are not an “either-or” proposition. You can activate multiple profiles at once. Programmatically, you can provide multiple profile names to the setActiveProfiles() method, which accepts String…​ varargs. The following example activates multiple profiles:

此外,你还可以通过 spring.profiles.active 属性声明性地激活配置文件。请注意,profiles不是“非此即彼”的命题。您可以一次激活多个配置文件。以编程方式,你可以为 setActiveProfiles() 方法提供多个配置文件名称,该方法接受 String… varargs。以下示例激活多个配置文件:

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

Default Profile
The default profile represents the profile that is enabled by default. If no profile is active, the dataSource is created. You can see this as a way to provide a default definition for one or more beans. If any profile is enabled, the default profile does not apply.Consider the following example:

默认配置文件代表默认启用的配置文件。如果没有配置文件处于激活状态,则创建数据源。您可以将其视为为一个或多个 bean 提供默认定义的一种方式。如果启用了任何配置文件,则默认配置文件不适用。参考以下示例:

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

二、PropertySource Abstraction(属性源抽象)

Spring’s Environment abstraction provides search operations over a configurable hierarchy of property sources. Consider the following listing:

Spring环境抽象提供在一个可在属性配置源层级中搜索的操作,参考以下代码:

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);

In the preceding snippet, we see a high-level way of asking Spring whether the my-property property is defined for the current environment. To answer this question, the Environment object performs a search over a set of PropertySource objects. A PropertySource is a simple abstraction over any source of key-value pairs, and Spring’s StandardEnvironment is configured with two PropertySource objects — one representing the set of JVM system properties (System.getProperties()) and one representing the set of system environment variables (System.getenv()).The search performed is hierarchical. By default, system properties have precedence over environment variables. So, if the my-property property happens to be set in both places during a call to env.getProperty(“my-property”), the system property value “wins” and is returned. Note that property values are not merged but rather completely overridden by a preceding entry.

在上面的代码片段中,可以看到我们使用了一种高级方法来询问spring当前环境是否含有my-property属性。为了回答这个问题,Environment对象基于PropertySource对象执行了一次搜索。一个PropertySource是配置信息键值对的简单抽象,并且spring的StandardEnvironment 配置有两个PropertySource 对象,一个代表JVM系统属性参数集,另一个代表应用系统环境变量。搜索的执行是分层级的,默认情况下,系统属性优先于环境变量。所以,如果my-property属性在系统属性和环境变量中都有,调用搜索时将返回系统属性值。请注意,属性值不会合并,而是被前面的条目完全覆盖。

Most importantly, the entire mechanism is configurable. Perhaps you have a custom source of properties that you want to integrate into this search. To do so, implement and instantiate your own PropertySource and add it to the set of PropertySources for the current Environment. The following example shows how to do so:

最重要的是,整个机制是可配置的。或许你有一个自定义属性想要整合到搜索中,因此你可以实现并实例化你的PropertySource并且将其添加到当前环境的PropertySources中,下面的代码展示了如何操作:

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

In the preceding code, MyPropertySource has been added with highest precedence in the search. If it contains a my-property property, the property is detected and returned.

在前面的代码中,MyPropertySource 已在搜索中以最高优先级添加。如果它包含 my-property 属性,则检测并返回该属性。

三、 Using @PropertySource(使用@PropertySource)

The @PropertySource annotation provides a convenient and declarative mechanism for adding a PropertySource to Spring’s Environment.Given a file called app.properties that contains the key-value pair testbean.name=myTestBean, the following @Configuration class uses @PropertySource in such a way that a call to testBean.getName() returns myTestBean:

@PropertySource提供了一个便利和声明式的机制来将添加一个PropertySource到spring的Environment中。给定包含键值对 testbean.name=myTestBean 的 app.properties 文件,以下 @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;
    }
}

Any ${…​} placeholders present in a @PropertySource resource location are resolved against the set of property sources already registered against the environment, as the following example shows:

@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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值