Java微服務開發指南 -- 使用Spring Boot構建微服務
使用Spring Boot構建微服務
Spring Boot是一個廣泛用來構建Java微服務的框架,它基於Spring依賴注入框架來進行工作。Spring Boot允許開發人員使用更少的配置來構建微服務,同時框架本身能夠儘可能的減少開發人員的衝突,它和我們後面要介紹的兩個框架類似,它通過以下幾個方面幫助開發人員:
- 自動化配置,一般情況下都有預設配置
- 提供一組流行的starter依賴,方便開發人員使用
- 簡化應用打包
- 提升應用執行時的內省性(例如:Metrics與環境資訊)
簡化的配置
Spring以噩夢般的配置而聞名,雖然框架本身相比其他元件模型(EJB 1.x 和 2.x)簡單了不少,但是它也帶來了自己的配置模式。也就是說,如果想要正確的使用Spring,你需要深入瞭解如何進行XML配置、瞭解JdbcTemplate
、JmsTemplate
以及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,比如:jpa
、security
和cassandra
進入到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
,你會看到如下內容
可以看到返回了預設的出錯頁面,到目前為止,它除了這個什麼也做不了。接下來,我們就新增一些特性,比如:REST訪問,做一個helloworld式的應用。
後續實踐內容與原文有不同,在操作性上要比原文具備更好的實踐性。
你好,世界
現在我們擁有了一個可以執行的Spring Boot應用,讓我們為它新增一些簡單的功能。首先,我們想做的是,讓應用暴露一個位置是api/holaV1
HTTP/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端點
到現在為止,我們只是建立了一個名為HolaRestControllerV1
的POJO
,你可以寫一些單元測試去做驗證,而讓它暴露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
,如果一切正常,我們可以看到如下內容。
現在這些返回內容是寫死的,如果我們想個應用增加一些環境相關的配置,如何做呢?比如:可以替代 Hola 這個詞,比如使用 Guten Tag 德語,我們把這個應用部署給德國人用,我們需要一個將外部屬性注入給應用的途徑。
外部配置
Spring Boot可以很容易使用諸如:properties檔案、命令列引數和系統環境變數等作為外部的配置來源。我們甚至可以將這些配置屬性通過Spring Context繫結到型別例項上,例如:如果想將helloapp.*
屬性繫結到HolaRestController
,可以在型別上宣告@ConfigurationProperties(prefix="helloapp")
,Spring Boot會自動嘗試將比如helloapp.foo
或者helloapp.bar
等這些屬性值繫結到型別例項的foo
、bar
等欄位上。
在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
,你會看到如下內容。
我們現在通過更改外部配置檔案來使應用適應部署的環境,比如:呼叫服務的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
,可以看到如下內容。
類似這樣的URL還有許多:
- http://localhost:8080/beans
- http://localhost:8080/env
- http://localhost:8080/health
- http://localhost:8080/metrics
- http://localhost:8080/trace
- http://localhost:8080/mappings
暴露出這些執行時資訊,使得開發人員在忙於業務開發的同時,更輕鬆獲取到系統資訊。
怎樣在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
就是這樣,我們可以啟動這個應用,後續接下來介紹的Dropwizard
和WildFly Swarm
都使用類似的方式進行。
呼叫其他服務
在微服務環境下,每個服務都有提供功能的義務並服務好它的呼叫者。就像我們在第一章中談的,因為網路的不確定性,構建分散式系統十分的困難,本章主要討論一個服務怎樣呼叫到後臺的服務。
在第五章中,將會討論服務的柔性、適應性互動和呼叫
接下來將擴充套件hola-springboot
專案,完成服務的呼叫,但在此之前,我們先要搭建一個後臺服務,完成類似下圖的互動。
後臺服務的構建,將採用forge + WildFly的方式進行,比原文中寫一個Servlet部署到Jetty顯得更好
關於forge的安裝,在mac下:brew install jboss-forge
通過以下方式,可以在microservices-camp
下建立一個具備持久化能力的REST服務,它可以自由的部署到WildFly
中。
使用forge
構建完成之後,可以將其匯入到IDE中,如果觀察BookEndpoint
這個型別,你會發現涉及到CRUD
以及分頁查詢等邏輯已經完全具備了。
通過上述命令,我們可以構建出一個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
。
可以看到後臺的更新日誌,從中可以瞭解到應用部署正常。
使用這種方式的好處在於開發階段如果有新的包生成直接進行上傳就好,如果想整體銷燬,直接停止刪除容器即可,不會弄壞WildFly
。下面使用chrome外掛Postman
構建Book
資料,然後測試是否可用。
新增資料測試。
查詢資料測試。
看來後臺服務應用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
輸出結果。
小結
通過本章的內容,我們學習了Spring Boot的基本知識,瞭解它與傳統的WAR
和EAR
不同的部署方式,以及如何使用外部資源來完成配置,並通過actuator暴露了Metrics,使用RestTemplate
呼叫了另一個服務。如果你想了解跟多內容,可以參考下面的連結。
相關文章
- Java微服務開發指南–使用Docker和Kubernetes構建可伸縮的微服務Java微服務Docker
- spring微服務實戰(二):使用Spring Boot建立微服務微服務Spring Boot
- [譯]Spring構建微服務Spring微服務
- 構建Spring Boot應用的微服務服務動態路由Spring Boot微服務路由
- 使用Java和Spring WebFlux構建響應式微服務JavaSpringWebUX微服務
- Go微服務開發指南Go微服務
- 微服務架構:構建PHP微服務生態微服務架構PHP
- 構建Spring Boot應用的微服務服務監控與告警Spring Boot微服務
- 使用Spring Boot和GraalVM在Knative上構建微服務 - piotrSpring BootLVM微服務
- Spring boot 2.1.9 + Dubbo 2.7.3 + Nacos 1.1.4 構建微服務系統Spring Boot微服務
- 使用Spring Boot實現微服務架構的開源專案Spring Boot微服務架構
- 微服務開發攻略之淺析微服務架構微服務架構
- 快速構建高併發微服務微服務
- 使用Golang和MongoDB構建微服務GolangMongoDB微服務
- Spring Cloud構建微服務架構(Feign)SpringCloud微服務架構
- Spring Cloud構建微服務架構—配置中心SpringCloud微服務架構
- Spring Cloud構建微服務架構-服務閘道器SpringCloud微服務架構
- Spring Cloud構建微服務架構-Hystrix服務降級SpringCloud微服務架構
- Spring Cloud構建微服務架構—註冊與發現SpringCloud微服務架構
- 如何構建微服務架構微服務架構
- spring cloud微服務雲架構-用java使用 redlockSpringCloud微服務架構Java
- 構建微服務的三種重要模式 - DZone微服務微服務模式
- Spring Cloud構建微服務架構-spring cloud服務監控中心SpringCloud微服務架構
- Spring Cloud構建微服務架構服務消費RibbonSpringCloud微服務架構
- Spring Cloud構建微服務架構—服務消費(Feign)SpringCloud微服務架構
- Spring Cloud構建微服務架構服務消費-RibbonSpringCloud微服務架構
- Spring Cloud構建微服務架構—服務消費FeignSpringCloud微服務架構
- Spring Cloud構建微服務架構—服務消費RibbonSpringCloud微服務架構
- Spring Cloud構建微服務架構—服務消費基礎SpringCloud微服務架構
- Spring Cloud構建微服務架構服務容錯保護SpringCloud微服務架構
- 構建Spring Cloud微服務分散式雲架構SpringCloud微服務分散式架構
- Spring Cloud分散式微服務雲架構構建SpringCloud分散式微服務架構
- Spring Cloud構建微服務架構:訊息驅動的微服務(消費組)【Dalston版】SpringCloud微服務架構
- Spring Cloud Spring Boot mybatis分散式微服務雲架構CloudSpring BootMyBatis分散式微服務架構
- 微服務2:微服務全景架構微服務架構
- 使用 ASP.NET Core 3.1 的微服務開發指南ASP.NET微服務
- Spring Cloud 微服務開發系列整理SpringCloud微服務
- Spring Cloud構建微服務架構-Hystrix斷路器SpringCloud微服務架構