分类
spring

Spring 情景设置

Spring Profiles

1. 概述

无论在任何项目中,项目配置都是避不开的核心章节:我们需要为同一项目的不同环境定制不同的配置。比如我们在开发时并不需要真正地向邮件服务器、短信服务器等发送数据,此时则需要一个开发环境的配置;比如我们在生产环境下应该启用正式的邮件服务、短信服务、日志推送服务等,此时则需要一个生产环境的配置;再比如在单元测试的环境中,我们更希望能够使用H2数据库来替换MYSQL等数据库,则此时还需要一个用于测试环境的配置。本文将围绕Spring项目的配置进行讨论。

在Spring配置的支持下,可以为不同的环境声明不同的Bean实现,进而达到满足不同环境功能要求的目的。下面,让我们来看下Spring项目是如何实现的。

2. 使用@Profile注解

Spring提供了一个非常简单的方式来指定特定环境下的Bean --- @Profile注解。

假设应用中的短信服务如下:

/**
 * 短信服务
 * @author panjie
 */
public interface SmsService {

    /**
     * 发送短信
     * @param phone 接收的手机号
     * @param context 发送的短信文本
     */
    void sentMessage(String phone, String context);
}

由于我们没有必要在开发环境中真实的发送短信,所以我们当前想实现:开发环境中在控制台中打印短信内容,而在生产环境中则调用实际的短信接口来发送短信:

@Service
@Profile("dev")
public class SmsServiceConsoleImpl implements SmsService {
    @Override
    public void sentMessage(String phone, String context) {
        // 在控制台中打印发送的手机号及内容
    }
}

上述代码中使用@Profile("dev")来标识SmsServiceConsoleImpl仅当项目情景为dev时生效。

@Service
@Profile("pro")
public class SmsServiceImpl implements SmsService {
    @Override
    public void sentMessage(String phone, String context) {
        // 调用短信api发送短信
    }
}

上述代码中使用@Profile("pro")来标识SmsServiceImpl仅当项目情景为pro时生效。

如果当前项目仅有两种模式或是想实现除了某种模式以外其它的模式全部生效,则还可以结合!来使用:

@Service
@Profile("!pro")
public class SmsServiceConsoleImpl implements SmsService {

上述代码实现了:当前环境不为pro时SmsServiceConsoleImpl生效。

3. 使用XML

同样还可以使用xml来进行配置:

<beans profile="dev">
    <bean id="smsServiceConsoleImpl" 
          class="club.codedemo.springprofiles.service.SmsServiceConsoleImpl" />
</beans>

4. 设置项目环境

Spring提供了多种设置项目环境的方法,本节中我们将一一展开:

4.1 实现WebApplicationInitializer接口

我们可以在Spring项目中,通过相应的编码来改变项目环境。需要注意的是:如果变更环境的代码发生在Spring读取环境以后,那么将不对Spring目的Bean配置产生影响。

方法一:调用ServletContext的setInitParameter方法。

@Configuration
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.setInitParameter( "spring.profiles.active", "dev");
    }

此方法将在原来的项目环境上进行追加,比如原来的项目环境为pro,则使用上述上码完成追加后,项目环境将变更为dev、pro。

4.2 注入ConfigurableEnvironment

也可以使用注入ConfigurableEnvironment的方法来设置项目环境:

    @Autowired
    private ConfigurableEnvironment env;
    ...
    env.setActiveProfiles("dev");

上述代码将覆盖项目的原情景,比如在执行上述代码前项目的情景为pro,则执行上述代码后项目情景将变更为dev。

4.3 设置web.xml

在web应用中,还可以通过设置web.xml来设置项目情景:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/app-config.xml</param-value>
</context-param>
<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>dev</param-value>
</context-param>

4.4 设置JVM

还可以通过设置JVM的系统参数来达到设置Spring项目情景的目的,比如我们在项目启动时加入如下JVM系统参数:

-Dspring.profiles.active=dev

4.5 环境变量

在Liunx/unix系统中,还可以通过设置环境变量的方法来设置项目情景:

export spring_profiles_active=dev

4.5 MAVEN配置

如果你的项目是一个标准的maven项目,那么还可以在maven项目中声明以下配置(pom.xml):

<profiles>
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <spring.profiles.active>dev</spring.profiles.active>
        </properties>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <spring.profiles.active>prod</spring.profiles.active>
        </properties>
    </profile>
</profiles>

然后在项目的配置文件application.properties:中如下设置spring.profiles.active:

spring.profiles.active=@spring.profiles.active@

如上述代码所示,我们使用了@关键字@的方法来获取maven项目中profile的相关值。

接着启用资源过滤器:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    ...
</build>

最后在打包时便可使用如下参数来指定打包的项目情景了:

mvn clean package -Pprod

使用上述命令打包将使应用在运行时将spring.profiles.active设置为prod。

4.7 单元测试

如果单元测试的内容与项目情景有关,则应该在特定的单元测试上使用@ActiveProfile以指定当前测试的情景:

@ActiveProfiles("dev")

4.8 总结

本节中我们讨论了几种设置项目情景的方法,当同时使用上述方法中的多个时,则优先级如下:

  1. web.xml设置(优先级最高)
  2. JVM设置
  3. 环境变量设置
  4. MAVEN属性设置(优先级最低)

5. 默认情景

当Spring中的Bean并没有显式的声明其属性哪个情景,则Spring默认添加到default情景。这也是当项目未声明情景时Spring认证的情景默认值。

我们可以通过设置spring.profiles.default来达到变更默认情景的目的。

6. 获取当前情景

Spring根据当前情景来决定是否启用某个使用了@Profile注解的Bean。有时候,我们可能需要在编码时获取项目当前情景。以下我们介绍几种获取项目当前情景的方法:

6.1 Environment

可以直接注入Environment并调用其上的getActiveProfiles方法来获取当前情景:

    @Autowired
    Environment environment;

    @Override
    public void sentMessage(String phone, String context) {
        for (String active: environment.getActiveProfiles()) {
            System.out.println(active);
        }
     
    }

6.2 @Value

还可以使用@Value来直接获取项目情景:

@Value("${spring.profiles.active}")
private String activeProfile;

需要注意的,当项目当前情景为多个时,比如为:pro、pro-aliyun。那么上述代码注入的activeProfile将为"pro,pro-aliyun",即:使用","分隔了多个情景。

同时,还应该考虑到该值未定义的情况:当spring.profiles.active未定义时,上述代码则会导到处IllegalArgumentException异常。此时,可以为其设置一个默认值以避免异常的发生:

@Value("${spring.profiles.active:}")
private String activeProfile;

上述代码中在spring.profiles.active的后面增加了":" ,用以表示:当应用未设置spring.profiles.active时,使用默认值空字符串来填充activeProfile。

7. 使用示例

比如多数的项目需要的短信验证功能。我们在开发、测试环境中并不需要向手机号上发送真实的短信,而在生产环境中则必须向手机号发送真实的短信。

public interface SmsService {

    /**
     * 发送短信
     * @param phone 接收的手机号
     * @param context 发送的短信文本
     */
    void sentMessage(String phone, String context);
}

那么此时我们便可以建立两个短信实现:

@Service
@Profile("!pro")
public class SmsServiceConsoleImpl implements SmsService {

以上代码实现了:非生产环境时,启用当前的Bean。以实现:在控制台中打印短信验证码。

而在生产环境中使用的真实短信实现则可以如下实现:

@Service
@Profile("pro")
public class SmsServiceImpl implements SmsService {

8. Spring Boot中配置项目情景

Spring Boot支持本文上述所有的配置,同时还提供了一些新特性供我们选择。

比如我们可以通过设置application.properities中的spring.profiles.active来达到快速配置项目情景的目的:

spring.profiles.active=dev

在使用编码进行设置时,还可以通过调用SpringApplication中的setAdditionalProfiles方法来设置项目情景:

    public static void main(String[] args) {
        // 方法一:在run方法执行前,设置spring.profiles.active
//        System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "dev");

        // 方法二:将原SpringApplication.run(SpringProfilesApplication.class, args)拆分
        // 在执行run方法前执行setAdditionalProfiles方法,来达到变更项目情景的目的
        SpringApplication application = new SpringApplication(SpringProfilesApplication.class);
        application.setAdditionalProfiles("pro");
        application.run(args);
    }

或者利用spring-boot-maven-plugin插件来设置:

<plugins>
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <profiles>
                <profile>dev</profile>
            </profiles>
        </configuration>
    </plugin>
    ...
</plugins>

此时当使用mvn spring-boot:run运行项目时,项目的情景被设置为:dev。

除此以外,Spring Boot带来的最大变化是我们可以将不同情景下的配置单独放到某一个配置文件中,并以application-情景模式名称.properties来命名。比如我们可以为开发、生产模式分别建立application-dev.properties以及application-pro.properties文件。在不同的情景下,Spring Boot将加载特定的配置文件并且使用该配置文件中定义的值来覆盖application.properties中的相同项,同时保留未被覆盖的application.properties中的配置项。

比如在生产环境中指定使用mysql数据库:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db-example
spring.datasource.username=root
spring.datasource.password=

然后在开发环境中指定使用h2数据为:

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=

引外,Spring还支持yaml格式的配置文件。在yaml格式的配置文件下,可以轻松的使用---在同一个配置文件application.yml中定义不同的情景:

# properties与yml文件并存时,优先使用properties文件
spring:
  profiles:
    active: dev

# 在yaml文件中,可以使用---来分隔不同的情景
---
spring:
  profiles: dev
  application:
    name: yml-name-for-dev

---
spring:
  profiles: pro
  application:
    name: yml-name-for-pro

如上代码定义在application.yml中,与properties需要为不同的情景定义多个配置文件不同,上述代码在一配置文件中定义了dev以及pro两个情景。

9. 总结

本文中我们介绍了如何依据项目的情景来定义Bean,并给出了几种设置项目情景的方法。

项目的情景配置是项目开发中的必修课,我们需要根据不同的情景来启用不同的Bean以适应不同的环境。比如为不同的情景配置不同的数源源、为不同的情景配置不同的短信服务、为不同的情景配置不同的日志服务等。掌握情景的使用以及配置能够使我们在开发过程中有效的规避生产与开发环境的不同,从而提升整个团队的开发效率。这是因为我们再也不需要要因环境变更而注释\启用相关代码块以适应不同的环境了。