重拾後端之Spring Boot(六) -- 熱載入、容器和多專案

接灰的電子產品發表於2017-06-28

重拾後端之Spring Boot(一):REST API的搭建可以這樣簡單
重拾後端之Spring Boot(二):MongoDb的無縫整合
重拾後端之Spring Boot(三):找回熟悉的Controller,Service
重拾後端之Spring Boot(四):使用 JWT 和 Spring Security 保護 REST API
重拾後端之 Spring Boot(五) -- 跨域、自定義查詢及分頁
重拾後端之Spring Boot(六) -- 熱載入、容器和多專案

這一章主要總結一下 Spring Boot 相關的環境配置和工具支援。

Spring Boot 的熱載入 (Live Load)

Spring Boot 內建提供了支援熱載入的功能,這個機制是通過 spring-dev-tools 來實現的。要實現這樣的功能,需要以下幾個步驟

第一步:在專案依賴中新增 spring-dev-tools:在 build.gradle 中新增

dependencies {
     compile("org.springframework.boot:spring-boot-devtools")
 }複製程式碼

第二步:由於我們會連線到應用的一個自動裝載器上,所以需要提供一個共享金鑰:在 application.ymlapplication.properties 中新增

如果是 application.yml 的話,請按此填寫:

spring:
  devtools:
    remote:
      secret: thisismysecret複製程式碼

如果是 application.properties 的話,請按此填寫:

# 如果是 application.properties 請按此填寫
spring.devtools.remote.secret=thisismysecret複製程式碼

第三步:在 Intellij IDEA 當中的 Preference -> Build, Execution, Deployment -> Compiler 中勾選 Build project automatically

IDEA 中勾選 Build project automatically
IDEA 中勾選 Build project automatically

在 IDEA 的 registry 中勾選 compiler.automake.allow.when.app.running (macOS 下使用 option + command + shift + / ,Windows 下使用 Ctrl + Alt + Shift + / 調出 registry 選單)

用快捷鍵調出 registry 選單
用快捷鍵調出 registry 選單

然後尋找到 compiler.automake.allow.when.app.running,進行勾選

勾選程式執行時允許編譯器自動構建
勾選程式執行時允許編譯器自動構建

最後重啟 IDE,在 Run/Debug Configurations 中新建一個 Spring Boot 模板的配置。其中 Main Class 填入 org.springframework.boot.devtools.RemoteSpringApplication (注意哦,點右邊的省略號按鈕勾選 include non-project classes 的選擇才有效)。然後在 Program arguments 中填入服務地址,比如你的服務埠是 8090 就填 http://localhost:8090Working Directory 填寫 $MODULE_DIR$,而 Use classpath of module 選擇當前專案即可。

建立一個 Spring Boot 的 Run/Debug 模板配置
建立一個 Spring Boot 的 Run/Debug 模板配置

遠端除錯

IDEA 提供了非常良好的遠端除錯支援,新增遠端除錯的話,可以去 Run/Debug Configurations 中新建一個 Remote 型別的配置,其中預設埠為 8000,Transport 選擇 SocketDebug Mode 選擇 Attach 即可。這種遠端除錯可以支援在 Docker 容器中進行除錯,方便團隊的環境容器化。

新增遠端除錯
新增遠端除錯

容器支援

使用容器(Docker)來發布 Spring Boot 專案非常簡單。如果你採用 Gradle 構建的話,可以不用寫 Dockerfile ,直接在 build.gradle 中建立一個 buildDocker 的任務即可。當然要支援這樣的任務的話,我們需要首先在 buildscriptdependencies 中引入 se.transmode.gradle:gradle-docker 的類庫,然後應用 docker 外掛(apply plugin: 'docker'

buildscript {
    // 省略其他部分
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath('se.transmode.gradle:gradle-docker:1.2')
    }
}
apply plugin: 'docker'

// docker 的 group
group = 'wpcfan'

// 建立 docker image
task buildDocker(type: Docker, dependsOn: build) {
    baseImage = 'frolvlad/alpine-oraclejdk8:slim' // 基於 jdk 的映象擴充
    tag = 'wpcfan/taskmgr-backend' // 要推送到 docker hub 的『組名/專案名』
    push = true
    applicationName = jar.baseName
    addFile {
        from jar
        rename {'app.jar'}
    }
    entryPoint([
            'java',
            '-Xdebug -Xrunjdwp:server=y,transport=dt_socket,suspend=n',
            '-Dspring.data.mongodb.uri=mongodb://mongodb/taskmgr',
            '-Djava.security.egd=file:/dev/./urandom',
            '-jar',
            '/app.jar'
    ])
    exposePort(8090)
}複製程式碼

其中 entryPoint 中的幾個引數含義分別是

-Xdebug -Xrunjdwp:server=y,transport=dt_socket,suspend=n 是讓容器可以支援 IDEA 遠端除錯
-Dspring.data.mongodb.uri=mongodb://mongodb/taskmgr 是指定 mongodb 的連線 URL,因為我們的 mongodb 也是容器,所以連線方式上需要使用『協議/主機名/資料庫』的方式。

兩個容器如果互相需要通訊的話,如果在不同主機,指定 IP 是可以的,但在同一個主機上怎麼破呢?這時候我們就需要利用 --link 指定要連線的容器(該選項後跟的是容器名稱),比如我們要連線 mongodb 容器,就寫成下面的樣子就行了。

docker run -p 80:8090 --name taskmgr-backend wpcfan/taskmgr-backend --link mongodb複製程式碼

多專案構建

在大型軟體開發中,一個專案解決所有問題顯然不可取的,因為存在太多的開發團隊共同協作,所以對專案進行拆分,形成多個子專案的形式是普遍存在的。而且一個子專案只專注自己的邏輯也易於維護和擴充,現在隨著容器和微服務的理念逐漸獲得大家的認可,多個子專案分別釋出到容器和形成多個微服務也逐漸成為趨勢。

我們的專案使用 Gradle 來處理多專案的構建,包括大專案和子專案的依賴管理以及容器的建立等。對於 Gradle 專案來說,我們會有一個根專案,這個根專案下會建立若干子專案,具體檔案結構如下:

|--spring-boot-tut (根專案)
|----common (共享子專案)
|------src (子專案原始碼目錄)
|--------main (子專案開發原始碼目錄)
|----------java(子專案開發 Java 類原始碼目錄)
|----------resources(子專案資源類原始碼目錄)
|------build.gradle (子專案 gradle 構建檔案)
|----api (API 子專案)
|------src
|--------main
|----------java
|----------resources
|------build.gradle
|----report (報表子專案)
|------src
|--------main
|----------java
|----------resources
|------build.gradle
|--build.gradle (根專案構建檔案)
|--settings.gradle (根專案設定檔案)複製程式碼

要讓 Gradle 支援多專案的話,首先需要把 settings.gradle 改成

include 'common'
include 'api'
include 'report'

rootProject.name = 'spring-boot-tut'複製程式碼

這樣 spring-boot-tut 就成為了根專案,而 commonapireport 就是其之下的子專案。接下來,我們看一下根專案的 build.gradle,對於多專案構建來說,根專案的 build.gradle 中應該儘可能的配置各子專案中共同的配置,從而讓子專案只配置自己不同的東西。

// 一個典型的根專案的構建檔案結構
buildscript {
    /*
     * 構建指令碼段落可以配置整個專案需要的外掛,構建過程中的依賴以及依賴類庫的版本號等
     */
}

allprojects {
    /*
     * 在這個段落中你可以宣告對於所有專案(含根專案)都適用的配置,比如依賴性的倉儲等
     */
}

subprojects {
    /*
     * 在這個段落中你可以宣告適用於各子專案的配置(不包括根專案哦)
     */
    version = "0.0.1"
}

/*
 * 對於子專案的特殊配置
 */
project(':common') {

}

project(':api') {

}

project(':report') {

}複製程式碼

其中,buildscript 段落用於配置 gradle 指令碼生成時需要的東西,比如配置整個專案需要的外掛,構建過程中的依賴以及在其他部分需要引用的依賴類庫的版本號等,就像下面這樣,我們在 ext 中定義了一些變數來集中配置了所有依賴的版本號,無論是根專案還是子專案都可以使用這些變數來指定版本號。這樣做的好處是當依賴的版本更新時,我們無需四處更改散落在各處的版本號。此外在這個段落中我們還提供了專案所需的第三方 Gradle 外掛所需的依賴:spring-boot-gradle-plugingradle-dockerdependency-management-plugin,這樣在後面,各子專案可以簡單的使用諸如 apply plugin: 'io.spring.dependency-management'apply plugin: 'docker' 等即可。

buildscript {
    ext {
        springBootVersion = '1.5.4.RELEASE'
        springCtxSupportVersion = '4.2.0.RELEASE'
        lombokVersion = '1.16.16'
        jjwtVersion = '0.7.0'
        jasperVersion = '6.4.0'
        poiVersion = '3.16'
        itextVersion = '2.1.7'
        olap4jVersion = '1.2.0'
        gradleDockerVersion = '1.2'
        gradleDMVersion = '1.0.3.RELEASE'
    }
    repositories {
        jcenter()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("se.transmode.gradle:gradle-docker:${gradleDockerVersion}")
        classpath("io.spring.gradle:dependency-management-plugin:${gradleDMVersion}")
    }
}複製程式碼

allprojects 中可以宣告對於所有專案(含根專案)都適用的配置,比如依賴性的倉儲等。而 subprojectsallprojects 的區別在於 subprojecrts 只應用到子專案,而非根專案。所以大部分通用型配置可以通過 subprojectsallprojects 來完成。下面列出的樣例配置中,我們為所有的專案包括根專案配置了依賴倉儲以及軟體的 group,同時為每個子專案配置了 javaidea 兩個外掛、版本號和通用的測試依賴。

allprojects {
    group = 'spring-tut'
    repositories() {
        jcenter()
    }
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'idea'
    version = "0.0.1"
    dependencies {
        testCompile("org.springframework.boot:spring-boot-starter-test")
    }
}複製程式碼

除此之外呢,為了展示一下 project 的用法, 我們這個例子裡把每個子專案的依賴放到根 build.gradle 中的 project(':子專案名') 中列出,這樣做有好處也有缺點,好處是依賴性的管理統一在根 build.gradle 完成,對於依賴的情況一目瞭然。當然缺點是每個專案更改依賴時都會造成根 gradle 的更新,這樣的話如果一個專案有非常多的子專案時,會在協作上出現一些問題。所以請根據具體情況決定把依賴放到根 build.gradle 中的 project(':子專案名') 中還是放到各子專案的 build.gradle 中。

project(':common') {
    dependencies {
        compile("org.springframework.boot:spring-boot-starter-data-rest")
        compile("org.springframework.boot:spring-boot-starter-data-mongodb")
        compile("org.projectlombok:lombok:${lombokVersion}")
    }
}

project(':api') {
    dependencies {
        compile project(':common')
        compile("org.springframework.boot:spring-boot-devtools")
        compile("org.springframework.boot:spring-boot-starter-security")
        compile("io.jsonwebtoken:jjwt:${jjwtVersion}")
        compile("org.projectlombok:lombok:${lombokVersion}")
    }
}

project(':report') {
    dependencies {
        compile project(':common')
        compile("org.springframework.boot:spring-boot-devtools")
        // the following 5 are required by jasperreport rendering
        compile files(["lib/simsun.jar"])
        compile("org.springframework.boot:spring-boot-starter-web")
        compile("org.springframework:spring-context-support:${springCtxSupportVersion}")
        compile("net.sf.jasperreports:jasperreports:${jasperVersion}")
        compile("com.lowagie:itext:${itextVersion}")
        compile("org.apache.poi:poi:${poiVersion}")
        compile("org.olap4j:olap4j:${olap4jVersion}")
    }
}複製程式碼

Spring Boot 中如何構建類庫工程

Spring Boot 的一大優點就是把應用做成了一個 Fat Jar,這種方式在部署時有極大的優勢。但如何在 Spring Boot 的多專案構建中建立一個類庫工程,而不是應用工程呢?當然前提是我們還能繼續享受 Spring Boot 帶來的配置便利性。

首先,我們需要在根工程的 build.gradle 中新增一個 Spring Boot 依賴管理的外掛:

buildscript {
    ext {
        springBootVersion = '1.5.4.RELEASE'
        gradleDMVersion = '1.0.3.RELEASE'
    }
    repositories {
        jcenter()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("io.spring.gradle:dependency-management-plugin:${gradleDMVersion}")
    }
}複製程式碼

然後在類庫子專案中的 build.gradle 中新增下面這句即可。

dependencyManagement {
    imports { mavenBom("org.springframework.boot:spring-boot-dependencies:${springBootVersion}") }
}複製程式碼

相關文章