1. 程式人生 > >Spring Boot深入詳解

Spring Boot深入詳解


Spring Boot是由Pivotal團隊提供的全新框架,其設計目的是用來簡化新Spring應用的初始搭建以及開發過程。該框架使用了特定的方式來進行配置,從而使開發人員不再需要定義樣板化的配置。通過這種方式,Boot致力於在蓬勃發展的快速應用開發領域(rapid application development)成為領導者。

多年以來,Spring IO平臺飽受非議的一點就是大量的XML配置以及複雜的依賴管理。在去年的SpringOne 2GX會議上,Pivotal的CTO Adrian Colyer迴應了這些批評,並且特別提到該平臺將來的目標之一就是實現免XML配置的開發體驗。Boot所實現的功能超出了這個任務的描述,開發人員不僅不再需要編寫XML,而且在一些場景中甚至不需要編寫繁瑣的import語句。在對外公開的beta版本剛剛釋出之時,Boot描述瞭如何使用該框架在140個字元內實現可執行的web應用,從而獲得了極大的關注度,該樣例發表在

tweet上。

然而,Spring Boot並不是要成為Spring IO平臺裡面眾多“Foundation”層專案的替代者。Spring Boot的目標不在於為已解決的問題域提供新的解決方案,而是為平臺帶來另一種開發體驗,從而簡化對這些已有技術的使用。對於已經熟悉Spring生態系統的開發人員來說,Boot是一個很理想的選擇,不過對於採用Spring技術的新人來說,Boot提供一種更簡潔的方式來使用這些技術。

在追求開發體驗的提升方面,Spring Boot,甚至可以說整個Spring生態系統都使用到了Groovy程式語言。Boot所提供的眾多便捷功能,都是藉助於Groovy強大的MetaObject協議、可插拔的AST轉換過程以及內建的依賴解決方案引擎所實現的。在其核心的編譯模型之中,Boot使用Groovy來構建工程檔案,所以它可以使用通用的匯入和樣板方法(如類的main方法)對類所生成的位元組碼進行裝飾(decorate)。這樣使用Boot編寫的應用就能保持非常簡潔,卻依然可以提供眾多的功能。

安裝Boot

從最根本上來講,Spring Boot就是一些庫的集合,它能夠被任意專案的構建系統所使用。簡便起見,該框架也提供了命令列介面,它可以用來執行和測試Boot應用。框架的釋出版本,包括整合的CLI(命令列介面),可以在Spring倉庫中手動下載和安裝。一種更為簡便的方式是使用Groovy環境管理器(Groovy enVironment Manager,GVM),它會處理Boot版本的安裝和管理。Boot及其CLI可以通過GVM的命令列gvm install springboot進行安裝。在OS X上安裝Boot可以使用Homebrew包管理器。為了完成安裝,首先要使用brew tap pivotal/tap

切換到Pivotal倉庫中,然後執行brew install springboot命令。

要進行打包和分發的工程會依賴於像MavenGradle這樣的構建系統。為了簡化依賴圖,Boot的功能是模組化的,通過匯入Boot所謂的“starter”模組,可以將許多的依賴新增到工程之中。為了更容易地管理依賴版本和使用預設配置,框架提供了一個parent POM,工程可以繼承它。Spring Boot工程的樣例POM檔案定義如程式清單1所示。

程式清單1

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.0.0.RC1</version>
    </parent>

    <!-- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/libs-snapshot</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/libs-snapshot</url>
        </pluginRepository>
    </pluginRepositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

為了實現更為簡單的構建配置,開發人員可以使用Gradle構建系統中簡潔的Groovy DSL,如程式清單1.1所示。

程式清單1.1

buildscript {
  repositories {
    maven { url "http://repo.spring.io/libs-snapshot" }
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RC1")
  }
}

apply plugin: 'java'
apply plugin: 'spring-boot'

repositories {
  mavenCentral()
  maven { url "http://repo.spring.io/libs-snapshot"  }
}

dependencies {
  compile 'org.springframework.boot:spring-boot-starter-actuator:1.0.0.RC1'
}

為了快速地搭建和執行Boot工程,Pivotal提供了稱之為“Spring Initializr” 的web介面,用於下載預先定義好的Maven或Gradle構建配置。我們也可以使用Lazybones模板實現快速起步,在執行lazybones create spring-boot-actuator my-app命令後,它會為Boot應用建立必要的工程結構以及gradle構建檔案。

開發Spring Boot應用

Spring Boot在剛剛公開宣佈之後就將一個樣例釋出到了Twitter上,它目前成為了最流行的一個應用樣例。它的全部描述如程式清單1.2所示,一個非常簡單的Groovy檔案可以生成功能強大的以Spring為後端的web應用。

程式清單1.2

@RestController
class App {
  @RequestMapping("/")
  String home() {
    "hello"
  }
}

這個應用可以通過spring run App.groovy命令在Spring Boot CLI中執行。Boot會分析檔案並根據各種“編譯器自動配置(compiler auto-configuration)”標示符來確定其意圖是生成Web應用。然後,它會在一個嵌入式的Tomcat中啟動Spring應用上下文,並且使用預設的8080埠。開啟瀏覽器並導航到給定的URL,隨後將會載入一個頁面並展現簡單的文字響應:“hello”。提供預設應用上下文以及嵌入式容器的這些過程,能夠讓開發人員更加關注於開發應用以及業務邏輯,從而不用再關心繁瑣的樣板式配置。

Boot能夠自動確定類所需的功能,這一點使其成為了強大的快速應用開發工具。當應用在Boot CLI中執行時,它們在使用內部的Groovy編譯器進行構建,這個編譯器可以在位元組碼生成的時候以編碼的方式探查並修改類。通過這種方式,使用CLI的開發人員不僅可以省去定義預設配置,在一定程度上甚至可以不用定義特定的匯入語句,它們可以在編譯的過程中識別出來並自動進行新增。除此之外,當應用在CLI中執行時,Groovy內建的依賴管理器,“Grape”,將會解析編譯期和執行時的類路徑依賴,與Boot編譯器的自動配置機制類似。這種方式不僅使得框架更加對使用者友好,而且能夠讓不同版本的Spring Boot與特定版本的來自於Spring IO平臺的庫相匹配,這樣一來開發人員就不用關心如何管理複雜的依賴圖和版本結構了。另外,它還有助於快速原型的開發並生成概念原型的工程程式碼。

對於不是使用CLI構建的工程,Boot提供了許多的“starter”模組,它們定義了一組依賴,這些依賴能夠新增到構建系統之中,從而解析框架及其父平臺所需的特定類庫。例如,spring-boot-starter-actuator依賴會引入一組基本的Spring專案,從而實現應用的快速配置和即時可用。關於這種依賴,值得強調的一點就是當開發Web應用,尤其是RESTful Web服務的時候,如果包含了spring-boot-starter-web依賴,它就會為你提供啟動嵌入式Tomcat容器的自動化配置,並且提供對微服務應用有價值的端點資訊,如伺服器資訊、應用指標(metrics)以及環境詳情。除此之外,如果引入spring-boot-starter-security模組的話,actuator會自動配置Spring Security,從而為應用提供基本的認證以及其他高階的安全特性。它還會為應用結構引入一個內部的審計框架,這個框架可以用來生成報告或其他的用途,比如開發認證失敗的鎖定策略。

為了闡述在Java Maven工程中,如何快速地使Spring Web工程準備就緒,考慮一下程式清單1.3中的應用程式程式碼。

程式清單1.3

package com.infoq.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.*;

@RestController
@EnableAutoConfiguration
public class Application {

  @RequestMapping("/")
  public String home() {
    return "Hello";
  }

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

Application類上的@EnableAutoConfiguration註解會告知Boot要採用一種特定的方式來對應用進行配置。這種方法會將其他樣板式的配置均假設為框架預設的約定,因此能夠聚焦於如何儘快地使應用準備就緒以便執行起來。Application類是可執行的,因此,當我們以Java Application的方式執行這個類時,就能啟動該應用及其嵌入式的容器,這樣也能實現即時地開發。

為了釋出版本而構建工程時,Boot的Maven和Gradle外掛可以嵌入(hook)到這些構建系統的打包過程中,以生成可執行的“胖jar包(fat jar)”,這種jar包含了工程的所有依賴並且能夠以可執行jar的方式執行。使用Maven打包Boot應用只需執行mvn package命令,與之類似,使用Gradle時,執行gradle build命令將會在構建的目標地址下生成可執行的jar。

開發微服務

Boot對Spring應用的開發進行了簡化,提供了模組化方式匯入依賴的能力,強調了開發RESTful Web服務的功能並提供了生成可執行jar的能力,這一切都清晰地表明在開發可部署的微服務方面Boot框架是一個強大的工具。正如前面的例子所示,藉助於Boot,讓一個RESTful Web工程執行起來是一件很容易的事情;不過,為了瞭解Boot所有潛在的功能,我們會闡述在開發完整功能的微服務時,會遇到的所有繁瑣的事情。在企業級基礎設施領域,微服務是一種越來越流行的應用架構,因為它能夠實現快速開發、更小的程式碼庫、企業級整合以及模組化部署。有眾多的框架致力於該領域的開發,該章節將會討論使用Boot如何簡化這一過程。

資料訪問

我們可以基於各種目的來構建微服務,但有一點是肯定的,那就是大多數都需要讀取和寫入資料庫的能力。Spring Boot使資料庫整合變成了一項非常簡單的任務,因為它具有自動配置Spring Data以訪問資料庫的能力。只需在你的工程中將spring-boot-starter-data-jpa包含進來,Boot的自動配置引擎就能探測到你的工程需要資料訪問功能,並且會在Spring應用上下文中建立必要的Bean,這樣你就可以使用Repository和服務了。為了更具體地闡述這一點,請參見程式清單1.4中的Gradle構建檔案,它列出了一個基於Groovy的微服務web應用的構建結構,該應用使用了Spring Data對JPA的支援來實現資料訪問。

程式清單1.4

buildscript {
  repositories {
    maven { url "http://repo.spring.io/libs-snapshot" }
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RC1")
  }
}

apply plugin: 'groovy'
apply plugin: 'spring-boot'

repositories {
  mavenCentral()
  maven { url "http://repo.spring.io/libs-snapshot"  }
}

ext {
  springBootVersion = "1.0.0.RC1"
}

dependencies {
  compile 'org.codehaus.groovy:groovy-all:2.2.1'
  compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
  compile "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
  compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
}

在這個配置中,Boot的actuator模組提供了對hsqldb的依賴,這會搭建所有必要的依賴——包括模式的建立——因此Spring Data可以使用這個記憶體資料庫作為資料來源。這種簡便的方式能夠讓開發人員免於在開發期建立和管理複雜的XML配置,進而能夠快速地開發資料庫驅動的微服務。如果在classpath中有H2或Derby資料庫的話,這種自動化配置也會生效。Boot所提供的另一個便利之處就是能夠快速簡便地使用相關資料啟動應用的資料庫模式。這在開發期是非常有用的,此時資料庫可能是在記憶體中或者是不穩定的,開發人員需要保證的是在應用啟動的時候能夠訪問到這些特定的資料。為了闡述這一點,考慮一下程式清單1.5中的示例JPA實體,它代表了微服務所提供的“User”資料結構。

程式清單1.5

@Entity
class User {
  @Id
  @GeneratedValue
  Long id

  String username
  String firstName
  String lastName
  Date createdDate
  Date lastAccessed

  Boolean isActive = Boolean.TRUE
}

為了啟用代表User物件的通用資料,我們只需建立一個名為schema.sqldata.sql的檔案,並將其包含在classpath之中。這個檔案會在模式建立完成之後執行,所以基於程式清單1.5所給出的實體,我們可以使用SQL語句啟用一個使用者賬號,如程式清單1.6所示。

程式清單1.6

insert into user(username, first_name, last_name, created_date) values ('danveloper', 'Dan', 'Woods', now())

在啟動的時候,我們所提供的SQL程式碼會執行,這樣就能確保有一個測試賬號可以使用。微服務此時已經具有了資料訪問的起始點,程式清單1.7展現瞭如何按照Spring Data的開發模式建立Repository介面,該介面會作為User實體的資料訪問物件(Data Access Object)。

程式清單1.7

public interface UserRepository extends CrudRepository<User, Long> {
}

CrudRepository提供了一些通用的介面方法來建立、查詢、更新以及刪除物件和物件集合。應用所需的其他特定功能可以按照Spring Data的Repository開發約定進行定義。一旦UserRepository介面建立成功,Boot的spring-data-jpa層會在工程中探測到它,並將其新增到Spring應用上下文之中,這樣對於controller和sevice物件來說,它就成為可以進行自動注入的可選物件。這種自動化的配置只有在Boot應用要求按照這種方式初始化的時候才生效,這是通過存在@EnableAutoConfiguration註解來標識的。藉助程式清單1.8中所實現的controller,微服務現在就可以定義RESTful端點了,服務的使用者可以獲取到User的列表或單個User。

程式清單1.8

@RestController
@EnableAutoConfiguration
@RequestMapping("/user")
class UserController {

  @Autowired
  UserRepository repository

  @RequestMapping(method=[RequestMethod.GET])
  def get(Long id) {
    id ? repository.findOne(id) : repository.findAll()
  }

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

在啟動的時候,應用將會輸出日誌,表明Hibernate按照User實體的定義建立資料庫結構,在應用初始化的最後,Boot還會從schema.sql檔案中匯入資料。

在開發微服務應用時,需要特別注意的一點是使用了@RequestMapping註解。這不是Boot特定的註解。不過,因為Boot安裝了自己的端點以監控應用的效能、健康情況以及配置,所以需要確保應用的程式碼不要與這些內建的提供詳情的路徑解析相沖突。鑑於此,如果有從請求路徑中解析屬性的需求(在我們的場景中,也就是user的id屬性),那麼我們需要仔細考慮這個動態的屬性解析會對微服務的其他行為產生什麼影響。在本例中,只是簡單地將controller對映到/user端點而不是根上下文,就能允許Boot的端點也可以進行訪問。

微服務所提供的資料並不一定全部適合關係型結構,針對這一點Spring Boot也暴露了一些模組,從而讓開發人員可以使用Spring Data的MongoDB和Redis專案,不過依然採取特定的方式來進行配置。Spring Data用來定義資料訪問物件(Data Access Object)的高層框架,這樣快速切換JPA與非JPA資料來源會變得非常容易。參見程式清單1.9,它展現了一個重新定義的UserRepository介面,這個介面設計為使用MongoDB取代JPA。

程式清單1.9

public interface UserRepository extends MongoRepository<User, Long> {
}

MongoRepository介面也擴充套件了CrudRepository,因此微服務的Controller程式碼,也就是程式清單1.8所示並不需要修改。為了實現與MongoDB的整合,工程唯一要做的就是在應用的classpath中包含spring-boot-starter-data-mongodb。程式清單1.4所示的Gradle構建檔案需要稍微調整一下依賴的部分,如程式清單1.10所示。

程式清單1.10

dependencies {
  compile 'org.codehaus.groovy:groovy-all:2.2.1'
  compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
  compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
  compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
}

MongoDB依賴都置於classpath之中以後,Boot將會自動配置Spring Data連線到localhost上的資料庫,並且預設的資料庫名為test。在這個庫中,將會自動建立User集合(按照MongoDB的標準),微服務現在就能使用MongoDB作為後端了。對非JPA的資料儲存來說,初始化資料比其他的方式更為簡單,這主要是因為它不能針對MongoDB的文件儲存和Redis的鍵值儲存執行SQL檔案。鑑於Spring Data會使用這些儲存的持久化例項,這就意味著開發期建立的資料需要在重啟後保留。為了持久化資料,我們需要修改微服務的controller,這樣服務的使用者就能建立User例項了。我們也可以將微服務的UserController進行修改,使其符合通用的RESTful API結構,讓controller以不同的方式處理不同的HTTP方法。程式清單1.11展現了為controller新增建立新User例項的功能。

程式清單1.11

@RestController
@RequestMapping("/user")
@EnableAutoConfiguration
class UserController {

  @Autowired
  UserRepository repository

  @RequestMapping(method=[RequestMethod.GET])
  def get(Long id) {
    id ? repository.findOne(id) : repository.findAll()
  }

  @RequestMapping(method=[RequestMethod.POST])
  def create(@RequestBody User user) {
    repository.save user
    user
  }

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

當微服務的使用者往應用的端點上傳送一個HTTP POST請求時,Spring將會把請求體轉換為User例項。程式碼接下來會使用UserRepository將這個物件儲存到MongoDB集合之中。使用curl建立User例項的樣例如程式清單1.12所示。

程式清單1.12

curl -v -H "Content-Type: application/json" -d "{ \"username\": \"danveloper\", \"firstName\": \"Dan\", \"lastName\": \"Woods\", \"createdDate\": \"2014-02-02T00:00:00\" }" http://localhost:8080/user

按照Boot針對Mongo資料來源的特定配置,新的User例項預設會持久化到本地Mongo例項的test資料庫的user集合之中。如果我們開啟web瀏覽器並對微服務發起一個HTTP GET請求,我們就能看到所建立的user存在於返回的列表之中。

配置

我們可以很快地重寫Spring Boot的預設配置。預設情況下,應用的配置可以使用Java屬性檔案來進行定義,這個檔名為application.properties並且位於應用的classpath根目錄下。不過,一種更好的方式是使用 YAML配置,它提供了結構化以及巢狀的配置。在應用的執行時類路徑之中包含snakeyaml之後,你的工程就可以在application.yml檔案中直接定義配置了。為了詳述這一點,考慮程式清單1.13的示例YAML配置,這裡列出了應用的嵌入式HTTP伺服器(預設是Tomcat,也可選擇Jetty)的各種設定項。

程式清單1.13

# Server settings (ServerProperties)
server:
  port: 8080
  address: 127.0.0.1
  sessionTimeout: 30
  contextPath: /

  # Tomcat specifics
  tomcat:
    accessLogEnabled: false
    protocolHeader: x-forwarded-proto
    remoteIpHeader: x-forwarded-for
    basedir:
    backgroundProcessorDelay: 30 # secs

允許重寫Boot的自動化配置,這一點能夠使你的應用從原型轉化為真正的產品,Boot使用相同的application.yml檔案進行配置,這樣就會非常容易。自動化配置的指令被設計的儘可能簡短,所以當使用actuator構建微服務時,會安裝一個配置屬性的端點,也就是/configprops,當確定哪些指令需要重寫時可以進行參考。如果我們的微服務要使用持久化資料來源,如MySQL,那麼只需將MySQL的Java驅動新增到執行時classpath中,然後在application.yml中新增必要的配置指令即可,如程式清單1.14所示。

程式清單1.14

spring:
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/proddb
    username: root
    password

在一些場景下你可能需要更為靈活的配置,Boot允許你通過Java的系統屬性(System properties)重寫很多它的預設配置。例如,如果你的應用需要在部署到產品化環境中使用不同的資料庫使用者,那麼username配置指令可以通過標準的Java系統屬性傳入到應用之中,而這需要切換到命令列中執行-Dspring.datasource.username=user。關於這一點更為現實的場景是雲部署環境,如Cloud Foundry或Heroku,這些平臺需要應用啟動特定的HTTP埠,這一點通過作業系統的環境變數可以實現。Boot能夠從系統屬性繼承得到配置,這樣你的應用就可以在命令列中使用-Dserver.port=$PORT來得到HTTP埠。在開發微服務時,這是一種相當有用的特性,因為它可以讓微服務應用執行在各種環境配置之中。

外部化配置

微服務必須要支援的很重要的一點就是外部化配置。這種配置可以包含任何的內容,從佔位符資訊到資料庫配置等等,在初始規劃和構建應用原型時,這是必須要考慮的架構內容。在Spring IO平臺中,已經存在各種匯入配置的策略,但是應用能夠以多種方式使用配置所造成的後果往往是產生冗長的編碼性耦合。

Boot一個很棒的特性在於它能管理外部化的配置並將其轉換為物件結構,這個物件可以在整個應用上下文中使用。建立一個簡單老式的Java/Groovy物件(Plain Old Java/Groovy Object),並使用@ConfigurationProperties註解,那麼這個物件就能使用Boot配置結構中預先定義的name名下的配置項。更具體一點來講,考慮一下程式清單1.15中的POGO,它能夠得到application.key下的配置指令。

程式清單1.15

@ConfigurationProperties(name = "application")
class ApplicationProperties {
  String name
  String version
}

ApplicationProperties物件在Spring上下文中建立完成之後,Boot將會識別出它是一個配置物件,並且會按照執行時classpath之中application.propertiesapplication.yml檔案中的配置指令填充它的屬性。因此,如果我們在微服務的application.yml檔案中新增application內容區的話,如程式清單1.16所示,那麼我們就可以在應用的其他部分以程式設計的方式訪問這些配置指令。

程式清單1.16

application:
  name: sb-ms-custdepl
  version: 0.1-CUSTOMER

這些配置指令可以有各種用途,要訪問這些指令的唯一要求就是代表它們的POJO/POGO必須是Spring應用上下文的成員。Boot能夠將一個controller作為Spring Java配置物件,這樣就能很容易地管理配置bean與應用上下文的整合,如程式清單1.17所示。

程式清單1.17

@RestController
@Configuration
@RequestMapping("/appinfo")
@EnableAutoConfiguration
class AppInfoController {

  @Autowired
  ApplicationProperties applicationProperties

  @RequestMapping(method=[RequestMethod.GET])
  def get() {
    [
      name: applicationProperties.name,
      version: applicationProperties.version
    ]
  }

  @Bean
  ApplicationProperties applicationProperties() {
    new ApplicationProperties()
  }

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

程式清單1.17中的樣例程式碼可能有些牽強,不過,即便是在更為複雜的場景下,如何使用Boot來訪問應用特定配置的原則是相同的。配置類也支援巢狀式的物件圖,這樣來自於配置中的深層資料就能更便利地進行訪問,也有了更好的語義。例如,如果我們想要得到的配置指令是application.根下的那些metrics key,那麼可以在ApplicationProperties POGO中新增一個巢狀物件來表示這些值,如程式清單1.18所示。

程式清單1.18

@ConfigurationProperties(name = "application")
class ApplicationProperties {
  String name
  String version

  final Metrics metrics = new Metrics()

  static class Metrics {
    String dbExecutionTimeKey
  }
}

現在,我們的application.yml檔案可以如程式清單1.19所示,它在application.程式碼塊中包含了metrics配置。

程式清單1.19

application:
  name: sb-ms-custdepl
  version: 0.1-CUSTOMER
  metrics:
    dbExecutionTimeKey: user.get.db.time

當我們需要訪問application.metrics.dbExecutionTimeKey的值時,能夠以程式設計的方式通過ApplicationProperties物件來進行訪問。

為了在整個應用之中使用application.propertiesapplication.yml檔案中的這些配置指令,我們並不是必須要將其轉換為物件圖。Boot也為Spring應用上下文提供了PropertySourcesPlaceholderConfiguration,這樣的話,來自於application.propertiesapplication.yml檔案的指令或者來自於Java系統的重寫屬性都可以作為Spring屬性佔位符來使用。Spring的這種機制能夠讓你以一種特定的語法來為屬性定義佔位符值,如果Spring發現了佔位符配置的話,就會用這個配置來進行填充。作為示例,我們可以在controller中使用@Value註解來直接訪問application.metrics.dbExecutionTimeKey,如程式清單1.20所示。

程式清單1.20

@RestController
@RequestMapping("/user")
@EnableAutoConfiguration
class UserController {

  @Autowired
  UserRepository repository

  @Autowired
  GaugeService gaugeService

  @Value('${application.metrics.dbExecutionTimeKey}')
  String dbExecutionKey

  @RequestMapping(method=[RequestMethod.GET])
  def get(Long id) {
    def start = new Date().time
    def result = id ? repository.findOne(id) : repository.findAll()
    gaugeService.submit dbExecutionKey, new Date().time - start
    result
  }

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

關於應用指標的報告,後面會有更為詳細的介紹,但現在重要的一點在於,理解@Value註解如何與Spring屬性佔位符一起使用,使Boot能夠自動注入值,從而滿足這個微服務的特定配置需求。

安全

在微服務的開發中,對於完備安全場景的需求會持續增長。為了滿足這種需求,Boot引入了強大完整的Spring Security,並且提供了自動配置的功能,以快速簡便地啟用安全層。只需在應用的classpath中包含spring-boot-starter-security模組就能使Boot引入一些安全特性,如跨站指令碼防護(cross-site scripting protection)並且會新增頭資訊以防止點選劫持(click-jacking)。除此之外,新增一條簡單的配置指令就能啟用基本認證來保護你的應用,如程式清單1.21所示。

程式清單1.21

security:
  basic:
    enabled: true

Boot會為你提供一個預設的使用者賬號user和預設角色USER,並且會在應用啟動的時候在控制檯上輸出隨機生成的密碼。就像Boot的其他功能那樣,對於內建的user賬號,我們可以很容易地指定不同的使用者名稱和密碼(分別為“secured”和“foo”),這需要通過明確定義的配置指令來實現,如程式清單1.22所示。

程式清單1.22

security:
  basic:
    enabled: true
  user:
    name: secured
    password: foo

對於簡單的內部應用或開發原型來說,Boot內建的基礎設施能夠快速地在微服務中啟用基本認證,這是非常有用的。隨著需求的演化,你的應用毫無疑問會需要更細粒度的安全特性,如保護端點只能由特定的角色訪問。從這個角度來看,我們可能希望具有USER角色的呼叫者只能讀取資料(即GET請求),而對具有ADMIN角色的呼叫者可以讀取和寫入資料(即POST請求)。為了做到這一點,我們需要在工程的application.yml檔案中禁用Boot的基本認證自動配置功能,並且定義我們自己的useradmin賬號以及對應的角色。當你的需求超過Boot所提供的預設功能時,它通常很快就能實現,這可以作為佐證這一點的又一個例子。為了更具體地闡述這一點,考慮一下程式清單1.23中的程式碼。這個樣例可以闡述如何發揮Spring Security所有潛在的功能以及更為複雜的認證策略,如基於JDBC後端、OpenID或單點登入(Single-Sign On)。

程式清單1.23

@RestController
@RequestMapping("/user")
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableAutoConfiguration
class UserController extends WebSecurityConfigurerAdapter {

  @Autowired
  UserRepository repository

  @RequestMapping(method = [GET])
  @Secured(['ROLE_USER'])
  def get(Long id) {
    id ? repository.findOne(id) : repository.findAll()
  }

  @RequestMapping(method = [POST])
  @Secured(['ROLE_ADMIN'])
  def create(@RequestBody User user) {
    repository.save user
    user
  }

  @Override
  void configure(AuthenticationManagerBuilder auth) {
    auth
    .inMemoryAuthentication()
    .withUser "user" password "password" roles "USER" and() withUser "admin" password "password" roles "USER", "ADMIN"
  }

  @Override
  void configure(HttpSecurity http) throws Exception {
    BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint()
    entryPoint.realmName = "Spring Boot"
    http.exceptionHandling().authenticationEntryPoint(entryPoint)
    http.requestMatchers().antMatchers("/**").anyRequest()
    .and().httpBasic().and().anonymous().disable().csrf().disable()
  }

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

在程式清單1.23的樣例之中,應用現在被明確地配置為要基於useradmin使用者賬號進行訪問,它們的密碼都是password,具有的角色分別是USERADMIN。微服務的GETPOST端點分別通過USERADMIN角色進行保護,這就意味著普通使用者可以訪問只讀的資料,而執行讀取-寫入操作的話,需要admin使用者憑證。

對於微服務來說,基本認證是很好的一個選擇,因為它遵循了很實用且廣泛使用的認證協議。換句話說,很多的API呼叫者,包括移動應用,能夠很容易地使用這一點來訪問你的微服務。當你的認證需求超過了基本認證的功能時(如OpenID或OAuth),微服務可以使用Spring Security的全部功能來滿足你的需求。

訊息整合

在任何的應用中,訊息(messaging)都是一種很強大的工具,在一點上,微服務當然也不能例外。使用訊息驅動架構開發的應用能夠更好地支援可重用性和擴充套件性。Spring Boot能夠讓開發人員在編寫微服務時將訊息作為架構的核心組成部分,它使用到了Spring IO平臺的企業整合模式(Enterprise Integration Patterns)實現,即Spring Integration。Spring Integration提供了開發訊息驅動架構的基本結構,以及與分散式企業平臺整合的模組。這種能力使得微服務可以使用來自抽象訊息源的業務物件,這些訊息源可以在應用內部,也可能是組織機構內部的其他服務所提供的。

儘管Boot並沒有提供明確的Spring上下文自動化配置,但是它為Spring Integration提供了一個starter模組,它會負責引入Spring Integration專案的一系列依賴。這些依賴包括Spring Integration的核心庫(Core library)、HTTP模組(用來進行面向HTTP的企業整合)、IP模組(用來進行基於Socket的整合操作)、File模組(用於進行檔案系統整合)以及Stream模組(用於支援使用Stream的操作,如stdin和stdout)。這個starter模組為開發人員提供了健壯的訊息功能的工具集,可以使已有的基礎設施適應微服務API。

除了starter模組,Boot也為通過CLI構建的應用提供了編譯器自動配置的功能。對於需要快速構建微服務原型並驗證可行性的開發者來說,這種方式提供了一些捷徑。使用企業級平臺的應用可以快速地進行開發,在轉移到正式的工程和構建系統之前,就能確認其價值。使用Spring Boot和Spring Integration使一個訊息驅動的微服務執行起來非常簡單,如程式清單1.24的樣例程式碼所示。

程式清單1.24

@RestController
@EnableIntegrationPatterns
class App {

  @Bean
  def userLookupChannel() {
    new DirectChannel()
  }

  @Bean
  def userTemplate() {
    new MessagingTemplate(userLookupChannel())
  }

  @RequestMapping(method=[RequestMethod.GET])
  def get(@RequestParam(required=false) Long id) {
    userTemplate().convertSendAndReceive( id ? id : "")
  }
}

class User {