图源:
如果你一路从阅读到,那我相信你应当和我一样掌握了基础的Spring Boot开发技能。
但是我们还需要补上最后一课——多环境部署。
在商业开发Web应用的时候,我们通常需要配置多种环境来满足开发/测试/上线的需要,一般来说至少需要以下三个环境:
-
dev,开发环境,一般为开发者自己的电脑,完成绝大部分开发工作。
-
test,测试环境,一般为公司的Linux服务器,贴近生产环境,用于完成测试工作。
-
prd,生产环境,应用上线运行的环境,一般为客户提供的服务器。
不同的环境需要使用不同的配置信息来运行,比如开发环境数据库一般使用本地数据库,即localhost,而测试环境和生产环境可能有独立部署的数据库服务器,即需要指定具体的ip。所以一般会有数份配置文件来保存不同环境的不同配置信息。
下面用实例来说明如何对Spring Boot应用配置多环境。
同之前一样,这里会基于一文中的最终示例代码,在这之上进行修改。
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脚本,具体可以参考。
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的方式是通过主类运行:
就会无法正常运行,会出现类似下面这样的奇怪错误信息:
Could not resolve placeholder 'spring.redis.password' in value "${spring.redis.password}"
这大概是因为通过这种方式不会通过mvn,也就无法加载mvn的配置,继而无法通过mvn配置@spring.profiles.active@
加载相应的spring boot配置。
这只是我个人的猜想。
当然要解决也很容易,改用mvn启动项目即可,也就是在IDE中配置一个mvn的启动项:
此外,如果通过mvn运行还是出现各种奇怪的错误,可以尝试删除项目中的IDE缓存(.idea
目录)来解决。
谢谢阅读。
本文中最终的完整示例代码见仓库。
文章评论