环境的抽象 Environment

Environment接口是集成在容器中的抽象,可对应用程序环境的两个关键方面进行建模:概要文件(profiles)和属性(properties)。

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

属性在几乎所有应用程序中都起着重要作用,并且可能源自各种来源:属性文件,JVM系统属性,系统环境变量,JNDI,Servlet上下文参数,临时属性对象,Map对象等。环境对象与属性相关的作用是为用户提供方便的服务界面,用于配置属性源并从中解析属性。

1.13.1 Bean定义配置文件

Bean定义配置文件在核心容器中提供了一种机制,该机制允许在不同环境中注册不同的Bean。 “环境”一词对不同的用户而言可能意味着不同的含义,并且此功能可以在许多用例中提供帮助,包括:

  1. 在开发中针对内存中的数据源进行工作,而不是在进行QA或生产时从JNDI查找相同的数据源。
  2. 仅在将应用程序部署到性能环境中时注册监视基础结构。
  3. 为客户A和客户B部署注册bean的自定义实现。

在需要数据源的实际应用中考虑第一个用例。在测试环境中,配置可能类似于以下内容:

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

现在,假设该应用程序的数据源已在生产应用程序服务器的JNDI目录中注册,请考虑如何将该应用程序部署到QA或生产环境中。现在,我们的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定义配置文件是一项核心容器功能,可提供此问题的解决方案。

如果我们概括前面特定于环境的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 / JndiLocatorDelegate帮助器或前面显示的直接JNDI InitialContext用法,而不是JndiObjectFactoryBean变体,这将迫使您将返回类型声明为FactoryBean类型。

​ 概要文件字符串可以包含简单的概要文件名称(例如,production)或概要文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑(例如,production 和us-east)。概要文件表达式中支持以下运算符:

1. !:配置文件的逻辑“不”

2. &:配置文件的逻辑“与”
3. |:配置文件的逻辑“或”

您不能将&和|混合使用。不使用括号的运算符。例如,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批注处于活动状态,否则将绕过该@Configuration方法。如果@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");
    }
}

使用@Bean方法上的@Profile时,可能会出现特殊情况:如果Java方法名称相同的重载@Bean方法(类似于构造函数重载),则必须在所有重载方法上一致声明@Profile条件。如果条件不一致,则仅重载方法中第一个声明的条件重要。因此,@ Profile不能用于选择具有另一个参数签名的重载方法。在创建时,同一bean的所有工厂方法之间的解析都遵循Spring的构造函数解析算法。 如果要使用不同的概要文件条件定义备用bean,请使用@Bean name属性使用不同的Java方法名称来指向相同的bean名称,如前面的示例所示。如果参数签名都相同(例如,所有变体都具有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>
    

    也可以避免在同一文件中拆分和嵌套元素,如以下示例所示:

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

  • 激活profile文件

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

    可以通过多种方式来激活配置文件,但是最简单的方法是针对可通过ApplicationContext获得的Environment API以编程方式进行配置。以下示例显示了如何执行此操作:

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

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

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

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

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

     java -jar xxx.jar -D spring.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。您可以看到这是为一个或多个bean提供默认定义的一种方法。如果启用了任何配置文件,则默认配置文件将不适用。

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

    1.13.2 PropertySource抽象

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

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

    这些默认属性源存在于StandardEnvironment中,可在独立应用程序中使用。 StandardServletEnvironment填充了其他默认属性源,包括servlet配置和servlet上下文参数。它可以选择启用JndiPropertySource。

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

    执行的搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调用env.getProperty(“ my-property”)时在两个地方都同时设置了my-property属性,则系统属性值“ wins”并返回。请注意,属性值不会合并,而是会被前面的条目完全覆盖。 对于常见的StandardServletEnvironment,完整层次结构如下,最高优先级条目位于顶部:

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

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

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

    在前面的代码中,在搜索中添加了具有最高优先级的MyPropertySource。如果它包含my-property属性,则将检测并返回该属性,以支持任何其他PropertySource中的my-property属性。 MutablePropertySources API公开了许多方法,这些方法允许对属性源集进行精确操作。

    1.13.3 使用@PropertySource注解

    @PropertySource注释为将PropertySource添加到Spring的环境中提供了一种方便的声明性机制。

    给定一个名为app.properties的文件,其中包含键-值对testbean.name = myTestBean,下面的@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;
        }
    }
    

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

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

    1.13.4 声明中的占位符解析

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

    具体而言,以下语句无论在何处定义customer属性,只要在Environment中可用,该语句就起作用:

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

    参考文献

    【https://docs.spring.io/spring-framework/docs/current/reference/html/core.html】【1.13. Environment Abstraction】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值