多环境支持-Maven和Spring的Profile

摘要

多环境支持是每个项目都要面对的问题,且不说很多环境,哪怕简单的项目也是需要区分本地开发的环境和正式运行的生产环境,不同的项目架构有不同的实现方式,这篇文章先说明利用Profile来进行配置的方式。
如今使用Spring Boot的项目已经很多了,用起来也算方便,下面来看看使用Maven作为构建工具,Spring Boot为基础框架,如何在不同的环境实现不同的运行状态。

Maven的profile

众所周知,Maven是支持profile配置的,在pom.xml配置文件中可以配置,在运行mvn命令时,可以指定profile:

mvn -P [profile]

很容易想到利用profile配置参数,然后根据参数打包不同的资源,这样就可以实现指定不同的profile时可以用不同的资源打包,就可以以不同的状态运行,实现目的。
下面将举一个简单的例子,实现打包不同的配置资源目录。
pom.xml

    ……
    <profiles>
        <profile>
            <id>local</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <env>local</env>
            </properties>
        </profile>

        <profile>
            <id>real</id>
            <properties>
                <env>release</env>
            </properties>
        </profile>
    </profiles>

    <build>
        <resources>
            ……
            <resource>
                <directory>src/main/resources-${env}</directory>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
            ……
        </resources>
        ……
    </build>
    ……

这段pom配置定义了两个profile:local和real,默认使用的是local,也就是说在不指定profile的时候将使用local。两个profile都定义了<env>的属性,属性值是不同的,这个属性可以在pom的其他地方引用,这个例子就在指定资源目录时引用了env属性值,因此,在指定profile为local时,将资源目录将使用resources-local目录,而profile指定为real时,将使用resources-release目录。除此了可以使用不同的资源目录,还可以实现很多目的,比如:使用不同的配置文件、为插件配置不同的参数等等。
下面再举一个常见例子,绝大部分项目都会用到数据库,可以使用flywaydb工具进行版本管理,但是一般生产环境不应当使用这种自动化管理工具,因此,仅仅在本地开发(或者测试)环境使用:
pom.xml

    <build>
        ……
        <plugins>
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>4.2.0</version>
                <configuration>
                    <url>${flyway.url}</url>
                    <user>${flyway.user}</user>
                    <password>${flyway.password}</password>
                </configuration>
            </plugin>
        </plugins>
        ……
    </build>

    <profiles>
        <profile>
            <id>local</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <flyway.url>jdbc:mysql://localhost:3306/testdb?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false</flyway.url>
                <flyway.user>admin</flyway.user>
                <flyway.password>admin</flyway.password>
            </properties>
        </profile>
        <profile>
            <id>test</id>
            <properties>
                <flyway.url>jdbc:h2:nio:${project.build.directory}/db/test</flyway.url>
                <flyway.user>ncp_console</flyway.user>
                <flyway.password>console123!@#</flyway.password>
            </properties>
        </profile>
        <profile>
            <id>release</id>
            <properties>
                <flyway.url></flyway.url>
                <flyway.user></flyway.user>
                <flyway.password></flyway.password>
            </properties>
        </profile>
    </profiles>

这段pom配置就可以达到目的,本地开发环境使用flyway管理本地mysql数据库,测试环境使用flyway管理h2数据库,生产环境flywaydb没有任何作用。

Spring的profile

spring和maven的profile

spring与maven一样都称为profile,很显然是类似的作用,但是要注意,二者并没有关联。spring的profile真正强大在于可以与spring结合,在运行时其实可以达到更多更灵活的目的,比如上下文不去加载一些bean等等,后面能看到一些示例。
既然已经有了maven的profile可以支持多环境配置,为什么还要介绍Spring的profile呢?在我看来,至少有一个理由可以决定是否要使用Spring的profile:是否仅提供一个发布包。也就是说,无论是在开发环境或者测试环境或者生产环境,都是提供相同的包,对于绝大多数情况,仅提供一个发布包是适用的,也是便于管理的。如果仅提供一个发布包,也就意味着在maven构建阶段,是不能区分profile的,因此就要靠spring了。

指定profile

Spring boot为profile的配置提供了很好的支持,比如默认的application配置文件默认就支持按照profile使用对应的application-{profile}配置文件,非常方便,具体参考官方手册。下面介绍几种常用的指定spring的当前profile(ActiveProfile):
- JVM参数指定profile:java …… -Dspring.profiles.active=local
- 注解指定profile: @ActiveProfiles("test"),这个注解是针对类型的注解,并且具有@Inherited特性,就是如果父类配置了,子类会继承该注解。
- 配置文件中指定profile:在application配置文件中可以配置spring.profiles.active=dev来指定profile。
完整的方法可以阅读Spring官方手册(包括spring.profiles.default和spring.profiles.active两个属性)、设置profile,还可以在代码中指定、web配置中指定等等。

profile的应用

仅仅指定profile是没有任何意义的,下面介绍一些常见用法:

定义Bean的时侯设置profile

如果是传统xml配置bean,可以配置<beans profile="local" ……>;如果使用注解方式,就在组件上添加注解@Profile("local")@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");
        }
    }

那么在指定profile为development时,”dataSource”的bean就是StandaloneDataConfig中定义的bean,使用嵌入式数据库;而如果指定profile为production时,”dataSource”的bean就是JndiDataConfig中定义的bean,使用Jndi中注册的数据源。

profile区分默认配置文件(以properties文件为例)

spring boot默认会使用application.properties配置文件,如果指定了profile,比如”local”,那么spring会按约定加载application-local.properties配置文件,并用application-local.properties中的配置覆盖application.properties中的配置。比如:

application.properties
    server.port=8082
    logging.config=classpath:log4j2.xml

application-local.properties
    logging.config=classpath:log4j2-local.xml

按照上面的配置,在没有指定profile时,log4j2的配置文件将使用log4j2.xml,如果指定profile为local时,将使用log4j2-local.xml,这样就可以在本地开发环境中将日志配置为trace级别,而其他环境配置为较高级别。但是因为application-local.properties中没有配置server.port,因此,无论是否指定profile为local,服务端口都是8082。

在配置注解中引用profile值

我们知道spring的配置可以使用${…}引用,当前指定的profile也可以通过${spring.profiles.active}引用。比如如果想自定义配置文件,而又要支持不同环境使用不同配置,可以这样:

project-local.properties
    recent.size=3

project-real.properties
    recent.size=10
    @SpringBootApplication
    @PropertySource("classpath:project-${spring.profiles.active}.properties")
    public class MainLauncher {
        ……
    }

    public class RecentRepository {

        @Value("${recent.size}")
        public int recentSize;
        ……
    }

在自定义配置文件时,通过引入profile值在不同环境下加载不同的自定义配置文件,在local环境,recentSize=3,在real环境,recentSize=10
以上这些用法都是比较常见的,已经能够实现绝大部分的需求了。

大家注意profile用于区分配置文件,下一篇介绍根据条件加载bean中还可以看到这个用法,结合加载条件的定义,还可以实现更强大的功能。


版权声明:本文为xiaosunzhu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。