高级装配 —— Spring profile

Q:什么是 profile?
A:可以理解为我们在Spring容器中所定义的Bean的逻辑组名称。只有当这些 Profile 被激活的时候,才会将 Profile 中所对应的 Bean 注册到 Spring 容器中。

Q:为什么要使用 profile bean?
A: 因为在不同的环境中某个 bean 会有所不同。我们就必须得有一种方法来配置 bean,使其在每种环境下都会选择最为合适的配置。
其中一种方法:在单独的配置类(或 XML 文件)中配置每个 bean,然后在构建阶段(可能会使用 Maven 的 profiles)确定要将哪一个配置编译到可部署的应用中。然而,这种方法的问题在于要为每种环境重新构建应用。比如:发开阶段 -> QA 阶段时,需要重新构建。QA 阶段 -> 开发阶段,需要重新构建。
而 Spring 所提供的解决方案(profile bean)不需要重新构建。他不是在构建阶段做出决策,而是等到运行时再来确定要将哪一个配置编译到可部署的应用中。这样的好处就是:同一个部署单元(可能会是 WAR 文件)能够适用于所有的环境,没有必要进行重新构建

如:在开发中,通常会出现在开发的时候使用一个开发数据库,测试的时候使用一个测试的数据库,而实际部署的时候需要一个数据库。环境的更改,每次都需要修改配置文件。但是使用 profile 就不需要了。

在 Spring 3.1 中,Spring 引入了 bean profile 的功能。

Q:怎么使用 profile?
A:首先要将所有不同的 bean 定义整理到一个或多个 profile 之中,再将应用部署到每个环境时,要确保对应的 profile 处于激活的状态。

Q:怎么配置 profile bean?

在 JavaConfig 中配置 profile

方式一:单个创建

package com.myapp;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

import javax.activation.DataSource;

/**
 * 在 Java 配置中,可以使用 @Profile 注解指定某个 bean 属于哪一个 profile。
 * 在配置类中,嵌入式数据库的 DataSource 可能会配置成如下所示。
 *
 * 注意:@Profile 注解应用在类级别上,它会告诉 Spring 这个配置类中的 bean 只有在 dev profile 激活时才会创建。
 *      如果 dev profile 没有激活的话,那么带有 @Bean 注解的方法都会被忽略掉。
 */
@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {

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

可能还需要一个适用于生产环境的配置:

package com.myapp;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jndi.JndiObjectFactoryBean;

import javax.activation.DataSource;

/**
 * 只有 prod profile 激活时,才会创建对应的 bean。
 */
@Configuration
@Profile("prod")
public class ProductionProfileConfig {

    @Bean
    public DataSource dataSource(){
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDS");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndiObjectFactoryBean.getObject();
    }
}

在 Spring 3.1 中,只能在类级别上使用 @Profile 注解。不过,从 Spring 3.2 开始,你也可以在方法级别上使用 @Profile 注解,与 @Bean 注解一同使用。这样的话,就能将这两个 bean 的声明放到同一个配置类之中

方式二:放在同一个配置类之中

package com.myapp;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;

import javax.activation.DataSource;

/**
 * @Profile 注解基于激活的 profile 实现 bean 的装配
 *
 * 注意:尽管每个 DataSource bean 都被声明在一个 profile 中,并且只有当规定的 profile 激活时,
 * 相应的 bean 才会被创建,但是可能会有其他的 bean 并没有声明在一个给定的 profile 范围内。
 * 没有指定 profile 的 bean 始终都会被创建,与激活哪个 profile 没有关系。
 */
@Configuration
public class DataSourceConfig {

    @Bean
    @Profile("dev")  //为 dev profile 装配的 bean
    public DataSource embeddedDataSource(){
        return (DataSource) new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:schema.sql")
                .addScript("classpath:test-data.sql")
                .build();
    }

    @Bean
    @Profile("prod") //为 prod profile 装配的 bean
    public DataSource jndiDataSource(){
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDS");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndiObjectFactoryBean.getObject();
    }
}

在 XML 中配置 profile

方式一:单个配置文件

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"
       profile="dev">

    <!-- 通过<beans>元素的 profile 属性,在 XML 配置中配置 profile bean -->

    <!--
        定义了适用于开发阶段的嵌入式数据库 DataSource bean,
        也可以将 profile 设置成 prod,创建适用于生产环境的从 JNDI 获取的 DataSource bean
     -->

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:schema.sql"/>
        <jdbc:script location="classpath:test-data.sql"/>
    </jdbc:embedded-database>

</beans>

方式二:重复使用元素来指定多个 profile

<?xml version="1.0" encoding="UTF-8"?>
<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="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">

    <!--
        这里的两个 bean ,类型都是 javax.sql.DataSource,并且ID 都是 dataSource
        但是运行时,只会创建一个 bean,这取决于处于激活状态的是哪个 profile
     -->

    <!-- dev profile 的 bean -->
    <beans profile="dev">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:schema.sql"/>
            <jdbc:script location="classpath:test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <!-- prod profile 的 bean -->
    <beans profile="prod">
        <jee:jndi-lookup jndi-name="jdbc/myDatabase" id="dataSource" resource-ref="true"
                         proxy-interface="javax.sql.DataSource"/>
    </beans>
</beans>
Q:怎么激活 profile?

Spring 在确定哪个 profile 处于激活状态时,需要依赖两个独立的属性:spring.profile.activespring.profile.default。Spring 会先查找 active 再找 default,如果两个均没有设置的话,那就没有激活的 profile,因此只会创建哪些没有定义的在 profile 中的 bean。

Q:有哪些方式是来设置这两个属性呢?
A:

  • 作为 DispatcherServlet 的初始化参数;
  • 作为 Web 应用的上下文参数;
  • 作为 JNDI 条目;
  • 作为环境变量;
  • 作为 JVM 的系统属性;
  • 在集成测试上,使用 @ActiveProfiles 注解设置。

在 Web 应用中,设置 spring.profiles.default 的 web.xml 文件如下所示
这里采用的方法:使用 DispatcherServlet 的初始化参数将 spring.profiles.default 设置为开发环境的 profile,我会在 Servlet 上下文中进行设置(为了兼顾到 ContextLoaderListener)。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- bean 配置所在的 xml -->
        <param-value>classpath:datesource.xml</param-value>
    </context-param>

    <!-- 为上下文设置默认的 profile:为了兼顾到 ContextLoaderListener -->
    <context-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </context-param>

    <!-- 需要导入 spring-web -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>


    <!-- 需要导入 spring-webmvc -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!-- 为 servlet 设置默认的 profile -->
            <param-name>spring.profiles.default</param-name>
            <param-value>dev</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

然后,当应用程序部署到 QA、生产或其他环境之中时,负责部署的人根据情况使用系统属性环境变量JNDI 设置 spring.profiles.active 即可。当设置了 active 以后,default 的值就无所谓了。

Q:怎么同时激活多个 profile?
A:通过列出多个 profile 名称,并以逗号分隔来实现。

当运行继承测试时,通常会希望采用与生产环境(或者是生产环境的部分子集)相同的配置进行测试。
如果配置中的 bean 定义在了 profile 中,那么在运行测试时,就需要一种方式来启用合适的 profile。

Q:如何使用 profile 进行测试?
Spring 提供了@ActiveProfiles 注解,我们可以使用它来指定运行测试时需要激活哪个 profile。在集成测试时,通常想要激活的是开发环境的 profile。

/**
 * 使用 @ActiveProfiles 激活 dev profile
 * <p>
 * SpringJUnit4ClassRunner 需要导入 spring-test 包
 */
@RunWith(SpringJUnit4ClassRunner.class)
// 设置配置文件
@ContextConfiguration("classpath:datesource.xml")
//@ContextConfiguration(classes = {PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
}

在条件化创建 bean 方面,Spring 的 profile 机制是一种很棒的方法,这里的条件要基于哪个 profile 处于激活状态来判断。
Spring 4.0 中提供了一种更为通用的机制来实现条件化的 bean 定义,在这种机制中,条件完全由你来确定。那就是 @Conditional 注解定义条件化的 bean

上一篇:自动装配、JavaConfig、XML 三种方案之间,怎么导入和混合配置?
下一篇:高级装配 —— 条件化的 bean

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值