Spring核心技术(十三)——环境的抽象

本章将描述一下Spring中针对环境的抽象。

Environment是一个集成到容器之中的特殊抽象,它针对应用的环境建立了两个关键的概念:profileproperties.

profile是命名好的,其中包含了多个Bean的定义的一个逻辑集合,只有当指定的profile被激活的时候,其中的Bean才会激活。无论是通过XML定义的还是通过注解解析的Bean都可以配置到profile之中。而Environment对象的角色就是跟profile相关联,然后决定来激活哪一个profile,还有哪一个profile为默认的profile。

properties在几乎所有的应用当中都有着重要的作用,当然也可能导致多个数据源:property文件,JVM系统property,系统环境变量,JNDI,servlet上下文参数,ad-hoc属性对象,Map等。Environment对象和property相关联,然后来给开发者一个方便的服务接口来配置这些数据源,并正确解析。

Bean定义的profile

在容器之中,Bean定义profile是一种允许不同环境注册不同bean的机制。环境的概念就意味着不同的东西对应不同的开发者,而且这个特性能够在一下的一些场景很有效:

  • 解决一些内存中的数据源的问题,可以在不同环境访问不同的数据源,开发环境,QA测试环境,生产环境等。
  • 仅仅在开发环境来使用一些监视服务
  • 在不同的环境,使用不同的bean实现

下面参考一个例子,下面的应用需要一个DataSource,在一个测试的环境下,可能类似如下代码:

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

现在考虑如果应用部署到QA环境或者生产环境,假设应用的数据源是服务器上的JNDI目录的话,我们的DataSource可能会如下:

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

问题就是如何基于当前的环境来使用不同的配置。过去,Spring的开发者开发了很多的方法来解决这个问题,通常都依赖于系统环境变量和XML中的<import/>标签以及占位符${placeholder}等来根据不同的环境解析当前的配置文件。Bean 的 profile是容器的特性,也是该问题的解决方案。

如果我们泛化了我们一些特殊环境下引用的bean定义,我们可以将其中指定的Bean注入到特定的context之中,而不是所有的context之中。很多开发者就希望能够在一种环境下使用Bean定义A,另一种情况下使用Bean定义B。

@Profile注解

@Profile注解允许开发者来表示一个组件是否适合在当前环境来进行注册,只有当在多个Profile之中,当前的Profile是激活的时候才可以进行注册。使用前面的例子,代码可以进行如下调整:

@Configuration
@Profile("dev")
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");
    }
}

@Profile注解可以当做元注解来使用。比如,下面所定义的@Production注解就可以来替代@Profile("production"):

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

@Profile注解也可以在方法级别使用,可以声明在包含@Bean注解的方法之上:

@Configuration
public class AppConfig {

    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        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
    @Profile("production")
    public DataSource productionDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

如果配置了@Configuration的类同时配置了@Profile,那么所有的配置了@Bean注解的方法和@Import注解的相关的类都会被传递为该Profile除非这个Profile激活了,否则Bean定义都不会激活。如果配置为@Component或者@Configuration的类标记了@Profile({"p1", "p2"}),那么这个类当且仅当Profile是p1或者p2的时候才会激活。如果某个Profile的前缀是!这个否操作符,那么@Profile注解的类会只有当前的Profile没有激活的时候才能生效。举例来说,如果配置为@Profile({"p1", "!p2"}),那么注册的行为会在Profile为p1或者是Profile为非p2的时候才会激活。

XML中Bean定义的profile

在XML中相对应配置是<beans/>中的profile属性。我们在前面配置的信息可以被重写到XML文件之中如下:

<beans profile="dev"
    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="dev">
        <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文件的配置提供更多便利。

激活profile

现在,我们已经更新了配置信息来使用环境抽象,但是我们还需要告诉Spring来激活具体哪一个Profile。如果我们直接启动应用的话,现在就回抛出NoSuchBeanDefinitionException异常,因为容器会找不到Spring的BeandataSource

有多种方法来激活一个Profile,最直接的方式就是通过编程的方式来直接调用EnvironmentAPI,ApplicationContext中包含这个接口:

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

额外的,Profile还可以通过spring.profiles.active中的属性来通过系统环境变量,JVM系统变量,servlet上下文中的参数,甚至是JNDI的一个参数等来写入。在集成测试中,激活Profile可以通过spring-test中的@ActiveProfiles来实现。

需要注意的是,Profile的定义并不是一种互斥的关系,我们完全可以在同一时间激活多个Profile的。编程上来说,为setActiveProfile()方法提供多个Profile的名字即可:

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

也可以通过spring.profiles.active来指定逗号分隔的多个Profile的名字:

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

默认profile

默认的Profile就表示默认启用的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();
    }
}

如果没有其他的Profile被激活,那么上面代码定义的dataSource就会被创建,这种方式就是为默认情况下提供Bean定义的一种方式。一旦任何一个Profile激活了,那么默认的Profile就不会激活。

默认的Profile的名字可以通过Environment中的setDefaultProfiles()方法或者是通过spring.profiles.default属性来更改。

属性源抽象

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

在上面的代码片段之中,我们看到一个high-level的查找Spring的foo属性是否定义的一种方式。为了知道Spring中是否包含这个属性,Environment对象会针对PropertySource的集合进行查找。PropertySource是针对一些key-value的属性对的简单抽象,而Spring的StandardEnvironment是由两个PropertySource对象所组成的,一个代表的是JVM的系统属性(可以通过System.getProperties()来获取),而另一种则是系统的环境变量(通过System.getenv()来获取。)

这些默认的属性源都是StandardEnvironment的代表,可以用在任何应用之中。StandardServletEnvironment则是包含Servlet配置的环境信息,其中会包含很多Servlet的配置和Servlet上下文参数。StandardPortletEnvironment类似于StandardServletEnvironment,能够配置portlet上下文参数。可以参考其Javadoc了解更多信息。

具体的说,当使用StandardEnvironment的时候,调用env.containsProperty("foo")将返回一个foo的系统属性,或者是foo的运行时环境变量。

查询配置属性是按层次来查询的。默认情况下,系统属性优优于系统环境变量,所以如果foo属性在两个环境中都有配置的话,那么在调用env.getProperty("foo")期间,系统属性值会优先返回。需要注意的是,属性的值是不会合并的,而是完全覆盖掉。

在一个普通的StandardServletEnvironment之中,查找的顺序如下,优先查找* ServletConfig参数(比如DispatcherServlet上下文),然后是* ServletContext参数(web.xml中的上下文参数),再然后是* JNDI环境变量,JVM系统变量(”-D”命令行参数)以及JVM环境变量(操作系统环境变量)。

最重要的是,整个的机制是可以配置的。也许开发者自己有些定义的配置源信息想集成到配置检索的系统中去。没问题,只要实现开发者自己的PropertySource并且将其加入到当前EnvironmentPropertySources之中即可:

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

在上面的代码之中,MyPropertySource被添加到检索配置的第一优先级之中。如果存在一个foo属性,它将由于其他的PropertySource之中的foo属性优先返回。MutablePropertySourcesAPI提供一些方法来允许精确控制配置源。

@PropertySource注解

@PropertySource注解提供了一种方便的机制来将PropertySource增加到Spring的Environment之中。

给定一个文件app.properties包含了key-value对testbean.name=myTestBean,下面的代码中,使用了@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之中形如${...}的占位符,都可以被解析成Environment中的属性资源,比如:

@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是我们已经注册到Environment之中的资源,举例来说,JVM系统属性或者是环境变量的话,占位符会解析成对象的值。如果没有的话,default/path会来作为默认值。如果没有指定默认值,而且占位符也解析不出来的话,就会抛出IllegalArgumentException

占位符解析

从历史上来说,占位符的值是只能针对JVM系统属性或者环境变量来解析的。但是现在不是了,因为环境抽象已经继承到了容器之中,现在很容易将占位符解析。这意味着开发者可以任意的配置占位符:

  • 调整系统变量还有环境变量的优先级
  • 增加自己的属性源信息

具体的说,下面的XML配置不会在意customer属性在哪里定义,只有这个值在Environment之中有效即可:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>
时间: 2024-11-05 22:05:12

Spring核心技术(十三)——环境的抽象的相关文章

Spring核心技术

这是第二次看关于Spring的资料,因为刚开始学习Spring的时候是边看视频边学习的,所以更注重的是实现代码,但是对宏观的掌握还是不够,这次主要从宏观的角度来分析一下Spring. 什么是Spring Spring是分层的Java SE/EE应用一站式的轻量级开源框架,以IoC(Inverse of Control:反转控制)和AOP(AspectOriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众

Spring核心技术IoC容器(六)

前文已经描述了Bean的作用域,本文将描述Bean的一些生命周期作用,配置还有Bean的继承. 定制Bean 生命周期回调 开发者通过实现Spring的InitializeingBean和DisposableBean接口,就可以让容器来管理Bean的生命周期.容器会调用afterPropertiesSet()前和destroy()后才会允许Bean在初始化和销毁Bean的时候执行一些操作. JSR-250的@PostConstruct和@PreDestroy注解就是现代Spring应用生命周期回

Spring核心技术IoC容器(四)

前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置.本文将继续描述玩全部的依赖信息. 使用 depends-on 如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也就是另一个Bean的属性之一.多数情况下,开发者可以在配置XML元数据的时候使用<ref/>标签.然而,有时Bean之间的依赖关系不是直接关联的.比如:需要调用类的静态实例化器来出发,类似数据库驱动注册.depends-on属性会使明确的强迫依赖的Bean在引用之前就会初始化.下面的例子使用dep

Spring核心技术IoC容器(五)

前文概述了Spring的容器,Bean,以及依赖的一些信息,本文将描述一下Bean的作用域 Bean的作用域 当开发者定义Bean的时候,同时也会定义了具体如何创建Bean实例的步骤.这些步骤是很重要的,因为只有通过这些配置,开发者才能创建实例对象. 开发者不仅可以控制多种多样的依赖到Bean之中,也可以配置Bean的作用域.这种方法是非常强大而且弹性也非常好,开发者可以通过配置来指定对象的作用域,而不用在Java类层次上来配置.Bean可以配置多种作用域.Spring框架支持5中作用域,有三种

Spring 核心技术(3)

接上篇:Spring 核心技术(2) version 5.1.8.RELEASE 1.4 依赖 典型的企业应用程序不会只包含单个对象(或 Spring 术语中的 bean).即使是最简单的应用程序也是由很多对象进行协同工作,以呈现出最终用户所看到的有条理的应用程序.下一节将介绍如何从定义多个独立的 bean 到实现对象之间相互协作从而实现可达成具体目标的应用程序. 1.4.1 依赖注入 依赖注入(DI)是一钟对象处理方式,通过这个过程,对象只能通过构造函数参数.工厂方法参数或对象实例化后设置的属

Spring 核心技术(4)

接上篇:Spring 核心技术(3) version 5.1.8.RELEASE 1.4.2 依赖关系及配置详情 如上一节所述,你可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者)的引用,或者作为内联定义的值.Spring 基于 XML 的配置元数据为此目的支持子元素<property/>和<constructor-arg/>. 直接值(基本类型,字符串等) <property/>元素的 value 属性指定一个属性或构造器参数为可读的字符串.Sp

Spring 核心技术(7)

接上篇:Spring 核心技术(6) version 5.1.8.RELEASE 1.6 定制 Bean 的特性 Spring Framework 提供了许多可用于自定义 bean 特性的接口.本节将它们分组如下: 生命周期回调 ApplicationContextAware 和 BeanNameAware 其他 Aware 接口 1.6.1 生命周期回调 要与容器的 bean 生命周期管理进行交互,可以实现 Spring InitializingBean 和 DisposableBean 接口

SpringData系列一Spring Data的环境搭建

本节作为主要讲解Spring Data的环境搭建 JPA Spring Data :致力于减少数据访问层(DAO)的开发量.开发者唯一要做的就是声音持久层的接口,其他都交给Spring Data JPA来帮你完成! 使用Spring Data JPA进行持久层开发需要的四个步骤: 配置Spring 整合 JPA 在Spring配置文件中配置Spring Data,让Spring 为声明的接口创建代理对象.配置了<jpa:repositories>后,Spring 初始化容器时将会扫描base-

核心技术与环境配置

核心技术与环境配置 asp.net 5是下一代的asp.net,该版本进行了全部重写以适用于跨平台,新新版本中,微软引入了如下工具与命令:DNVM.DNX.DNU. DNVM(.NET Version Manager):由于要实现跨平台的目录,微软提供了DNVM功能,DNVM是ASP.NET最底层的内容,他是一组Powershell脚本,用于启动指定版本的ASP.NET运行环境,并且可以在同一台机器的同一时间点上通过使用Nuget工具来管理各种版本的ASP.NET运行环境(DNX),以及进行相应