SpringFramework核心技术一(IOC:环境抽象)

环境抽象

这Environment 是一个集成在容器中的抽象,它模拟了应用程序环境的两个关键方面:配置文件 和属性。
一个轮廓是bean定义一个命名的逻辑组,只有当指定的配置文件是活动的容器进行登记。bean可以被分配给配置文件,不管是用XML还是通过注释来定义。Environment对象与配置文件相关的角色是确定哪些配置文件(如果有)当前处于活动状态,以及哪些配置文件(如果有)在默认情况下应该处于活动状态。
属性在几乎所有的应用程序中都扮演着重要的角色,可能来自各种来源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,ad-hoc属性对象,地图等等。与Environment对象相关的对象的作用是为用户提供一个方便的服务接口,用于配置属性来源并解析属性。


一、Bean定义配置文件

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用户已经设计了许多方法来完成这个任务,通常依赖于系统环境变量和<import/>包含${placeholder}令牌的XML 语句的组合,这些令牌根据环境变量的值解析为正确的配置文件路径。Bean定义配置文件是提供解决此问题的核心容器功能。

如果我们概括一下特定于环境的bean定义的上面的示例用例,我们最终需要在特定的上下文中注册某些bean定义,而不是其他的定义。你可以说你想在情况A中注册一个特定的bean定义配置文件,在情形B中需要另一个配置文件。我们先看看我们如何更新我们的配置以反映这种需求。

1.@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类型。

@Profile可以用作一个元注释用于创建自定义的目的组成的注解。以下示例定义了@Production可用作下拉式替换的自定义 注释 @Profile("production")

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

如果一个@Configuration类的中的Bean使用@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");
    }
}

对于@Profileon @Bean方法,可能需要一个特殊的场景:对于具有@Bean相同Java方法名称的重载方法(类似于构造函数重载),@Profile需要在所有重载的方法上一致地声明一个条件。如果条件不一致,那么只有重载方法中的第一个声明的条件很重要。@Profile因此不能用于选择具有特定参数签名而不是另一个的重载方法; 同一个bean的所有工厂方法之间的分辨率在创建时遵循Spring的构造函数解析算法。
如果您想要定义具有不同配置文件条件的备用bean,请使用不同的Java方法名称,通过@Bean name属性指向同一个bean名称,如上例所示。如果参数签名完全相同(例如所有变体都没有arg工厂方法),那么这是首先在有效的Java类中表示这种安排的唯一方法(因为只能有一种方法一个特定的名字和参数签名)。

2.XML bean定义配置文件

XML相对应的是元素的profile属性<beans>。上面的示例配置可以用两个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/>在同一个文件中拆分和嵌套元素:

<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文件中产生混乱。

3.激活配置文件

现在我们已经更新了配置,我们仍然需要指示Spring哪个配置文件处于活动状态。如果我们现在开始我们的示例应用程序,我们会看到一个NoSuchBeanDefinitionException抛出的,因为容器找不到名为的Spring bean dataSource。
激活配置文件可以通过几种方式完成,但最直接的方法是通过编程方式对照Environment通过以下方式提供的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…​可变参数:

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

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

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

4.默认配置文件

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

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

二、PropertySource抽象

Spring的Environment抽象提供了对属性资源的可配置层次结构的搜索操作。为了充分解释,请考虑以下几点:

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

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

这些默认属性源StandardEnvironment用于独立应用程序。StandardServletEnvironment 被填充了额外的默认属性来源,包括servlet配置和servlet上下文参数。它可以选择启用一个JndiPropertySource。有关详细信息,请参阅javadocs。

具体来说,在使用时StandardEnvironment,env.containsProperty(“foo”) 如果foo系统属性或foo环境变量在运行时出现,则调用将返回true 。

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

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

最重要的是,整个机制是可配置的。也许你有一个自定义的属性来源,你想集成到这个搜索。没问题 - 只需实现并实例化你自己PropertySource并将其添加到PropertySources当前的集合Environment:

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

在上面的代码中,MyPropertySource已经在搜索中添加了最高优先级。如果它包含一个foo属性,它将foo在任何其他属性之前被检测到并返回PropertySource。该 MutablePropertySources API公开了许多允许精确操作属性源集的方法。

三、@PropertySource

该@PropertySource 注解提供便利和声明的机制添加PropertySource 到Spring的Environment。
给定一个包含键/值对的文件“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”存在于已注册的属性资源之一中,例如系统属性或环境变量,占位符将被解析为相应的值。如果不是,则默认使用“默认/路径”。如果未指定默认值并且属性无法解IllegalArgumentException则会抛出一个属性 。

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

四、报表中的占位符解析

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

具体来说,无论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、付费专栏及课程。

余额充值