深刻學習微框架:Spring Boot

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

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

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

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

安裝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命令。github

要進行打包和分發的工程會依賴於像MavenGradle這樣的構建系統。爲了簡化依賴圖,Boot的功能是模塊化的,經過導入Boot所謂的「starter」模塊,能夠將許多的依賴添加到工程之中。爲了更容易地管理依賴版本和使用默認配置,框架提供了一個parent POM,工程能夠繼承它。Spring Boot工程的樣例POM文件定義如程序清單1所示。web

程序清單1spring

<?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所示。sql

程序清單1.1mongodb

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 {
  Long id
}

@MessageEndpoint
class UserLookupObject {

  @ServiceActivator(inputChannel="userLookupChannel")
  def get(Long id) {
    id ? new User(id:id) : new User()
  }
}

使用消息驅動的方式來進行微服務的開發能提供很大的代碼可重用性,而且可以與底層的服務提供者實現相解耦。在更爲正式的場景之中,程序清單1.24的代碼可能會負責組合數據,這些數據可能來自於數據庫調用和企業組織中某個外部的服務集成。Spring Integration具備內置的設施用來進行負載路由(payload routing)和處理器鏈(handler chaining),這對於組合不一樣的數據來講,是一個頗有吸引力的方案,咱們的微服務能夠做爲一個數據的提供者(provider)。

提供度量指標

微服務最重要的一個特性可能就是爲報表終端(reporting agent)提供度量指標。不像那些功能完備的Web應用,微服務是輕量級的,設計時可能就不會規劃提供報表界面或完備的接口來分析服務的活動。這種類型的操做最好是留給專門進行數據聚合和分析的應用,這些數據可以用來進行穩定性、性能以及商務智能的監控。基於這樣的前提,微服務應該爲這些工具提供端點,從而更加容易地獲取有關該服務活動的數據。而報表工具負責將數據聚合到一個視圖或報告中,對於關心數據的人這纔是有意義的。

微服務的一些指標如穩定性和性能,對全部的應用都是通用的,可是與業務操做相關的指標必須由應用自己來具體進行管理。針對這一點,Spring Boot的actuator模塊爲開發人員提供了一種機制,容許開發人員經過/metrics端點以編碼的方式暴露微服務狀態的細節。Boot將指標拆分爲「counter」和「gauge」兩種類別:counter是全部以Number類型來展示的指標,而gauge是衡量雙精度計算的指標。爲了讓微服務的開發人員更加容易地使用指標,Boot暴露了CounterServiceGaugeService,它們能夠自動織入到應用上下文之中。請參見程序清單1.25的樣例,它闡述瞭如何經過CounterService對外暴露點擊數。

程序清單1.25

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

  @Autowired
  UserRepository repository

  @Autowired
  CounterService counterService

  @RequestMapping(method = [GET])
  def get() {
    get(null)
  }

  @RequestMapping(value="/{id}", method = [GET])
  def get(@PathVariable Long id) {
    counterService.increment id ? "queries.by.id.$id" : "queries.without.id"
    id ? repository.findOne(id) : repository.findAll()
  }
}

在點擊/user端點時,有可能提供ID也有可能不提供ID,/metrics端點都會在counter.父節點下記錄新的key。例如,若是咱們只是查詢/user端點而不帶有ID的話,那麼就會註冊counter.queries.without.id指標。相似的,若是咱們帶有ID的話,那麼就會看到有一個counter.queries.by.id.<id>的key,它能用來標記對於給定的ID已經進行了多少次查詢。這些指標可能會有助於掌握最常常訪問的User對象並指導要採起的行爲,如緩存或數據庫索引。相似於遞增指標的數值,CounterService也容許將指標的值將爲零。這對於跟蹤打開的鏈接數或其餘頻率分佈(histographic)的測量都是頗有用處的。

gauge是稍微有所不一樣的一種類型指標,它會進行探索性的計算或基於請求來肯定值。如GaugeService的JavaDocs所述,「gauge」能夠測量任意的值,從方法執行的次數到會議室的溫度。當須要爲報表工具暴露細節時,這種類型的測量尤爲適合於使用GaugeService。gauge的指標會在/metrics端點之下進行訪問,而且帶有gauge.前綴。它們的註冊方式與counter有些差異,如程序清單1.26所示。

程序清單1.26

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

  @Autowired
  UserRepository repository

  @Autowired
  CounterService counterService

  @RequestMapping(method = [GET])
  def get() {
    get(null)
  }

  @RequestMapping(value="/{id}", method = [GET])
  def get(@PathVariable Long id) {
    def start = new Date().time
    def result = id ? repository.findOne(id) : repository.findAll()
    def time = new Date().time - start
    gaugeService.submit("user.get.db.time", time.doubleValue())
    result
  }
}

默認狀況下,指標會存儲在一個易失的內存數據庫之中,但Boot同時也爲應用上下文提供了MetricsRepository實現,它能支持更爲持久化的行爲。Boot自帶了一個RedisMetricsRepository,它可以自動織入進來,從而將指標存儲到Redis值存儲之中,另外,能夠編寫自定義的實現將指標存儲到任意的數據存儲形式之中。

Boot還提供了對Coda Hale Metrics庫的支持,它會將以特定名稱開頭的指標強制轉換爲對應的Metrics類型。例如,若是有一個指標是以histogram.開頭,那麼這個值將會做爲Histogram對象類型。這種自動化的強制轉換對於meter.timer.key也是有效的,而普通的指標將會做爲Gauge類型。

一旦微服務的指標在Boot中進行了註冊,那麼報表工具就能夠經過/metrics端點來檢索它們。已命名的指標能夠經過/metrics端點獲取,只需將指標的key名做爲查詢字符串的一部分便可。例如,若是只是訪問gauge指標下的「user.get.db.time」,報表工具能夠針對/metrics/gauge.user.get.db.time進行查詢。

打包Boot應用

正如前面所討論的,Boot提供了Maven和Gradle插件,它爲構建系統的打包階段提供了一種鉤子(hook),以產生所謂的「胖jar」,在這種jar中包含了工程的全部依賴。當這個胖jar包執行時,應用將會運行在與工程開發期相同的嵌入式容器之中。這種簡便的方式可以讓開發人員省去不少麻煩,由於他們的部署包在開發期和運行時環境之中具備相同的依賴結構。這也可以緩解運維團隊的焦慮,他們不用擔憂部署的場景,由於在部署時一個錯誤配置的運行時容器可能會帶有某個特定的依賴,而在項目的開發期所依賴的多是另一個。

爲了在Maven下執行打包,只需執行mvn package命令。Spring Boot的插件會備份工程所建立的原始jar而且在文件名上添加「.original」。在這裏,可以獲得可運行的jar,文件符合Maven artifact的命名約定,它能夠按照工程最合適的方式進行部署。使用Gradle構建Boot工程一樣很簡單,只需執行標準的gradle build命令便可。相似於Maven,Boot插件在原有的打包任務以後使用Gradle安裝了一個生命週期事件,而且會在build/libs目錄下建立胖jar包。對所生成的胖jar包進行檢查的一種方式就是全部依賴的jar都會位於歸檔文件的lib/目錄下。

打包完成以後,胖jar包就可以像其餘可運行的jar文件那樣在命令行中執行了,也就是使用$JAVA_HOME/bin/java -jar path/to/myproject.jar命令。啓動後,Boot應用的日誌將會顯示在控制檯上。

對於須要部署到傳統servlet容器之中的應用,Boot提供了一種方式以編碼的方式初始化Web配置。爲了使用這一點,Boot提供了可選的WebApplicationInitializer,它會使用servlet容器來註冊應用,這會經過Servlet 3.0 API以編碼的方式註冊servlet而且會用到ServletContext。經過提供SpringBootServletInitializer的子類,Boot應用可以使用嵌入的Spring上下文來註冊配置,這個Spring上下文是在容器初始化的時候建立的。爲了闡述這個功能,考慮程序清單1.27中的示例代碼。

程序清單1.27

@RestController
@EnableAutoConfiguration
class Application extends SpringBootServletInitializer {

  @RequestMapping(method = RequestMethod.GET)
  String get() {
    "home"
  }

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

  @Override
  SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    application.sources Application
  }
}

Application類中被重寫的configure方法就是使用嵌入式的Spring上下文註冊應用的地方。在更爲正式的場景之中,這個方法可能會用來註冊Spring Java配置類,它會定義應用中全部controller和服務的bean。

當將應用打包部署到servlet容器之中時,工程要構建爲一個war文件。在Maven工程中,爲了適應這一點,須要移除Boot插件,而且packaging須要明肯定義爲「war」類型,如程序清單1.28所示。

程序清單1.28

<?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>
    <packaging>war</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.0.0.RC1</version>
    </parent>

    <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>
</project>

對這個工程執行mvn install命令會在target目錄下生成myproject-1.0.0-SNAPSHOT.war文件。使用Gradle構建的工程可使用Gradle War Plugin,它爲構建war文件暴露了一個war任務。相似於Maven的配置,Boot Gradle工程也須要移除所包含的Boot插件。產生war文件的示例Gradle構建腳本如程序清單1.29所示。

程序清單1.29

apply plugin: 'java'
apply plugin: 'war'

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

ext {
  springBootVersion = '1.0.0.BUILD-SNAPSHOT'
}

dependencies {
    compile "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
    compile "org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}"
}

對於Boot工程,使用這個構建腳本運行Gradle的war任務將會在build/libs目錄下產生war文件。

無論是Maven仍是Gradle的配置,一旦war文件產生,它就能夠部署到任意兼容Servlet 3.0的應用服務器之中。部分兼容的容器包括Tomcat 7+、Jetty 八、Glassfish 3.x、JBoss AS 6.x/7.x以及Websphere 8.0。

延伸閱讀

Spring Boot團隊已經編寫了完整的指導和樣例來闡述框架的功能。Blog文章、參考資料以及API文檔均可以在Spring.IO網站上找到。項目的GitHub頁面上能夠找到示例的工程,更爲具體的細節能夠閱讀Spring Boot的參考手冊。SpringSourceDev YouTube頻道有一個關於Spring Boot的webinar,它概述了這個項目的目標和功能。在去年在倫敦舉行的Groovy & Grails Exchange上,David Dawson作了一個使用Spring Boot開發微服務的演講

關於做者

Daniel Woods是Netflix的高級軟件工程師,負責開發持續交付和雲部署工具。他擅長JVM棧相關的技術,活躍在Groovy、Grails和Spring社區。能夠經過電子郵件地址danielpwoods@gmail.com或Twitter @danveloper聯繫到Daniel。

 

關注IT趨勢,承載前沿、深刻、有溫度的內容。感興趣的讀者能夠搜索ID:laocuixiabian,或者掃描下方二維碼加關注。

相關文章
相關標籤/搜索