本文逐渐涉及当前敏捷开发中非常火的DevOps相关的技术,也就是自动化开发运维。整体偏向于环境搭建,整体学习路线为:
(1)SpringBoot+Gradle+Nexus远程仓库构建打包工程jar
(2)搭建jenkins环境并自动关联打包好的工程jar进行自动发布
(3)搭建docker容器环境学习软件运行环境的搭建
(4)搭建K8S环境进行使用学习
后面容器化相关的将后面文章记录,本文主要对前两点进行总结。
(一)SpringBoot+Gradle工程构建打包工程jar
我们知道使用SpringBoot可以很容易搭建一个简单的web运行环境,主要是运用了springboot为我们自动配置的一系列配置和tomcat容器环境。但是当一个SpringBoot工程代码编写完之后需要发布,就没办法用那一套进行线上发布了,就必须要将需要运行的工程资源进行有机整合之后,再打包出来才可以进行线上发布。一般来说对于项目的构建打包通用可以用Maven或Gradle,由于Maven的构建打包非常耗时,因此一般来说使用Gradle可以很快替代Maven进行快速打包构建。
下面将以实际样例,介绍怎么使用Gradle打包构建出一个可以直接运行的jar/war工程,当然需要一定的Gradle基础或实际项目使用经验,关于Gradle的基础知识可以查阅小编的其它文章gradle基础+Groovy描述笔记(一)_喜欢火影的木易杨的博客-CSDN博客
不多说,直接show the code...
首先:新建一个Gradle SpringBoot项目可以看到build.gradle文件,添加启动类、properties文件、common.gradle文件,整体工程结构大致如下(由于最近在接触SpringCloudNacos相关的东西所以工程集成了Nacos的相关依赖,该工程主要是作为一个注册在Nacos作为服务方的工程):
(1)NacosProviderApplication:如果最后打包出来的工程可以正常启动,那么会有一行====日志输出
package com.alibaba.nacos.example.spring.cloud;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableDiscoveryClient
public class NacosProviderApplication {
public static void main(String[] args) {
SpringApplication.run(NacosProviderApplication.class, args);
System.out.println("=================NacosProviderApplication");
}
@Value("${server.port}")
private int port;
@RestController
class EchoController {
@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
public String echo(@PathVariable String string) {
return "Hello Nacos Discovery " + string + ",port:"+port;
}
}
}
(2)application.properties:主要提供Nacos的注册地址等环境信息
server.port=8071 spring.application.name=service-provider spring.cloud.nacos.discovery.enabled=true spring.cloud.nacos.discovery.server-addr=127.0.0.1:3333,127.0.0.1:4444,127.0.0.1:5555
接下来是最重要的两个配置文件。
(3)build.gradle:作为构建工具最核心的配置文件。这个配置文件我总结主要可以做下面几件事情:
a.声明需要使用的各种插件plugin,比如我们这里使用springboot的bootJar打包插件;
b.声明依赖仓库repositories:本地仓库、远程仓库等。我们本例子尽可能贴近项目实际情况将自行搭建一个Nexus远程仓库作为依赖仓库;
c.声明项目需要的依赖dependencies:主要是内部和第三方的各类jar等,包含运行时使用和编译时使用的jar;
d.引入子配置文件如apply from: 'common.gradle',复合多个不同工程等;
group 'org.example'
version '1.0-SNAPSHOT'
//禁止springboot gradle插件依赖管理功能
ext.disableDependencyManagementBom = true
buildscript{
//注意这个repositories必须要有,否则会报错。可以使用本地仓库和远程仓库
repositories{
mavenCentral()
}
dependencies{
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.0.8.RELEASE'
classpath 'org.springframework.boot:spring-boot-loader-tools:2.0.8.RELEASE'
classpath 'org.springframework:spring-core:5.0.12.RELEASE'
classpath 'org.apache.commons:commons-compress:1.14'
}
}
//注意这里使用org.springframework.boot的bootJar插件因此要引入这个插件
apply plugin: "org.springframework.boot"
apply plugin: 'java'
apply plugin: 'war'
//引入common.gradle辅助配置文件
apply from: 'common.gradle'
//声明仓库:一定要有否则所有jar都会找不到
repositories {
mavenCentral()
}
dependencies {
//这里存在nacos-client和httpclient冲突需要进行exclude
compile('org.springframework.cloud:spring-cloud-starter-alibaba-nacos-discovery:0.2.2.RELEASE'){
exclude group:'com.alibaba.nacos', module: 'nacos-client'
exclude group:'org.apache.httpcomponents', module: 'httpclient'
}
//这里存在hibernate-validator和httpclient冲突需要进行exclude
compile('org.springframework.boot:spring-boot-starter-web:2.0.8.RELEASE'){
exclude group:'org.hibernate', module: 'hibernate-validator'
exclude group:'org.apache.httpcomponents', module: 'httpclient'
}
//引入合适的版本进行替换,结合common.gradle的DuplicatesStrategy.INCLUDE
compile 'com.alibaba.nacos:nacos-client:1.1.0'
compile 'org.hibernate:hibernate-validator:5.1.0.Final'
compile 'org.apache.httpcomponents:httpclient:4.5.5'
}
在本例中,我们暂时使用了本地仓库后面远程仓库搭建后修改过来,同时引入了common.gradle文件辅助构建文件。这个common.gradle构建文件主要是声明一些我们使用的Task任务(Task是Gradle最基本的单元),实际的构建行为也是在这个文件里面定义的。
(4)common.gradle:声明各种Task任务,实际执行构建动作(任务为bootJar)的文件。
//JDK编译级别
def compatibilityVersion = 1.8
sourceCompatibility = compatibilityVersion
targetCompatibility = compatibilityVersion
//META-INF
version = "1.0.0.RELEASE"
ext{
companyEngName = "yang"
companyChName = "杨"
compilePath = "build/classes"
}
//jar归档信息
ext.sharedManifest = manifest{
attributes 'Built-company': companyEngName
attributes 'Implementation-Title' : project.name
attributes 'Implementation-Version' : version
attributes 'Gradle-Version' : gradle.gradleVersion
attributes 'Build-Jdk' : compatibilityVersion
attributes 'Build-Version' : version + "Build" + new Date().format('yyyyMMddHHmmss')
attributes 'Build-Verdor' : companyChName
attributes 'Created-Time' : new Date().format('yyyy-MM-dd HH:mm:ss')
}
war{
enabled = true
}
//必须要加这个task,否则构建时会报不存在该task导致无法构建
task compileDebugSources{
}
//禁掉jar task,一般都不用原生的jar而是使用插件提供的打包任务如bootjar
jar.enabled = false
// 将依赖包复制到lib目录
task copyJar(type: Copy) {
// 清除lib目录的历史文件
delete "build\libs\lib"
from configurations.compileClasspath
into "build\libs\lib"
}
// 拷贝class文件
task copyClassFile(type: Copy) {
// 清除classes目录的历史文件
delete "build\libs\classes"
from('src/main/java')
into 'build/libs/classes'
}
// 拷贝配置文件
task copyConfigFile(type: Copy) {
// 清除resources配置目录的历史文件
delete "build\libs\resources"
from('src/main/resources')
into 'build/libs/classes'
}
// 配置bootJar进行打包:真正进行构建动作的任务
bootJar {
baseName = companyEngName
appendix = project.name
archiveName = baseName + "-" + appendix + new Date().format('yyyyMMddHHmmss') + "." + extension
//jar重复冲突时的策略:以最后一个为准。这个有多种参数使用INCLUDE主要是针对有jar
//冲突时使用最后一个,避免了jar冲突导致无法构建。如本例就有nacos-client等jar存在冲突
duplicatesStrategy = DuplicatesStrategy.INCLUDE
// libs目录的历史文件清除和拷贝任务
dependsOn copyJar
dependsOn copyClassFile
dependsOn copyConfigFile
// 声明其它manifest信息,主要在META-INF文件中体现
manifest {
from sharedManifest
}
//将libs临时目录下生成的待打包文件,统一整合打包到同一个jar里
//注意这里不可以放在manifest里面,否则会报已经构建无法进行更改的错误,
//有很多其它博客误导大家就是放manifest里面了导致报错
from {
configurations.runtimeClasspath.files.collect { "libs/$it.name" }.join(' ')
}
}
在这里重点关注bootJar任务,这个任务原本是SpringBoot自动给我们提供出来的,但是默认提供出来的这个任务构建出来的工程jar没办法自定义,因此我们进行了改写从而可以自定义(比如添加manifest描述、jar包命名规则定义等)。
我们看下这个bootJar任务主要做些什么事?首先我们可以想一下如果让我们手工构建,我们需要做哪些事。首先我们会想到我们编写的文件有哪些文件、哪些是class文件、哪些是依赖的jar包、哪些是资源文件、哪些是配置文件等等,这些文件是不是都要放到jar包里发布出去的。
想清楚这一点很重要,因为这一点直接影响了我们要怎么编写构建脚本。就针对本例来说,我们需要把class文件、resources下的配置文件都打包到classes目录下,同时工程还依赖了很多jar包这些包都不在工程的/lib目录下而是引用了maven仓库的,因此这些间接引用的jar依赖也需要打包到jar工程里。明确后开始解析bootJar任务。
// 配置bootJar进行打包:真正进行构建动作的任务
bootJar {
baseName = companyEngName
appendix = project.name
//打包的jar名
archiveName = baseName + "-" + appendix + new Date().format('yyyyMMddHHmmss') + "." + extension
//jar重复冲突时的策略:以最后一个为准
duplicatesStrategy = DuplicatesStrategy.INCLUDE
// 将工程依赖的jar拷贝到临时目录build\libs\lib下
dependsOn copyJar
//将工程中所有的class文件拷贝到临时目录build/libs/classes下
dependsOn copyClassFile
// 将工程中所有的配置文件拷贝到临时目录build/libs/classes下
dependsOn copyConfigFile
// 声明自定义的manifest信息
manifest {
from sharedManifest
}
from { //将libs目录下所有文件统一打包到一个jar里
configurations.runtimeClasspath.files.collect { "libs/$it.name" }.join(' ')
}
}
上面copyJar、copyClassFile、copyConfigFile分别就是进行简单的文件移动而已,如果有哪些文件是不需要移动需要剔除的可以结合Exclude命令进行剔除。
最后在控制台输入:gradle bootJar 就可以在build/libs目录下给我们自动构建出一个jar了:
我们用解压工具打开这个jar看下内容:
最后,我们通过控制台命令 jar -jar xxxxx.jar运行这个jar,可以看到打包后的工程正常发布了:
要特别注意使用gradle编写脚本时遇到了不少坑,经过一整天摸索才逐个解决,在gradle文件中都强调注释出来了,如果遇到可以往这个方向思考。
(二)搭建jenkins环境并自动关联打包好的工程jar进行自动发布



