SpringBoot的部署上,个人习惯于代码部分打入镜像,配置项以外部挂载的方式进行关联,从而进行代码和配置的解耦合。这也符合程序设计上把易变部分和不变部分进行分离的思想。在部署不同环境的时候(例如,test、dev、staging、product)只需要一个镜像即可,只需创建对应的配置对象即可。这里的配置对象在docker上可以是文件夹,每个文件夹对应一个环境,docker run的时候通过 -v 命令进行关联,在kubernetes上可以是一个configMap对象。
需要外提的配置项一般有application.porperties一族(application-mysql.porperties、application-redis.porperties、application-kafka.porperties、application-otherMiddleware.porperties等),和日志配置一族(logback.xml、log4j.xml等)。下面针对这几种配置文件进行分析。
application.porperties
文件分析
一般用于代码中的属性注入,例如:
@Component
public class MyBean {
@Value("${name}")
private String name;
// ...
}
通过@Value注解,可以把properties中name的值注入到代码中,实现代码和配置文件的解耦合。它的实现依赖于设置java线程中的环境变量,因此它也可以通过命令行覆盖,例如:
java -jar xx.jar --name="nick"
执行上面命令,就会用命令行中的name代码properties中的name覆盖。
application.properties文件外提:
常用的有两种方式:
第一种,在主类上加一个注解@PorpertySource(value={"classpath:a/b/c/application.properties"})这种方式用于项目内文件移动。@PorpertySource(value={"file:/a/b/c/application.properties"})这种方式用于外提,需要使用绝对路径,尽量不要用相对路径。在jar外部创建对应的目录 /a/b/c/(这个目录不需要和代码中一致,在挂载的时候指向正确文件路径即可),并放入application.properties,如果是docker,使用-v /a/b/c/:/a/b/c/ 把宿主机的application.properties文件挂入容器即可。
第二种,在命令行中修改,通过-Dspring.config.location指定。
java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
上面例子改为绝对路径的宿主机地址即可实现外部挂载配置。而且路径也支持通配符的方式进行挂载。
除此之外,还有--spring.config.additional-location支持自定义配置载入。
application.properties文件切换:
如果配置文件打在镜像内部,且有多个版本的配置(例如,application-test.porperties、application-dev.porperties、application-staging.porperties、application-product.porperties),在启动的时候根据环境选择对应的配置文件,执行命令:
-Dspring.profiles.active=dev
传入配置后部分名称进行选择,匹配对应的properties文件,默认是-default.properties。
也可以在application.properties中添加
spring.profiles.active=dev
区别上一个在命令行里指定的方式。但是它们的核心思想没变:把properties文件中的内容写到环境变量里,在application.properties中定义,则默认加载了application.properties所有内容,并把spring.profiles.active指定的properties文件加载到环境变量。此处需要注意,如果两个配置文件中如果有key重复,会发生覆盖的问题。
application.properties使用占位符${}:
如果配置文件只有一份,且打在镜像内,可以考虑使用此方式。通过占位符的方式进行配置。
spring.datasource.url = ${OPENSHIFT_MYSQL_DB_HOST}:${OPENSHIFT_MYSQL_DB_PORT}/"nameofDB"
spring.datasource.username = ${OPENSHIFT_MYSQL_DB_USERNAME}
spring.datasource.password = ${OPENSHIFT_MYSQL_DB_PASSWORD}
这种方式需要在启动java进程的时候创建以上环境变量,在docker中可以通过 -e 的命令创建:
docker run --name my-tomcat -p 127.0.0.1:8080:8080 -e APP_DB_DB=mydb -e APP_DB_USER=dbuser -e APP_DB_PASS=dbpass --link mongo-myapp:mongo -v /path-to/tomcat/webapps:/usr/local/tomcat/webapps -d tomcat:8-jre8-alpine
在kubernetes中,yaml中的containers的下一层加入:
env:
- name: db_host
value: "127.0.0.1"
- name: db_port
value: 27017
...
logbak.xml
以logback.xml为例,外提分几种方式:
通过application.properties修改:
在application.properties中添加配置
logging.config=/home/dev-01/Documents/logback.xml
通过命令行修改:
java -jar xx.jar -Dlogging.config=/home/dev-01/Documents/logback.xml
通过include的方式修改日志内容:
这种方式比较特殊,也很非主流,在上述两种方式都不生效的情况下采取这种方式实现。经过查资料,说springboot加载logback配置只会在classpath下,如何外提都不生效(具体问题讨论),因此classpath下保留原始配置,但是把关键配置(appender)提取到另一个xml中,通过include的方式引入。
在classpath中的logback.xml中加入:
<include file="/a/b/c/logback-ext.xml">
logback-ext.xml中需要以<included>开始,包含主xml的关键信息即可。例如:
<included>
<appender>
...
</appender>
</included>
需要注意缩进,如果错误可能无法include成功。
configuration
|
|———— property(全局属性定义,用作占位符替换 ${xxx})
|———— appender(日志输出方式)
| |
| |—— ConsoleAppender
| |__ RollingFileAppender
| |__ FileAppender...
|———— logger(和代码中LoggerFactory.getLogger(xxx)返回的logger对象对应)
|———— root (可以有多个,会分别执行)
|———— include (这种特殊情况下会使用)
// 传入一个class,对应配置项中logger的name,clazz最后会用class.getName()寻找到logger,因此是以包名的全限定名决定。
// 如果 clazz是a.b.c.d,logger有name是a.b.c.d的就会匹配上,否则按a.b.c, a.b, a依次匹配
// 而且依次有继承关系,logger的继承是按包名的,且都会继承root
Logger LOGGER = LoggerFactory.getLogger(Class clazz);
// str 和 class.getName()一致
Logger LOGGER = LoggerFactory.getLogger(String str);