1、前言
项目使用spring boot编写使用yaml编写sping boot的配置。同时使用profiles的active这个配置选项激活不同的配置文件,达到区分测试和生产环境配置的目的,其中环境的配置文件格式是这样的:application-xxx.yml,其中xxx是具体要激活的配置。具体的application.yml负责加载不同配置,application具体代码如下:
spring:
profiles:
active: dev
logging:
config: classpath:logback-spring.xml
其中的问题就是每次打包都要改动application.yml的active选项,很不方便,有时打包还会忘记改动配置。所以就有编写一个gradle打包测试和正式环境的war包的需求。
基本思路是重写bootWar脚本,再者观察到是一些列的操作再到bootWar操作的,而且gradle的task是有前置和后置方法的,所以在前置方法中通过文件读写改变application.yml的active的值,再执行bootWar操作即可完成目标war的生成。
2、系统环境
jdk版本:1.8
gradle版本:v5.1
3、BootWar脚本
编写独立的task.gradle
buildscript {
ext {
springBootVersion = '2.0.2.RELEASE'
//启动类名称
startClass = 'OperationDecisionApplication'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
task bootDevWar(type: org.springframework.boot.gradle.tasks.bundling.BootWar) {
def env = "dev"
doFirst {
def file = new File("${buildDir}/resources/main/application.yml")
println("正在编译war包---环境[${env}]")
println("${buildDir}")
def profile = ""
file.eachLine { line ->
if (line.endsWith("prod") && !(line = line.replace("prod", "dev")) == ("")) {}
profile += line + "\r\n"
}
println(profile)
file.newWriter(false).with {
it.write(profile)
it.flush()
it.close()
}
}
group 'build War'
description 'pack a dev war'
mainClassName = "com.XXX.${startClass}"//启动类
destinationDir = file("build/libs/${env}")
}
task bootProdWar(type: org.springframework.boot.gradle.tasks.bundling.BootWar) {
def env = "prod"
doFirst {
def file = new File("${buildDir}/resources/main/application.yml")
println("正在编译war包---环境[${env}]")
println("${buildDir}")
def profile = ""
file.eachLine { line ->
if (line.endsWith("dev") && !(line = line.replace("dev", "prod")) == ("")) {}
profile += line + "\r\n"
}
println(profile)
file.newWriter(false).with {
it.write(profile)
it.flush()
it.close()
}
}
group 'build War'
description 'pack a dev war'
mainClassName = "com.XXX.${startClass}"//启动类
destinationDir = file("build/libs/${env}")
}
下面解释一下,一些知识点:
- buildscript引入构建gradle文件自解析文档结构的能力,让 build.gradle能够引入这个task.gradle。
- type: org.springframework.boot.gradle.tasks.bundling.BootWar是继承语法,继承自org.springframework.boot.gradle.tasks.bundling.BootWar,这个全路径类
- 需要引入spring-boot-gradle-plugin,来支持bootWar脚本,classpath(“org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}”)
- doFirst方法是task接口实现类的前置方法,在具体方法执行之前执行
- group 'build War’将gradle的task任务分组显示
- mainClassName是Spring Boot的启动Application类,不配置这个war包是启动不了的
- destinationDir是War文件的输出路径
这是分组的效果:
在build.gradle中用apply from:“task.gradle”,引入写的gradle,如果觉得这样子不好,也可以直接在build.gradle中写task。
文件路径如下:
4、BootJar脚本
note: 因为差不多内容,所以把BootJar的脚本补在这里
项目使用的是application.properties,所以占位符跟yaml的差不多。效果如下:
基本思路是,通过finalizedBy关键字设置后置执行bootJar,在执行前,当前Task任务变更build/libs下的application.properties的内容,同时更改bootJar输出路径。
脚本如下:
buildscript {
ext {
springBootVersion = '2.1.4.RELEASE'
//启动类名称
projStartClass = 'EcApplication'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
task bootDevJar() {
def env = "dev"
doFirst {
bootJar.destinationDir = file("build/libs/${env}")
def file = new File("${buildDir}/resources/main/application.properties")
println("正在编译war包---环境[${env}]")
println("${buildDir}")
def profile = ""
file.eachLine { line ->
if (line.endsWith("prod") && !(line = line.replace("prod", "dev")) == ("")) {
}
profile += line + "\r\n"
}
println(profile)
file.newWriter(false).with {
it.write(profile)
it.flush()
it.close()
}
}
group 'build Jar'
description 'pack a dev jar'
dependsOn("compileJava", "processResources", "classes")
}
bootDevJar.finalizedBy bootJar
task bootProdJar() {
def env = "prod"
doFirst {
bootJar.destinationDir = file("build/libs/${env}")
def file = new File("${buildDir}/resources/main/application.properties")
println("正在编译war包---环境[${env}]")
println("${buildDir}")
def profile = ""
file.eachLine { line ->
if (line.endsWith("dev") && !(line = line.replace("dev", "prod")) == ("")) {
}
profile += line + "\r\n"
}
println(profile)
file.newWriter(false).with {
it.write(profile)
it.flush()
it.close()
}
}
group 'build Jar'
description 'pack a dev jar'
dependsOn("compileJava", "processResources", "classes")
}
bootProdJar.finalizedBy bootJar
bootJar {
doLast {//清除变量
bootJar.destinationDir = file("build/libs")
}
}
这次实现没有采用继承的方式,而是用采用执行链中更改前置task更改后置task的方式变量实现。不采用继承的主要原因是BOOT-INF文件夹下东西没有打包进jar包,就我个人观察而言,是BootJar的构造方法不执行。
这个关系关系链如下:compileJava -> processResources -> classes - > bootProdJar/bootDevJar -> bootJar
分三步走:
- 在compileJava -> processResources -> classes中生成打包相关临时文件
- bootProdJar/bootDevJar中更改临时文件application.properties达到更改配置文件-
- bootJar使用前面生成和处理的临时文件打包
dependsOn(“compileJava”, “processResources”, “classes”)是设置前置task链,bootProdJar.finalizedBy bootJar
是设置后置任务bootJar
bootProdJar/bootDevJar的doFirst中通过bootJar.destinationDir = file(“build/libs/${env}”)更改bootJar文件的输出路径
在bootJar的doLast将变量置回默认,防止影响默认的bootJar命令。
在build.gradle中用apply from:“task.gradle”,引入写的gradle,如果觉得这样子不好,也可以直接在build.gradle中写task。
文件路径如下:
5、ssh的put脚本
因为代码写在task.gradle文件,所以在build.gradle中写添加ssh插件:
在apply from:"task.gradle"上一行填入apply plugin: ‘org.hidetake.ssh’,引入gradle的ssh插件。
为org.hidetake.ssh插件内置变量
赋值:
//远程服务器信息
remotes {
devService {
host = '106.12.128.166'
user = 'root'
password = 'passwd'
knownHosts = allowAnyHosts//加了这个才能ssh上传文件
}
prodService {
host = '134.175.85.159'
user = 'ubuntu'
password = 'passwd'
retryCount = 1 //重连次数,0为取消重新
retryWaitSec = 3
knownHosts = allowAnyHosts
}
}
war包的具体逻辑:
task putWar {
group 'build War'
description 'put war to service and execute'
doLast {
if (!project.hasProperty('isRelease')) {
println "没有设置env变量,任务停止"
return
}
def env = project.getProperties().get("isRelease")=="true"?"prod":"dev"
println "put war pack to " + project.getProperties().get("env") + " service"
if (env == "dev") {
project.ssh.run {
session(project.remotes.DevService) {
put from: "${buildDir}/libs/${env}/decision-external.war", into: "${tomcatRootDic}/webapps/"
execute "${tomcatRootDic}/bin/tomcat-restart.sh"
}
}
println "put war pack to ${env} service ${project.remotes.DevService.host}"
} else if (env == "prod") {
project.ssh.run {
session(project.remotes.DevService) {
put from: "${buildDir}/libs/${env}/decision-external.war", into: "${tomcatRootDic}/webapps/"
execute "${tomcatRootDic}/bin/tomcat-restart.sh"
}
}
println "put war pack to ${env} service ${project.remotes.DevService.host}"
}
}
}
jar包的具体逻辑:
task putJar() {
group 'build Jar'
description 'put jar to service and execute'
doLast {
if (!project.hasProperty('isRelease')) {
println "没有设置env变量,任务停止"
return
}
def env = project.getProperties().get("isRelease")=="true"?"prod":"dev"
println "put jar pack to ${env} service"
if (env == "dev") {
project.ssh.run {
session(project.remotes.devService) {
put from: "${buildDir}/libs/${env}/ec-1.0.jar", into: "/data/"
execute "/data/restart-jar.sh"
}
}
println "put jar pack to ${env} service ${project.remotes.devService.host}"
} else if (env == "prod") {
project.ssh.run {
session(project.remotes.prodService) {
put from: "${buildDir}/libs/${env}/ec-1.0.jar", into: "/data/"
execute "/data/restart-jar.sh"
}
}
println "put jar pack to ${env} service ${project.remotes.prodService.host}"
}
}
}
这里具体要执行的东西,仅供参考,因为这是两个项目的put方法的实现,稍微修改一下即可。