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 {
  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 8、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開發微服務的演講


轉:http://www.infoq.com/cn/articles/microframeworks1-spring-boot


發佈了90 篇原創文章 · 獲贊 19 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章