Java微服務開發指南 -- 使用Spring Boot構建微服務

weixin_33766168發表於2017-11-03

使用Spring Boot構建微服務

    Spring Boot是一個廣泛用來構建Java微服務的框架,它基於Spring依賴注入框架來進行工作。Spring Boot允許開發人員使用更少的配置來構建微服務,同時框架本身能夠儘可能的減少開發人員的衝突,它和我們後面要介紹的兩個框架類似,它通過以下幾個方面幫助開發人員:

  • 自動化配置,一般情況下都有預設配置
  • 提供一組流行的starter依賴,方便開發人員使用
  • 簡化應用打包
  • 提升應用執行時的內省性(例如:Metrics與環境資訊)

簡化的配置

    Spring以噩夢般的配置而聞名,雖然框架本身相比其他元件模型(EJB 1.x 和 2.x)簡單了不少,但是它也帶來了自己的配置模式。也就是說,如果想要正確的使用Spring,你需要深入瞭解如何進行XML配置、瞭解JdbcTemplateJmsTemplate以及BeanFactory生命週期、瞭解Servlet監聽器,你以為掌握了這些就可以開始開發了嗎?實際上問題遠沒有結束,如果你要用Spring MVC編寫一個簡單的hello world,你還需要了解DispatcherServlet和一堆Model-View-Controller相關的型別。

    Spring Boot目標就是消除掉這些與業務無關的配置和概念,通過簡單的註解,你就能夠完成這些工作,當然如果你想繼續想以前一樣使用Spring,它也不會攔著你。

Starter依賴

    Spring廣泛使用著,包括了大型企業應用,在應用中,使用者將會使用到不同的技術元件,包括:JDBC資料來源、訊息佇列、檔案系統以及應用快取等。開發人員需要在需要這些功能時,停下來,仔細分析一下自己究竟需要什麼?需要的內容屬於哪個依賴(“哦,我需要JPA依賴”),然後花費大量的時間在依賴組織和排除上。

    Spring Boot提供了功能域(一批jar包依賴)的依賴,它讓開發人員宣告需要的功能,而不用去關係究竟如何處理依賴關係。這些starter可以允許開發人員直接使用這些功能:

  • JPA持久化
  • NoSQL資料庫支援,例如:MongoDB、Cassandra或者CouchBase
  • Redis快取
  • Tomcat、Jetty或者Undertow的Servlet引擎
  • JTA事務

    通過直接新增一個starter,能夠讓開發人員獲得這個特性相關的一組依賴,而這些依賴的組合已經被驗證,省卻了開發人員的不少時間。

應用打包

    Spring Boot是一組jar包和符合其約定的配置的構建塊,因此它不會執行在現有的應用伺服器中,而使用Spring Boot的大多數開發人員更喜歡的是直接執行的這種自包含的jar包。這意味著Spring Boot將所有的依賴和應用程式程式碼都包裝到一個自包含的jar中,而這些jar包執行在一個平面的類載入器中。簡單的類載入體系使得開發人員更容易理解應用程式的啟動、依賴關係和日誌輸出,但更重要的是,它有助於減少應用從構建到生產環境的步驟數量。這意味著開發人員不必將打包好的應用放置到應用伺服器中,而是直接執行這個standalone的應用,如果你需要servlet,那麼完全可以將其打包在應用內,使其為你服務。

    沒錯,一個簡單的java -jar <name.jar>就可以啟動你的應用了!Spring Boot、Dropwizard和WildFly Swarm都遵循將所有內容打包成可執行的jar模式。但是傳統的應用伺服器包含的管理能力,怎麼在這種模式下實現呢?

為生產環境而準備

    Spring Boot推出了一個叫做actuator的模組,它可以實現應用的指標統計。例如:我們可以收集日誌、檢視指標、生成執行執行緒dump、顯示環境變數、瞭解gc以及顯示BeanFactory中配置的bean。可以通過HTTP或者JMX暴露這些資訊或者進行日誌輸出。藉助Spring Boot,我們可以利用Spring框架的功能、減少配置並快速開發應用並上線。

    說了這麼多,讓我們看看怎麼使用它。

開始使用

我們接下來使用Spring Boot的命令列工具(CLI)來建立第一個Spring Boot程式(CLI底層使用了Spring Initializer)。你也可以使用自己喜歡的方式,比如使用整合了Spring Initializer的IDE,或者直接訪問web來建立一個工程。

Spring Boot CLI 的安裝方式,可以參考 這裡

Homebrew下:
brew tap pivotal/tap
brew install springboot

    一旦你安裝了Spring Boot CLI,你可以這樣檢查一下。

$ spring --version
Spring CLI v1.5.4.RELEASE

    如果你能看到版本的輸出,恭喜你,安裝成功了。接下來,在你希望建立工程的目錄下執行命令:spring init --build maven --groupId com.murdock.examples --version 1.0 --java-version 1.8 --dependencies web --name hola-springboot hola-springboot

在microservices-camp下執行。

    執行該命令後,將會在當前目錄下建立一個hola-springboot目錄,同時該目錄下包含了一個完整的Spring Boot程式,簡單的介紹一下這個命令中包含的內容。

  • --build
    使用的構建工具,示例中是:maven
  • --groupId
    maven座標中的組Id,也就是程式碼的包名,如果你想改包名,只有在IDE中修改
  • --version
    maven座標中的version
  • --java-version
    Java版本
  • --dependencies
    這是一個有趣的引數,我們可以指定某種開發型別的依賴。比如:web就是指當前專案使用Spring MVC框架,預設基於內嵌的Tomcat(Jetty和Undertow作為可選)。其他的依賴或者starter,比如:jpasecuritycassandra

    進入到hola-springboot目錄中, 執行命令:mvn spring-boot:run,如果程式啟動,沒有報錯,你就能看到如下的日誌:

2017-06-18 10:46:51.070  INFO 3397 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-06-18 10:46:51.081  INFO 3397 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2017-06-18 10:46:51.253  INFO 3397 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-06-18 10:46:51.262  INFO 3397 --- [           main] c.m.e.h.HolaSpringbootApplication        : Started HolaSpringbootApplication in 13.988 seconds (JVM running for 17.985)

    恭喜你!你快速的建立了一個Spring Boot應用,並且啟動了它,你甚至可以訪問http://localhost:8080,你會看到如下內容


chapter2-1.png

    可以看到返回了預設的出錯頁面,到目前為止,它除了這個什麼也做不了。接下來,我們就新增一些特性,比如:REST訪問,做一個helloworld式的應用。

後續實踐內容與原文有不同,在操作性上要比原文具備更好的實踐性。

你好,世界

    現在我們擁有了一個可以執行的Spring Boot應用,讓我們為它新增一些簡單的功能。首先,我們想做的是,讓應用暴露一個位置是api/holaV1HTTP/REST端點,訪問它將返回 Hola Spring Boot @ X,而其中的 X 是執行應用的本機IP。

    在編寫程式碼前,先將hola-springboot匯入到IDE中,在com.murdock.examples.holaspringboot包下面建立一個類,名稱為HolaRestControllerV1

public class HolaRestControllerV1 {

    public String hola() throws UnknownHostException {
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                    .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return "Hola Spring Boot @ " + hostname;
    }
}

    可以看到方法hola()返回了我們需要的內容,一個簡單的字串。

新增HTTP端點

    到現在為止,我們只是建立了一個名為HolaRestControllerV1POJO,你可以寫一些單元測試去做驗證,而讓它暴露HTTP端點,則需要增加一些內容。

@RestController
@RequestMapping("/api")
public class HolaRestControllerV1 {

    @RequestMapping(method = RequestMethod.GET, value = "/holaV1", produces = "text/plain")
    public String hola() throws UnknownHostException {
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                    .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return "Hola Spring Boot @ " + hostname;
    }
}
  • @RestController
    這個註解告知Spring,該類是一個用於暴露HTTP端點的控制器(可以暴露GET、PUT和POST等基於HTTP協議的功能)
  • @RequestMapping
    用於對映HTTP URI到對應的類或者方法

    通過新增這兩個註解,我們就可以使得原有的類具備了暴露HTTP端點的能力。針對上面的程式碼,比如@RequestMapping("/api")代表著HolaRestControllerV1接受來自/api路徑的請求,當新增@RequestMapping(method = RequestMethod.GET, value = "/holaV1", produces = "text/plain")時,表示告知Spring在/holaV1(其實是/api/holaV1)暴露HTTP GET端點,該端點接受的型別是text/plain。Spring Boot將會使用內建的Tomcat執行,當然你也可以切換到Jetty或者Undertow

    我們在hola-springboot目錄下,執行mvn clean package spring-boot:run,然後使用瀏覽器訪問http://localhost:8080/api/holaV1,如果一切正常,我們可以看到如下內容。


chapter2-2.png

    現在這些返回內容是寫死的,如果我們想個應用增加一些環境相關的配置,如何做呢?比如:可以替代 Hola 這個詞,比如使用 Guten Tag 德語,我們把這個應用部署給德國人用,我們需要一個將外部屬性注入給應用的途徑。

外部配置

    Spring Boot可以很容易使用諸如:properties檔案、命令列引數和系統環境變數等作為外部的配置來源。我們甚至可以將這些配置屬性通過Spring Context繫結到型別例項上,例如:如果想將helloapp.*屬性繫結到HolaRestController,可以在型別上宣告@ConfigurationProperties(prefix="helloapp"),Spring Boot會自動嘗試將比如helloapp.foo或者helloapp.bar等這些屬性值繫結到型別例項的foobar等欄位上。

    在Spring Initializer CLI建立的工程中,已經有了一個application.properties,我們就可以在這個檔案中定義新屬性,比如:helloapp.saying

$ more src/main/resources/application.properties
helloapp.saying=Guten Tag aus

    建立一個新的控制器HolaRestControllerV2

@RestController
@RequestMapping("/api")
@ConfigurationProperties(prefix = "helloapp")
public class HolaRestControllerV2 {

    private String saying;

    @RequestMapping(method = RequestMethod.GET, value = "/holaV2", produces = "text/plain")
    public String hola() throws UnknownHostException {
        String hostname = null;
        try {
            hostname = InetAddress.getLocalHost()
                    .getHostAddress();
        } catch (UnknownHostException e) {
            hostname = "unknown";
        }
        return saying + " @ " + hostname;
    }

    public String getSaying() {
        return saying;
    }

    public void setSaying(String saying) {
        this.saying = saying;
    }
}

    停止之前執行的應用,然後在hola-springboot目錄下,繼續使用mvn clean package spring-boot:run來編譯工程,執行這個應用,然後使用瀏覽器訪問http://localhost:8080/api/holaV2,你會看到如下內容。


chapter2-3.png

    我們現在通過更改外部配置檔案來使應用適應部署的環境,比如:呼叫服務的url、資料庫url和密碼以及訊息佇列配置,這些都適合作為配置。但是也要把握度,不是所有的內容都適合放置在配置中,比如:應用在任何環境下都應該具備相同的超時、執行緒池、重試等配置。

暴露應用的Metrics和資訊

    一個Spring Boot應用搭建起來了,緊接著就是將其部署到生產環境,我們怎樣監控它呢?當我們想知道它執行的怎麼樣,我們該怎麼辦呢?除非我們讓應用向外暴露出Metrics,否則應用就會像黑盒子一樣。Spring Boot專門提供了一個starter -- actuator來完成這個工作。

    讓我們看看如何啟用actuator,啟用的過程非常簡單。在hola-springboot/pom.xml中依賴:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

    然後在hola-springboot/src/main/resources/application.properties中增加一個配置(安全原因):

$ more src/main/resources/application.properties
management.security.enabled=false

    隨後,結束當前應用,在hola-springboot下執行:mvn clean package spring-boot:run重新編譯工程,啟動專案。

    我們可以通過瀏覽器訪問幾次http://localhost:8080/api/holaV1以及http://localhost:8080/api/holaV2,然後訪問一下:http://localhost:8080/metrics,可以看到如下內容。


chapter2-4.png

    類似這樣的URL還有許多:

    暴露出這些執行時資訊,使得開發人員在忙於業務開發的同時,更輕鬆獲取到系統資訊。

怎樣在maven之外執行

    到現在為止,我們還是以開發者視角使用maven來構建這個簡單的工程。如果我們需要將它部署到其他環境,比如:開發、測試或者生產環境,需要怎麼做呢?

    幸運的是,使用Spring Boot,我們可以輕鬆的釋出和構建,Spring Boot推薦單一、可執行的jar,而在這個jar中包括了所有的依賴,這些依賴的jar都會放置在應用的類路徑下。在hola-springboot下,執行mvn clean package,然後可以通過java -jar來執行。

$ mvn clean package
$ java -jar target/hola-springboot-1.0.jar

    就是這樣,我們可以啟動這個應用,後續接下來介紹的DropwizardWildFly Swarm都使用類似的方式進行。

呼叫其他服務

    在微服務環境下,每個服務都有提供功能的義務並服務好它的呼叫者。就像我們在第一章中談的,因為網路的不確定性,構建分散式系統十分的困難,本章主要討論一個服務怎樣呼叫到後臺的服務。

在第五章中,將會討論服務的柔性、適應性互動和呼叫

    接下來將擴充套件hola-springboot專案,完成服務的呼叫,但在此之前,我們先要搭建一個後臺服務,完成類似下圖的互動。


chapter2-5.png

後臺服務的構建,將採用forge + WildFly的方式進行,比原文中寫一個Servlet部署到Jetty顯得更好
關於forge的安裝,在mac下:brew install jboss-forge

    通過以下方式,可以在microservices-camp下建立一個具備持久化能力的REST服務,它可以自由的部署到WildFly中。

    使用forge構建完成之後,可以將其匯入到IDE中,如果觀察BookEndpoint這個型別,你會發現涉及到CRUD以及分頁查詢等邏輯已經完全具備了。


chapter2-11.png

    通過上述命令,我們可以構建出一個hola-backend.war的應用,下面我們將其部署到WildFly中。WildFly的使用可以通過下載到本地執行,但是由於涉及到兩個程式的互動,本文采用Docker的方式進行部署,讀者可以自行準備環境。

筆者準備了WildFly映象,可以簡單的執行起來
執行:sudo docker run --name wildfly -it -p 9990:9990 -p 8080:8080 weipeng2k/wildfly-admin,可以啟動一個WildFly,HTTP埠在8080,應用管理埠在9990
管理員賬號筆者已經構建在映象中:admin/Admin#hello1234

    登入到WildFly後臺,通過管理介面,部署hola-backend.war


chapter2-6.png

    可以看到後臺的更新日誌,從中可以瞭解到應用部署正常。


chapter2-7.png

    使用這種方式的好處在於開發階段如果有新的包生成直接進行上傳就好,如果想整體銷燬,直接停止刪除容器即可,不會弄壞WildFly。下面使用chrome外掛Postman構建Book資料,然後測試是否可用。

    新增資料測試。


chapter2-8.png

    查詢資料測試。


chapter2-9.png

    看來後臺服務應用hola-backend工作正常,當然可以通過WildFly的管理介面查詢執行時資訊,這點和Spring Boot的actuator很像,但是產品化的體驗做的更好些。

    接下來在hola-springboot專案中新建BookRestController,使用RestTemplate來完成後端服務的互動。

@RestController
@RequestMapping("/api")
@ConfigurationProperties(prefix = "books")
public class BookRestController {

    private RestTemplate template = new RestTemplate();

    private String backendHost;

    private int backendPort;

    @RequestMapping(value = "/books/{bookId}",
            method = RequestMethod.GET, produces = "text/plain")
    public String greeting(@PathVariable("bookId") Long bookId) {
        String backendServiceUrl = String.format("http://%s:%d/hola-backend/rest/books/{bookId}", backendHost, backendPort);
        Map object = template.getForObject(backendServiceUrl, Map.class, bookId);
        return object.toString();
    }

    public String getBackendHost() {
        return backendHost;
    }

    public void setBackendHost(String backendHost) {
        this.backendHost = backendHost;
    }

    public int getBackendPort() {
        return backendPort;
    }

    public void setBackendPort(int backendPort) {
        this.backendPort = backendPort;
    }
}

    可以看到BookRestController將後端的host與port放在了配置中,而字首是books,那麼也就需要在application.properties中增加這些配置。

$ more src/main/resources/application.properties
books.backendHost=192.168.0.125
books.backendPort=8080

    接下來,開啟瀏覽器訪問:http://localhost:8080/api/books/1,它將訪問hola-springboot,而hola-springboot將會呼叫hola-backend,最終由hola-springboot輸出結果。


chapter2-10.png

小結

    通過本章的內容,我們學習了Spring Boot的基本知識,瞭解它與傳統的WAREAR不同的部署方式,以及如何使用外部資源來完成配置,並通過actuator暴露了Metrics,使用RestTemplate呼叫了另一個服務。如果你想了解跟多內容,可以參考下面的連結。

相關文章