用Gradle構建Spring Boot項目

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

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

建立Gradle工程

Gradle採用了與Maven相同的目錄組織結構,你能夠經過Spring Initializr網站建立Spring Boot工程。可是在本文中,咱們將所有經過命令行操做建立Spring Boot工程。首先在命令行中建立以下目錄結構:git

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

而後在src同級目錄中添加一個build.gradle文件,內容以下:github

apply plugin: 'java'

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

gradle build

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

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

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有如下好處:app

  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插件對代碼風格/格式作了靜態檢查。

相關文章
相關標籤/搜索