红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

从零开始 Sping Boot 20:多环境部署

2022年8月29日 1203点热度 0人点赞 0条评论

spring boot

图源:简书 (jianshu.com)

如果你一路从从零开始Spring Boot 1:快速构建 - 魔芋红茶's blog (icexmoon.cn)阅读到从零开始 Spring Boot 19:Redis - 魔芋红茶's blog (icexmoon.cn),那我相信你应当和我一样掌握了基础的Spring Boot开发技能。

但是我们还需要补上最后一课——多环境部署。

在商业开发Web应用的时候,我们通常需要配置多种环境来满足开发/测试/上线的需要,一般来说至少需要以下三个环境:

  • dev,开发环境,一般为开发者自己的电脑,完成绝大部分开发工作。

  • test,测试环境,一般为公司的Linux服务器,贴近生产环境,用于完成测试工作。

  • prd,生产环境,应用上线运行的环境,一般为客户提供的服务器。

不同的环境需要使用不同的配置信息来运行,比如开发环境数据库一般使用本地数据库,即localhost,而测试环境和生产环境可能有独立部署的数据库服务器,即需要指定具体的ip。所以一般会有数份配置文件来保存不同环境的不同配置信息。

下面用实例来说明如何对Spring Boot应用配置多环境。

同之前一样,这里会基于从零开始 Spring Boot 19:Redis - 魔芋红茶's blog (icexmoon.cn)一文中的最终示例代码,在这之上进行修改。

properties

我们原始的项目配置文件application.properties包含以下内容:

#默认应用运行加载的配置文件
spring.profiles.active=dev
#文件上传目录
books.file.upload.path=D:/workspace/learn_spring_boot/ch16/books/upload/
#应用使用的域名
books.web.host=5v609116p2.oicp.vip
#应用监听端口
server.port=8080
#单个文件上传大小限制
spring.servlet.multipart.max-file-size=10MB
##一次请求上传文件总大小限制
spring.servlet.multipart.max-request-size=10MB
spring.datasource.url=jdbc:mysql://localhost:3306/books
spring.datasource.username=root
spring.datasource.password=mysql
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
#http客户端设置
httpclient.maxTotal=1
httpclient.defaultMaxPerRoute=1
httpclient.connectTimeout=2000
httpclient.connectionRequestTimeout=2000
httpclient.socketTimeout=2000
#yesapi配置
books.yesapi.host=hn216.api.yesapi.cn
books.yesapi.appKey=6F7CF03FAA834E181B2D1D8ABE5665CE
#日志配置文件
logging.config=classpath:logback-dev.xml
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=debug
logging.level.cn.icexmoon.demo.books=TRACE
#软删除
mybatis-plus.global-config.db-config.logic-delete-field=delFlag
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
#联表查询时查找DAO类的包路径
mybatis-plus.type-aliases-package=cn.icexmoon.demo.books.*.entity
#微信公众号信息
books.appid=wx934590a6bf2965da
books.secret=813437b0e7d4763c028034cca9f864a2
#redis 公共配置
spring.redis.database=0
# Redis服务器连接端口
spring.redis.port=6379
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接密码(默认为空)
spring.redis.password=

现在将不同环境需要设置不同值的配置属性提取出来,分别写入多环境对应的配置文件中,这些环境配置文件命名是有规律的:

  • application-dev.properties,开发环境配置

  • application-prd.properties,生产环境配置

  • application-test.properties,测试环境配置

新生成的开发环境配置文件包含以下配置信息:

#项目根目录
books.home=D:/workspace/learn_spring_boot/ch20/books
#文件上传目录
books.file.upload.path=${books.home}/upload/
#应用使用的域名
books.web.host=localhost
#应用监听端口
server.port=8080
#加载mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#日志错误级别
books.log.level=DEBUG
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=${books.log.level}
logging.level.cn.icexmoon.demo.books=${books.log.level}
#微信公众号信息
books.appid=wx934590a6bf2965da
books.secret=813437b0e7d4763c028034cca9f864a2
#数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/books
spring.datasource.username=root
spring.datasource.password=mysql
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=

如果是多人协同开发,可以将这个配置上传到版本库后加入.gitigonre文件,以避免不同开发者修改本地配置后提交到版本库影响其他人。

在配置中可以使用已有配置设置新配置,比如books.file.upload.path=${books.home}/upload/。

相应的测试环境配置文件:

#项目根目录
books.home=/mnt/books
#文件上传目录
books.file.upload.path=${books.home}/upload/
#应用使用的域名
books.web.host=192.168.1.119
#应用监听端口
server.port=8083
#加载mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#日志错误级别
books.log.level=INFO
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=${books.log.level}
logging.level.cn.icexmoon.demo.books=${books.log.level}
#微信公众号信息
books.appid=wx934590a6bf2965da
books.secret=813437b0e7d4763c028034cca9f864a2
#数据库配置
spring.datasource.url=jdbc:mysql://192.168.1.119:3306/books
spring.datasource.username=root
spring.datasource.password=root
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=redis

这里只需要按需要修改即可,因为测试环境是Linux服务器,所以项目根目录采用/mnt这样的路径。生产环境配置与之类似,不再赘述。

现在已经有了所有三个环境的配置文件,而当前应用启动后默认使用哪个配置由spring.profiles.active配置项决定,比如现在的主配置文件中:

#默认应用运行加载的配置文件
spring.profiles.active=dev

这意味着默认会加载application-dev.properties配置文件。

logback

除了Spring Boot本身的配置外,因为books还使用了logback,所以还需要修改logback配置文件,以满足多环境的需要。

首先要说明的是,在没有使用logging.config配置定义logback配置文件的情况下,Spring Boot会自动加载resource目录下的logback.xml、logback-test.xml、logback-spring.xml。所以你可以将logback配置文件名定义为任意一种。甚至可以是不符合上述命名,但是直接通过logging.config配置项指定。

但是实际上这几种配置是有区别的,Spring Boot启动时会按照以下顺序进行加载:

logback.xml -> application.properties -> logback-spring.xml

这意味着如果要在logback配置文件中使用Spring Boot中的配置项,那就必须使用logback-spring.xml而非logback.xml。

所以这里将原本的logback配置文件重命名为logback-spring.xml,并且使用相关配置项代替硬编码,以满足不同环境下让日志保存到不同目录以及使用不同的日志级别:

<configuration>
    <springProperty scop="context" name="books.home" source="books.home"/>
    <springProperty scop="context" name="books.log.level" source="books.log.level"/>
    <!-- 日志内容格式 -->
    <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
    <!-- 日志文件路径规则 -->
    <property name="FILE_PATH" value="${books.home}/log/%d{yyyy-MM-dd}.%i.log" />
    <!-- 控制台日志输出设置 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 按照上面配置的LOG_PATTERN来打印日志 -->
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    <!-- 文件日志相关设置 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按照上面配置的FILE_PATH路径来保存日志 -->
            <fileNamePattern>${FILE_PATH}</fileNamePattern>
            <!-- 日志保存15天 -->
            <maxHistory>15</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 单个日志文件的最大,超过则新建日志文件存储 -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <!-- 按照上面配置的LOG_PATTERN来打印日志 -->
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    <!-- 日志记录级别 -->
    <logger name="cn.icexmoon.demo.books" level="${books.log.level}" />
    <root level="${books.log.level}">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

需要注意的是,不能在logback配置文件中直接使用Spring Boot中的配置项,需要先使用xml标签springProperty将配置项中的值绑定成logback配置中的属性后,才可以在logback配置中通过${xxx}的方式使用,比如:

<springProperty scop="context" name="books.home" source="books.home"/>

springProperty的name属性指backlog配置文件中绑定后的名称,而source属性指的是Spring Boot配置文件中的配置项名称。

  • 这里为了简单起见两者命名相同,当然也可以使用不同的命名。

  • 因为之前说的原因,logback.xml是不能使用springProperty标签的,否则应用无法启动,会报logback文件解析出错之类的错误。

springProperty标签还有一个defaultValue属性,在缺少Spring Boot配置项的时候可以为其指定一个默认值。

现在一切准备就绪,确认相应环境配置设置无误后就可以打包发布了,只需要部署到相应目录后通过以下命令启动应用即可:

java -jar books-0.0.1-SNAPSHOT.jar --spring.profiles.active=test

这里通过命令行参数spring.profiles.active来指定启动时使用哪个环境的配置,命令行参数的优先级是要高于主配置中的spring.profiles.active配置项的,所以这里会正常加载application-test.properties配置文件。

你可能会觉得这样每次输入一长串命令都很麻烦,那可以考虑将命令设置成Linux的快捷指令或者干脆编写一个bash脚本,具体可以参考Spring Boot 应用一键发布脚本_魔芋红茶的博客-CSDN博客。

mvn

一般来说,关于多环境部署的内容到这里就可以结束了,但某些情况下我们可能会使用包管理工具从工程文件来启动应用而不是直接使用java -jar命令从jar包启动。

如果你使用的是MVN,可以通过以下命令从项目目录启动Spring Boot应用:

mvn spring-boot:run -Dprofiles=dev

这里通过命令行参数profiles可以指定运行时加载哪个环境配置。

mvn命令指定命令行参数都需要以-D开头。

其实profiles是Spring Boot的mvn插件为了方便指定配置文件预定义的一个“快捷参数”,其作用和以下命令是相同的:

mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"

这个命令实际上是通过Spring Boot的mvn插件预定义的spring-boot.run.jvmArguments属性来设置mvn启动jar包时候所用的命令行参数。

奇怪的是上边这个命令在Windows下的PowerShell中执行会出错,但CMD下执行没有问题。

我们还可以用mvn配置来“取代”Spring Boot配置项,比如用mvn配置来控制应用启动时加载哪种环境的配置:

<project>
    <!-- ... -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <profiles>dev</profiles>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <profiles>
        <!--开发环境-->
        <profile>
            <id>dev</id>
            <properties>
                <spring.profiles.active>dev</spring.profiles.active>
            </properties>
        </profile>
        <profile>
            <id>test</id>
            <properties>
                <spring.profiles.active>test</spring.profiles.active>
            </properties>
        </profile>
        <!--生产环境-->
        <profile>
            <id>prd</id>
            <properties>
                <spring.profiles.active>prd</spring.profiles.active>
            </properties>
        </profile>
    </profiles>
</project>

需要注意的是,plugin中的profiles属性指的是要加载的mvn配置,也就是下边profiles节点中定义的profile。

实际上这样做只是让mvn加载了一系列自定义profile的自定义属性,而要让属性生效还需要我们在Spring Boot的配置文件中进行替换:

#默认应用运行加载的配置文件
spring.profiles.active=@spring.profiles.active@
#...

为了区分Spring Boot中${...}风格的配置引用写法,要在其中引用mvn配置中的属性,需要使用@...@的写法。

现在再执行mvn spring-boot:run就会默认使用dev环境配置,并且你依然可以针对mvn生成的Jar包使用java -jar books-0.0.1-SNAPSHOT.jar --spring.profiles.active=test命令来指定需要加载的环境配置运行。

实际上还可以通过profile节点中定义activeByDefault的方式来指定默认加载的mvn配置:

<project>
<!-- ... -->
<profiles>
 <!--开发环境-->
 <profile>
   <id>dev</id>
   <properties>
     <spring.profiles.active>dev</spring.profiles.active>
   </properties>
   <activation>
     <activeByDefault>true</activeByDefault>
   </activation>
 </profile>
    <!-- ... -->
</profiles>
</project>

但实际使用时候发现这样做有些问题,使用这种方式定义后用mvn生成的jar包只能运行在默认mvn配置对应的环境,而无法通过java -jar books-0.0.1-SNAPSHOT.jar --spring.profiles.active=test命令来重新指定加载其他环境的配置运行。

我们还可以通过自定义mvn配置来控制,mvn加载配置:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!-- ... -->
    <properties>
        <!-- ... -->
        <app.profiles>dev</app.profiles>
    </properties>
    <!-- ... -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <profiles>${app.profiles}</profiles>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <!-- ... -->
</project>

这样就可以通过命令行参数来控制:

mvn spring-boot:run -Dapp.profiles=dev

实际测试这种方式也不会影响生成的jar包通过命令行参数切换配置加载。

通过mvn控制配置加载在我看来显得多余和不必要,之所以研究了下这块内容,是因为实际中一个项目使用了这种方式控制配置加载,而中间因此遇到了不少坑,出现无法通过命令行参数控制jar包切换配置的情况,生产环境无法启动jar包,所以专门研究了一下。

IDE

实际中发现如果按照上边的方式将环境配置加载交给mvn,会导致一个额外的副作用。

如果你在IDE中运行Spring Boot的方式是通过主类运行:

image-20220830095315029

就会无法正常运行,会出现类似下面这样的奇怪错误信息:

Could not resolve placeholder 'spring.redis.password' in value "${spring.redis.password}"

这大概是因为通过这种方式不会通过mvn,也就无法加载mvn的配置,继而无法通过mvn配置@spring.profiles.active@加载相应的spring boot配置。

这只是我个人的猜想。

当然要解决也很容易,改用mvn启动项目即可,也就是在IDE中配置一个mvn的启动项:

image-20220830095706962

此外,如果通过mvn运行还是出现各种奇怪的错误,可以尝试删除项目中的IDE缓存(.idea目录)来解决。

关于Spring Boot多环境部署的相关内容就介绍到这里了。

谢谢阅读。

本文中最终的完整示例代码见仓库learn_spring_boot/ch20 (github.com)。

参考资料

  • logback.xml和logback-spring.xml的区别zhuwei_clark的博客-CSDN博客logback logback-spring

  • Spring Boot Maven Plugin Documentation

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: spring boot
最后更新:2022年8月30日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号