Spring官方文档阅读(九)之IOC容器(环境抽象)

13.环境抽象

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

概要文件是一个命名的bean定义逻辑组,只有在给定的概要文件处于活动状态时才向容器注册。bean可以被分配给一个配置文件,无论这个配置文件是用XML定义的还是用注释定义的。与概要文件相关的Environment对象的作用是确定哪些概要文件(如果有的话)当前处于活动状态,以及默认情况下哪些概要文件(如果有的话)应该处于活动状态。

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

13.1Bean定义概要文件

ean定义概要文件在核心容器中提供了一种机制,允许在不同环境中注册不同的Bean。对于不同的用户,环境这个词可能有不同的含义,这个特性可以帮助处理许多用例,包括:

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

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

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

现在考虑如何将该应用程序部署到QA或生产环境中,假设应用程序的数据源已注册到生产应用程序服务器的JNDI目录中。我们的数据源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中注册一个不同的概要文件。我们首先更新配置以反映这一需求。

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

注意:

  • 如前所述,对于@Bean方法,通常选择使用程序化JNDI查找,方法是使用Spring的JndiTemplate / JndiLocatorDelegate帮助器或前面显示的直接JNDI InitialContext用法,而不是JndiObjectFactoryBean变体,这将迫使您将返回类型声明为 FactoryBean类型。

概要文件字符串可以包含简单的概要文件名称(例如,生产)或概要文件表达式。 配置文件表达式允许表达更复杂的配置文件逻辑(例如,生产和美国东部)。 概要文件表达式中支持以下运算符:

  • !:配置文件的逻辑“不”
  • &:配置文件的逻辑“与”
  • |:配置文件的逻辑“或”

注意:

  • 您不能将&和 | 混合使用 不使用括号的运算符。 例如,生产与美国东部| eu-central不是有效的表达式。 它必须表示为生产&(美国东部|欧盟中央)。

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

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

注意:

  • 如果@Configuration类用@Profile标记,则除非该类中的一个或多个指定的配置文件处于活动状态,否则所有与该类关联的@Bean方法和@Import注解都会被忽略。 如果@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方法只在开发概要文件中可用。

  • jndiDataSource方法仅在生产配置文件中可用。

  • 使用@Bean方法上的@Profile时,可能会出现特殊情况:如果Java方法名称相同的重载@Bean方法(类似于构造函数重载),则必须在所有重载方法上一致声明@Profile条件。 如果条件不一致,则仅重载方法中第一个声明的条件很重要。 因此,@ Profile不能用于选择具有另一个参数签名的重载方法。 在创建时,同一bean的所有工厂方法之间的解析都遵循Spring的构造函数解析算法。

  • 如果您希望定义具有不同配置文件条件的可选bean,请使用不同的Java方法名称,通过使用@Bean name属性指向相同的bean名称,如前面的示例所示。如果参数签名都是相同的(例如,所有的变量都不带参数工厂方法),这是唯一的方式来表示这种安排在第一时间有效的Java类(因为只能有一个方法的名称和参数签名)。

13.12XML 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。

13.13激活一个概要文件

现在我们已经更新了配置,我们仍然需要指示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抽象 )。 在集成测试中,可以通过使用spring-test模块中的@ActiveProfiles批注来声明活动配置文件。

注意,配置文件不是一个非此即彼的命题。您可以同时激活多个配置文件。通过编程,您可以向setActiveProfiles()方法提供多个概要文件名称,该方法接受字符串可变参数。下面的示例激活多个概要文件

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

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

-Dspring.profiles.active="profile1,profile2"
13.14默认配置文件

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

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

如果没有配置文件是活动的,则创建数据源。您可以将此看作为一个或多个bean提供默认定义的一种方法。如果启用了任何配置文件,则不应用默认配置文件。

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

13.2PropertySource抽象

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对象执行搜索。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属性,则系统属性值将胜出并被返回。注意,属性值没有被合并,而是被前面的条目完全覆盖。
  • 对于常见的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公开了许多允许精确操作属性源集的方法。

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

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

13.3使用@PropertySource

@PropertySource注解为向Spring环境添加PropertySource提供了一种方便的声明性机制。

给定一个名为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注解都需要在同一级别声明,要么直接在配置类上声明,要么作为同一自定义注解中的元注解声明。不推荐混合使用直接注解和元注解,因为直接注解有效地覆盖了元注解。

13.4语句中的占位符解析

过去,元素中的占位符的值只能根据JVM系统属性或环境变量解析。现在情况已经不一样了。因为Environment抽象集成在整个容器中,所以很容易通过它来解析占位符。这意味着您可以以任何您喜欢的方式配置解析过程。您可以更改搜索系统属性和环境变量的优先级,或者完全删除它们。您还可以在适当的情况下添加您自己的属性源。

具体地说,下面的语句不管客户属性定义在哪里,只要它在环境中可用就可以:

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

14.注册一个LoadTimeWeaver

Spring使用LoadTimeWeaver在将类加载到Java虚拟机(JVM)中时对其进行动态转换。

要启用加载时编织,可以将@enableloadtime织造添加到一个@Configuration类中,如下面的示例所示

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}

另外,对于XML配置,您可以使用context:load-time-weaver元素

<beans>
    <context:load-time-weaver/>
</beans>

一旦为ApplicationContext配置好,该ApplicationContext中的任何bean都可以实现LoadTimeWeaverAware,从而接收到加载时weaver实例的引用。这与Spring JPA支持结合起来特别有用,其中加载时编织对于JPA类转换可能是必要的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值