2.13 Spring Framework 5.x 之环境抽象

1.13 环境抽象

该Environment接口是集成在容器模型应用环境的两个关键方面的抽象:profiles 和 properties

profile 配置文件是仅在给定配置文件处于活动状态时才向容器注册的bean定义的命名逻辑组。可以将Bean分配给配置文件,无论是以XML还是使用注解定义。Environment与配置文件相关的对象的作用是确定哪些配置文件(如果有)当前处于活动状态,以及默认情况下哪些配置文件(如果有)应处于活动状态。

Properties在几乎所有应用程序中都发挥着重要作用,可能源自各种源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,ad-hoc Properties对象,Map对象等。Environment与属性相关的对象的作用是为用户提供方便的服务接口,用于配置属性源和从中解析属性。

1.13.1. Bean 定义 Profiles

Bean定义配置文件在核心容器中提供了一种机制,允许在不同环境中注册不同的bean。“环境”这个词对不同的用户来说意味着不同的东西,这个功能可以帮助解决许多用例,包括:

  • 在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目录。我们的dataSourcebean现在看起来如下:

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

问题是如何根据当前环境在使用这两种变体之间切换。随着时间的推移,Spring用户已经设计了许多方法来完成这项工作,通常依赖于系统环境变量和包含${placeholder}令牌的XML 语句的组合,这些令牌根据环境变量的值解析为正确的配置文件路径。Bean定义配置文件是核心容器功能,可为此问题提供解决方案。

如果我们概括了前面特定于环境的bean定义示例中显示的用例,我们最终需要在某些上下文中注册某些bean定义,而在其他上下文中则不需要。您可以说您希望在情境A中注册特定的bean定义配置文件,在情况B中注册不同的配置文件。我们首先更新配置以反映此需求。

运用 @Profile

通过@Profile 注释,您可以指示当一个或多个指定的配置文件处于活动状态时,组件符合注册条件。使用前面的示例,我们可以dataSource按如下方式重写配置:

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

如前所述,使用@Bean方法,您通常选择使用编程JNDI查找,使用Spring的JndiTemplate/ JndiLocatorDelegatehelper或InitialContext前面显示的直接JNDI 用法,但不使用JndiObjectFactoryBean 变量,这会强制您将返回类型声明为FactoryBean类型。

配置文件字符串可以包含简单的配置文件名称(例如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注释。如果a @Component或@Configuration类被标记@Profile({“p1”, “p2”}),则该类未被注册或处理,除非已激活配置文件’p1’或’p2’。如果给定的配置文件以NOT运算符(!)作为前缀,则仅在配置文件未激活时才注册带注释的元素。例如,@Profile({“p1”, “!p2”})如果配置文件“p1”处于活动状态或配置文件“p2”未激活,则会发生注册。

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

该standaloneDataSource方法仅在development配置文件中可用。
该jndiDataSource方法仅在production配置文件中可用。

使用@Profileon @Bean方法,可能会应用特殊方案:对于@Bean相同Java方法名称的重载方法(类似于构造函数重载),@Profile需要在所有重载方法上一致地声明条件。如果条件不一致,则只有重载方法中第一个声明的条件才重要。因此,@Profile不能用于选择具有特定参数签名的重载方法。同一个bean的所有工厂方法之间的分辨率遵循Spring的构造函数解析算法在创建时。

如果要定义具有不同配置文件条件的备用Bean,请使用通过使用@Beanname属性指向同一bean名称的不同Java方法名称,如上例所示。如果参数签名都是相同的(例如,所有变体都具有no-arg工厂方法),这是首先在有效的Java类中表示这种排列的唯一方法(因为只有一个特定名称和参数签名的方法)。

XML Bean定义配置文件

XML对应物是元素的profile属性。我们之前的示例配置可以在两个XML文件中重写,如下所示:

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

也可以避免在同一文件中使用split和nest 元素,如下例所示:

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

在spring-bean.xsd受到了制约,使这些元素只能作为文件中的最后一个人。这应该有助于提供灵活性,而不会在XML文件中引起混乱。

XML对应方不支持前面描述的配置文件表达式。但是,可以通过使用!运算符来否定轮廓。也可以通过嵌套配置文件来应用逻辑“和”,如以下示例所示:

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

在前面的示例中,dataSource如果两个production和 us-east配置文件都处于活动状态,则会公开Bean 。

Activating 一个Profile

现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件处于活动状态。如果我们现在开始我们的示例应用程序,我们会看到NoSuchBeanDefinitionException抛出,因为容器找不到名为的Spring bean dataSource。

激活配置文件可以通过多种方式完成,但最直接的方法是以编程方式对Environment可通过API提供的API进行操作 ApplicationContext。以下示例显示了如何执行此操作:

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

此外,您还可以通过spring.profiles.active属性声明性地激活配置文件,该 属性可以通过系统环境变量,JVM系统属性,servlet上下文参数web.xml或甚至作为JNDI中的条目来指定(请参阅PropertySource抽象)。在集成测试中,可以使用模块中的@ActiveProfiles注释声明活动配置文件spring-test(请参阅使用环境配置文件的上下文配置)。

请注意,配置文件不是“任何 - 或”命题。您可以一次激活多个配置文件。以编程方式,您可以为setActiveProfiles()方法提供多个配置文件名称,该 方法接受String… varargs。以下示例激活多个配置文件:

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

声明性地,spring.profiles.active可以接受以逗号分隔的配置文件名称列表,如以下示例所示:

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

默认配置文件
默认配置文件表示默认启用的配置文件。请考虑以下示例:

@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则创建该配置文件。您可以将此视为一种为一个或多个bean提供默认定义的方法。如果启用了任何配置文件,则默认配置文件不适用。

您可以通过更改默认的配置文件的名称setDefaultProfiles()上Environment,或者声明,通过使用spring.profiles.default属性。

1.13.2 PropertySource抽象化

Spring的Environment抽象提供了对可配置的属性源层次结构的搜索操作。请考虑以下列表:

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

在前面的代码片段中,我们看到了一种向Spring询问是否my-property为当前环境定义属性的高级方法。要回答此问题,Environment对象将对一组对象执行搜索PropertySource 。A PropertySource是对任何键值对源的简单抽象,Spring StandardEnvironment 配置有两个PropertySource对象 - 一个表示JVM系统属性集(System.getProperties()),另一个表示系统环境变量集(System.getenv()

这些默认属性源StandardEnvironment适用于独立应用程序。StandardServletEnvironment 填充了其他默认属性源,包括servlet配置和servlet上下文参数。它可以选择启用a JndiPropertySource。有关详细信息,请参阅javadoc。

具体来说,当您使用时StandardEnvironment,env.containsProperty(“my-property”) 如果运行时存在my-property系统属性或my-propertyi环境变量,则调用将返回true 。

执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果my-property在调用期间恰好在两个位置都设置了属性env.getProperty(“my-property”),则系统属性值“wins”并返回。请注意,属性值未合并,而是由前面的条目完全覆盖。

对于公共StandardServletEnvironment层次结构,完整层次结构如下,最高优先级条目位于顶部:

  1. ServletConfig参数(如果适用-例如,在DispatcherServlet上下文的情况下)
  2. ServletContext参数(web.xml context-param条目)
  3. JNDI环境变量(java:comp/env/条目)
  4. JVM系统属性(-D命令行参数)
  5. JVM系统环境(操作系统环境变量)

最重要的是,整个机制是可配置的。您可能希望将自定义的属性源集成到此搜索中。为此,请实现并实例化您自己的PropertySource并将其添加到PropertySources当前的集合中Environment。以下示例显示了如何执行此操作:

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

在上面的代码中,MyPropertySource在搜索中添加了最高优先级。如果它包含my-property属性,则检测并返回该属性,以支持my-property任何其他属性PropertySource。所述 MutablePropertySources API公开了大量的,其允许该组的属性源的精确操作方法。

1.13.3 运用@PropertySource

该@PropertySource 注解提供便利和声明的机制添加PropertySource 到Spring的Environment。

给定一个app.properties包含键值对的文件testbean.name=myTestBean,以下@Configuration类使用以下@PropertySource方式调用testBean.getName()return 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则抛出a。

该@PropertySource注释是可重复的,根据Java的8约定。但是,所有这些@PropertySource注释都需要在同一级别声明,可以直接在配置类上声明,也可以在同一自定义注释中作为元注释声明。不建议混合直接注释和元注释,因为直接注释有效地覆盖了元注释。

1.13.4 占位符决议在声明中

从历史上看,元素中占位符的值只能针对JVM系统属性或环境变量进行解析。这已不再是这种情况。因为Environment抽象集成在整个容器中,所以很容易通过它来解决占位符的分辨率。这意味着您可以以任何您喜欢的方式配置解析过程。您可以更改搜索系统属性和环境变量的优先级,或完全删除它们。您也可以根据需要将自己的属性源添加到混合中。

具体而言,无论customer属性在何处定义,以下语句都可以工作,只要它在以下位置可用Environment:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值