用Gradle構建Spring Boot項目


相比起Maven的XML配置方式,Gradle提供了一套簡明的DSL用於構建Java項目,使我們就像編寫程序一樣編寫項目構建腳本。本文將從無到有創建一個用Gradle構建的Spring Boot項目,並在此過程中講到Gradle的一些典型用法。

本文Github代碼:https://github.com/davenkin/gradle-spring-boot.git


創建Gradle工程

Gradle采用了與Maven相同的目錄組織結構,你可以通過Spring Initializr網站創建Spring Boot工程。但是在本文中,我們將全部通過命令行操作創建Spring Boot工程。首先在命令行中創建如下目錄結構:

└── src
    ├── main
    │   └── java
    └── test
        └── java

然后在src同級目錄中添加一個build.gradle文件,內容如下:

apply plugin: 'java'

大功告成,一個用Gradle構建的Java項目創建好了,盡情用以下命令編譯並打包咱們的Java項目吧:

gradle build

只是現在咱們的Java項目還是一個空架子,不用急,在下文中我們將一步一步在這個空架子中搭建一個有血有肉的Spring Boot項目。

值得一提的是,雖然此時的build.gradle文件中只有一行配置(apply plugin: 'java',作用是引入java插件),但是其背后已經幫我們做了很多事情,比如它使得我們能夠運行gradle build命令。這里的build即為Gradle中的一個任務(Task),我們還可以運行以下命令查看到更多的Task。

gradle tasks

此時輸出:

...
Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.
...

這里的assemble、build和jar等Task都是java插件引入的。build.gradle是Gradle的配置文件,更多關於Gradle的知識請參考筆者的Gradle學習系列文章


使用Gradle Wrapper

對於所有的Gradle項目來說,筆者都推薦使用Gradle Wrapper,甚至應該將其當做創建代碼庫之后的第一件事來做。使用Gradle Wrapper有以下好處:

  1. 不用安裝gradle也能運行gradle
  2. 所有人使用相同的gradle版本

在build.gradle中加入以下配置:

task wrapper(type: Wrapper) {
    gradleVersion = '3.0'
}

然后在命令行運行:

gradle wrapper

此時會生成以下三個文件(夾):gradlew、gradlew.bat和gradle目錄。

這里的gradlew和gradlew.bat其實只是腳本文件(前者用於Unix/Linux/Mac,后者用於Windows),在使用gradle命令的地方替換為gradlew或gradlew.bat,他們將自動下載指定的gradle版本,然后用該版本進行項目構建。如上文所示,本文中我們配置gradle版本為3.0。

請注意,這三個文件(夾)都需要提交到代碼庫中。當項目其他人拿到代碼之后,由於gradlew和gradlew.bat文件均在源代碼中,他們本地即便沒有gradle,依然可以通過以下命令進行項目構建:

./gradlew build

如果你的項目有持續集成(CI)服務器(你也應該有),那么你的CI機器也沒有必要安裝Gradle了。另外,此時所有人都是使用的相同版本的gradle,進而避免了由於版本不同所帶來的問題。


添加Spring Boot依賴

在本文中,我們的業務非常簡單———輸出“Hello World!”。但是麻雀雖小,五臟俱全,首先需要在build.gradle中配置spring-boot插件,並引入Spring的Web組件,整個build.gradle如下:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
    }
}

repositories {
    jcenter()
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'


sourceCompatibility = 1.8
targetCompatibility = 1.8


task wrapper(type: Wrapper) {
    gradleVersion = '3.0'
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    testCompile("org.springframework.boot:spring-boot-starter-test")
}

然后創建Application類:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

依然很簡單,是吧?!這個Application類便是Spring Boot程序的入口。另外我們還需要一個Controller和一個業務類HelloWorld:

HelloWorldController:

@RestController("/helloworld")
public class HelloController {

    private HelloWorld helloWorld;

    public HelloController(HelloWorld helloWorld) {
        this.helloWorld = helloWorld;
    }

    @GetMapping
    public String hello() {
        return helloWorld.hello();
    }
}

HelloWorld:

@Component
public class HelloWorld {

    public String hello() {
        return "Hello World!";
    }
}

此時工程的目錄結構為:

├── README.md
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
    ├── main
    │   └── java
    │       └── davenkin
    │           ├── Application.java
    │           ├── HelloController.java
    │           └── HelloWorld.java
    └── test
        └── java

然后運行:

./gradlew bootRun

在瀏覽器或者Postman中打開http://localhost:8080/gradle-spring-boot/helloworld,便可以看到久違的"Hello World!"了。


生成IDE工程文件

我曾經看到不少人在Eclipse或者IntelliJ IDEA中導入Maven/Gradle工程,甚至在IDE中使用嵌入Tomcat容器。我並不推薦這么做,這些嚴重依賴於GUI操作的功能其實是很笨拙、很脆弱的。以嵌入Tomcat容器為例,它要求項目中所有人都在自己的IDE中手動地對Tomcat進行配置,而手動的過程總是容易出錯的。在持續交付中有個原則是“凡是能夠自動化的,都應該自動化”,這里的自動化說白了其實就是代碼化。

因此,在使用Gradle時,筆者更推崇的一種方式是通過Gradle的IDE插件一鍵式地生成IDE工程文件,然后在IDE中直接打開這樣的工程文件。這樣的好處一是非常簡單,二是所有人都使用了相同的IDE配置。

在Gradle中配置IntelliJ IDEA插件,只需在build.gradle中配置:

    apply plugin: 'idea'

然后運行:

./gradlew idea

此時將生成后綴為ipr的IntelliJ IDEA工程文件,在IntelliJ IDEA中直接打開(Open)該文件即可。

對於Eclipse,在build.gradle中增加以下配置:

    apply plugin: 'eclipse'

然后運行:

./gradlew eclipse

此時將生成Eclipse的.project工程文件。

請注意,所有IDE工程文件都不應該提交到代碼庫,對於Git來說應該將這些文件注冊到.gitignore文件中。各個開發者拿到代碼后需要各自運行./graldlw idea或./gradlew eclipse命令以生成本地工程文件。


調試

至少有兩種方式可以對Spring Boot項目進行調試。一種是直接運行命令:

./gradlew bootRun --debug-jvm

此時程序將默認監聽5005端口,並暫停以等待調試客戶端的連接,然后啟動Spring Boot。

另一種方式是使用Gradle的Application插件,在build.gradle中添加:

apply plugin: 'application'
applicationDefaultJvmArgs = [ "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" ]

此時運行:

./gradlew bootRun

程序將啟動並監聽5005調試端口,但是與第一種方法不同的是,程序不會暫停,而是將直接啟動整個Spring Boot程序。如果你想調試Spring Boot在啟動過程中的某些代碼,比如Spring框架啟動代碼,那么請選擇第一種方式;否則,第二種是更合適的選擇,因為我們並不是每次啟動程序都一定會調試的,對吧?!


自動化測試

軟件項目可以包含多種自動化測試,比如單元測試、集成測試、功能測試等。對於Spring Boot項目來說,筆者推薦將自動化測試划分為單元測試和API測試,其中單元測試即是傳統的單元測試,而API測試包含了集成測試、功能測試和端到端測試的功能,它的測試對象是程序向外暴露的REST API接口,在測試時我們需要啟動整個Spring Boot程序,然后模擬客戶端調用這些API接口來完成業務測試用例。

單元測試相對比較簡單,Spring Boot也提供了一些有助於單元測試的設施,但是我並不推薦大家使用,因為單元測試應該是非常純粹、粒度非常小的測試,不應該有框架摻和。

通常來說,單元測試和API測試應該是分離的,也即他們的代碼應該是分開的,運行測試的命令也應該是不同的。但是這給Gradle帶來了難題,因為默認情況下Gradle只提供一個./gradlew test命令用於測試,並且默認要求測試代碼位於src/test/java目錄下。為此,我們需要對Gradle進行改造。

我們的目的是:

  • 默認的src/test/java目錄用於單元測試代碼,通過./gradlew test運行
  • 新建src/apiTest/java目錄用於API測試代碼,通過./gradlew apiTest運行

可以看到,我么將Gradle默認的測試設施用於了單元測試,也即對於單元測試我們不需要做任何改變。對於API測試而言,首先我們需要添加名為apiTest的源代碼集合(SrouceSet),該SourceSet即對應了src/apiTest/java目錄,在build.gradle文件中增加如下配置:

sourceSets {
    apiTest {
        compileClasspath += main.output + test.output
        runtimeClasspath += main.output + test.output
    }
}

configurations {
    apiTestCompile.extendsFrom testCompile
    apiTestRuntime.extendsFrom testRuntime
}


然后,添加一個Test類型的Task用於運行src/apiTest/java目錄下的API測試代碼:

task apiTest(type: Test) {
    testClassesDir = sourceSets.apiTest.output.classesDir
    classpath = sourceSets.apiTest.runtimeClasspath
}

為了使Intelli IDEA能夠感知到這些新添加的測試代碼,我們需要對Gradle的idea插件進行額外配置:

idea {
    module {
        testSourceDirs += file('src/apiTest/java')
        testSourceDirs += file('src/apiTest/resources')
        scopes.TEST.plus += [configurations.apiTestCompile]
        scopes.TEST.plus += [configurations.apiTestRuntime]
    }
}

另外,為了使本地構建(./gradlew biuld)過程能夠先運行單元測試,再運行API測試,我們還需要做以下配置:

apiTest.mustRunAfter test
build.dependsOn apiTest

第一行的意思是API測試必須運行在單元測試之后,第二行的意思是將API測試包含在build任務中。


使用JaCoCo

JaCoCo是一款代碼測試覆蓋率統計工具,我們主要將其用於統計單元測試的覆蓋率。在build.gradle中增加配置:

apply plugin: "jacoco"

此時運行./gradlew build之后,JaCoCo將在build/jacoco目錄下為單元測試和API測試分別生成原始數據文件(test.exec和apiTest.exec),但是此時並沒有測試報告生成,為此,我們還需要單獨運行:

./gradlew jacocoTestReport

在瀏覽器中打開build/report/jacoco/test/index.html,你將看到單元測試覆蓋率報告:

單元測試覆蓋率報告

但是,此時的覆蓋率報告只是針對單元測試的,為了得到API測試的覆蓋率,我們需要添加一個新的Task:

task jacocoApiTestReport(type: JacocoReport){
    sourceSets sourceSets.main
    executionData apiTest
}

然后運行:

./gradlew jacocoApiTestReport

在瀏覽器中打開build/report/jacoco/jacocoApiTestReport/index.html,你將看到單元測試覆蓋率報告。

有時,我們希望看到單元測試和API測試的整體覆蓋率,此時我們需要再添加一個Task:

//Unit Test and API Test Code coverage all together
task jacocoAllTestReport(type: JacocoReport){
    sourceSets sourceSets.main
    executionData test, apiTest
}

然后運行:

./gradlew jacocoAllTestReport

在瀏覽器中打開build/report/jacoco/jacocoAllTestReport/index.html,你將看到所有測試整合后的覆蓋率報告。

作為演示,我們在HelloWorld中添加一個新的anotherHello()方法,此時HelloWorld為:

@Component
public class HelloWorld {

    public String hello() {
        return "Hello World!";
    }

    public String anotherHello() {
        return "Another Hello World!";
    }
}

對應的HelloWorldController也變為:

@RestController
public class HelloController {

    private HelloWorld helloWorld;

    public HelloController(HelloWorld helloWorld) {
        this.helloWorld = helloWorld;
    }

    @GetMapping("/helloworld")
    public String hello() {
        return helloWorld.hello();
    }


    @GetMapping("/anotherHelloworld")
    public String anotherHello() {
        return helloWorld.anotherHello();
    }
}

然后,我們讓HelloWorld的單元測試只測試hello()方法,讓API測試只測試anotherHello()方法(也即只調用“anotherHelloworld”的URL接口)。

此時單元測試覆蓋率為:

單元測試覆蓋率

可以看到,anohterHello()方法沒有被單元測試覆蓋到。而集成測試雖然覆蓋到了anotherHello()方法,卻沒有覆蓋到hello()方法:

API測試覆蓋率

總體測試覆蓋率為:

總體測試覆蓋率

此時,總體測試覆蓋率同時統計了單元測試和集成測試的覆蓋率。


使用Checkstyle

CheckStyle是一種靜態代碼檢查工具,主要用於檢查代碼風格或格式是否滿足要求。首先,我們需要一份配置文件來配置這樣的要求,這里我們采用Google的Checkstyle配置文件。

在biuld.gradle中增加checkstyle插件:

apply plugin: 'checkstyle'

下載Google的checkstyle文件並將其拷貝為config/checkstyle/checkstyle.xml,Gradle的checkstyle插件默認將讀取該配置文件。CheckStyle檢查將包含在./gradlew build中。注:在筆者電腦上,使用Google原始Checkstyle配置文件總是報錯,對Checkstyle進行了一些精簡之后運行成功。


總結

在本文中,我們從無到有創建了一個使用Gradle構建的Spring Boot項目,包括了對項目的編譯打包、運行單元測試和API測試,並且獲得測試覆蓋率報告。另外,我們提倡使用Gradle的idea/eclipse插件生成IDE工程文件,最后我們使用Checkstyle插件對代碼風格/格式做了靜態檢查。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM