標籤(空格分隔): springbootcss
Spring Boot是由Pivotal團隊提供的全新框架,其設計目的是用來簡化新Spring應用的初始搭建以及開發過程。該框架使用了特定的方式來進行配置,從而使開發人員再也不須要定義樣板化的配置。用個人話來理解,就是spring boot其實不是什麼新的框架,它默認配置了不少框架的使用方式,就像maven整合了全部的jar包,spring boot整合了全部的框架(不知道這樣比喻是否合適)。html
其實就是簡單、快速、方便!平時若是咱們須要搭建一個spring web項目的時候須要怎麼作呢?前端
- 配置web.xml,加載spring和spring mvc
如今很是流行微服務,若是我這個項目僅僅只是須要發送一個郵件,若是個人項目僅僅是生產一個積分;我都須要這樣折騰一遍!vue
可是若是使用spring boot呢?java
很簡單,我僅僅只須要很是少的幾個配置就能夠迅速方便的搭建起來一套web項目或者是構建一個微服務!mysql
因此spring boot的優勢爲:react
本節主要目標完成Spring Boot基礎項目的構建,而且實現一個簡單的Http請求處理,經過這個例子對Spring Boot有一個初步的瞭解,並體驗其結構簡單、開發快速的特性。web
本教材採用Java 1.8.0_131
、Spring Boot 1.5.10
實現。spring
雖然JDK目前已經發布1.9版本,可是目前在互聯網公司中,使用JDK1.8版本的更多,而傳統軟件公司中不少還停留在JDK1.6,JDK1.7,甚至還有JDK1.5的。
spring boot也在2018年3月發佈了2.0的正式版本,可是對如今來講仍是太新。sql
經過SPRING INITIALIZR工具產生基礎項目
訪問:http://start.spring.io/
選擇構建工具Maven Project
、Spring Boot
版本1.5.10
以及一些工程基本信息,可參考下圖所示:
點擊Generate Project
下載項目壓縮包,解壓項目包,並用IDE以Maven項目導入,以IntelliJ IDEA
爲例:
菜單中選擇File
–>New
–>Project from Existing Sources...
選擇解壓後的項目文件夾,點擊OK
點擊Import project from external model
並選擇Maven
,點擊Next
到底爲止。
若你的環境有多個版本的JDK,注意到選擇Java SDK的時候請選擇Java 7`以上的版本
前面構建的spring boot項目的目錄結構以下:
經過上面步驟完成了基礎項目的建立,如上圖所示,Spring Boot的基礎結構共三個文件(具體路徑根據用戶生成項目時填寫的Group全部差別):
src/main/java
下的程序入口:SbDemoApplication.java
src/main/resources
下的配置文件:application.properties
src/test/
下的測試入口:SbDemoApplicationTests.java
生成的SbDemoApplication.java
和SbDemoApplicationTests.java
類均可以直接運行來啓動當前建立的項目,因爲目前該項目未配合任何數據訪問或Web模塊,程序會在加載完Spring以後結束運行。
當前的pom.xml
內容以下,僅引入了兩個模塊:
spring-boot-starter
:核心模塊,包括自動配置支持、日誌和YAML
spring-boot-starter-test
:測試模塊,包括JUnit、Hamcrest、Mockito
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
引入Web模塊,需添加spring-boot-starter-web
模塊:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
HelloWorld
服務建立package
命名爲com.dengcl.sb_demo.web
(根據實際狀況修改)
建立HelloController
類,內容以下
@RestController public class HelloController { @RequestMapping("/hello") public String sayHello() { return "Hello World"; } }
啓動主程序,執行src/main/java
下的程序入口:SbDemoApplication.java
,在控制檯出現以下圖內容:
打開瀏覽器訪問http://localhost:8080/hello
,能夠看到頁面輸出Hello World
打開的src/test/
下的測試入口SbDemoApplicationTests.java
類。下面編寫一個簡單的單元測試來模擬http請求,具體以下:
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class SbDemoApplicationTests { @Autowired private MockMvc mvc; @Test public void getHello() throws Exception { mvc.perform(get("/hello").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string(equalTo("Hello World!"))); } }
注意引入下面內容,讓status
、content
、equalTo
函數可用
import static org.hamcrest.Matchers.equalTo; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
至此已完成目標,經過Maven
構建了一個空白Spring Boot
項目,再經過引入web
模塊實現了一個簡單的請求處理。
前面提到使用SPRING INITIALIZR
頁面工具來建立spring boot
項目,接下來將介紹嵌入的IDEA中的Spring Initializr
工具,它同Web提供的建立功能同樣,能夠幫助咱們快速的構建出一個基礎的Spring Boot
工程
菜單欄中選擇File
=>New
=>Project
..,咱們能夠看到以下圖所示的建立功能窗口。其中Initial Service Url
指向的地址就是Spring官方提供的Spring Initializr
工具地址,因此這裏建立的工程實際上也是基於它的Web工具來實現的。
點擊Next
,等待片刻後,咱們能夠看到以下圖所示的工程信息窗口,在這裏咱們能夠編輯咱們想要建立的工程信息。其中,Type能夠改變咱們要構建的工程類型,好比:Maven
、Gradle
;Language能夠選擇:Java
、Groovy
、Kotlin
。
點擊Next
,進入選擇Spring Boot版本和依賴管理的窗口。在這裏值的咱們關注的是,它不只包含了Spring Boot Starter POMs中的各個依賴,還包含了Spring Cloud的各類依賴。
點擊Next
,進入最後關於工程物理存儲的一些細節。最後,點擊Finish
就能完成工程的構建了。
IDEA中的Spring Initializr雖然仍是基於官方Web實現,可是經過工具來進行調用並直接將結果構建到咱們的本地文件系統中,讓整個構建流程變得更加順暢,尚未體驗過此功能的Spring Boot/Cloud愛好者們不妨能夠嘗試一下這種不一樣的構建方式。
首先,回顧並詳細說明一下在前面使用的@Controller
、@RestController
、@RequestMapping
註解。能夠發現這些註解都和前面學習的Spring MVC
中一致,其實spring boot的web實現就是經過spring mvc來實現的。
@Controller
:修飾class,用來建立處理http請求的對象
@RestController
:Spring4以後加入的註解,原來在@Controller
中返回json
須要@ResponseBody
來配合,若是直接用@RestController
替代@Controller
就不須要再配置@ResponseBody
,默認返回json
格式。
@RequestMapping
:配置url
映射
下面咱們嘗試使用Spring MVC來實現一組對User對象操做的RESTful API,配合註釋詳細說明在Spring MVC中如何映射HTTP請求、如何傳參、如何編寫單元測試。
RESTful API具體設計以下:
User實體定義:
public class User { private Long id; private String name; private Integer age; // 省略setter和getter }
實現對User對象的操做接口UserController
@RestController @RequestMapping(value="/users") // 經過這裏配置使下面的映射都在/users下 public class UserController { // 建立線程安全的Map static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); @RequestMapping(value="/", method= RequestMethod.GET) public List<User> getUserList() { // 處理"/users/"的GET請求,用來獲取用戶列表 // 還能夠經過@RequestParam從頁面中傳遞參數來進行查詢條件或者翻頁信息的傳遞 List<User> r = new ArrayList<User>(users.values()); return r; } @RequestMapping(value="/", method=RequestMethod.POST) public String postUser(@ModelAttribute User user) { // 處理"/users/"的POST請求,用來建立User // 除了@ModelAttribute綁定參數以外,還能夠經過@RequestParam從頁面中傳遞參數 users.put(user.getId(), user); return "success"; } @RequestMapping(value="/{id}", method=RequestMethod.GET) public User getUser(@PathVariable Long id) { // 處理"/users/{id}"的GET請求,用來獲取url中id值的User信息 // url中的id可經過@PathVariable綁定到函數的參數中 return users.get(id); } @RequestMapping(value="/{id}", method=RequestMethod.PUT) public String putUser(@PathVariable Long id, @ModelAttribute User user) { // 處理"/users/{id}"的PUT請求,用來更新User信息 User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } @RequestMapping(value="/{id}", method=RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { // 處理"/users/{id}"的DELETE請求,用來刪除User users.remove(id); return "success"; } }
針對該Controller編寫測試用例驗證正確性,具體以下。固然也能夠經過瀏覽器插件等進行請求提交驗證。
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.hamcrest.Matchers.equalTo; @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class UserControllerTest { @Autowired private MockMvc mvc; @Test public void testUserController() throws Exception { // 測試UserController RequestBuilder request = null; // 一、get查一下user列表,應該爲空 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[]"))); // 二、post提交一個user request = post("/users/") .param("id", "1") .param("name", "測試大師") .param("age", "20"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 三、get獲取user列表,應該有剛纔插入的數據 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"測試大師\",\"age\":20}]"))); // 四、put修改id爲1的user request = put("/users/1") .param("name", "測試終極大師") .param("age", "30"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 五、get一個id爲1的user request = get("/users/1"); mvc.perform(request) .andExpect(content().string(equalTo("{\"id\":1,\"name\":\"測試終極大師\",\"age\":30}"))); // 六、del刪除id爲1的user request = delete("/users/1"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 七、get查一下user列表,應該爲空 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[]"))); }
至此,咱們經過引入web模塊(沒有作其餘的任何配置),就能夠輕鬆利用Spring MVC的功能,以很是簡潔的代碼完成了對User
對象的RESTful API的建立以及單元測試的編寫。其中同時介紹了Spring MVC中最爲經常使用的幾個核心註解:@Controller
,@RestController
,RequestMapping
以及一些參數綁定的註解:@PathVariable
,@ModelAttribute
,@RequestParam
等。
在前面章節中咱們完成了一個簡單的RESTful Service,體驗了快速開發的特性。可是如何把處理結果渲染到頁面上呢?那麼本篇就在上篇基礎上介紹一下如何進行Web應用的開發。
在咱們開發Web應用的時候,須要引用大量的js、css、圖片等靜態資源。
Spring Boot默認提供靜態資源目錄位置需置於classpath
下,目錄名需符合以下規則:
/static
/public
/resources
/META-INF/resources
舉例:咱們能夠在src/main/resources/
目錄下建立static
,在該位置放置一個圖片文件。啓動程序後,嘗試訪問http://localhost:8080/D.jpg
。如能顯示圖片,配置成功。
在以前的示例中,咱們都是經過@RestController
來處理請求,因此返回的內容爲json
對象。那麼若是須要渲染html頁面的時候,要如何實現呢?
在動態HTML實現上Spring Boot依然能夠完美勝任,而且提供了多種模板引擎的默認配置支持,因此在推薦的模板引擎下,咱們能夠很快的上手開發動態網站。
Spring Boot提供了默認配置的模板引擎主要有如下幾種:
當使用上述模板引擎中的任何一個,它們默認的模板配置路徑爲:src/main/resources/templates
。固然也能夠修改這個路徑,具體如何修改,可在後續模板引擎的配置屬性中查詢並修改。
Thymeleaf是一個XML/XHTML/HTML5模板引擎,可用於Web與非Web環境中的應用開發。它是一個開源的Java庫,基於Apache License 2.0許可,由Daniel Fernández建立,該做者仍是Java加密庫Jasypt的做者。
Thymeleaf提供了一個用於整合Spring MVC的可選模塊,在應用開發中,你可使用Thymeleaf來徹底代替JSP或其餘模板引擎,如Velocity、FreeMarker等。Thymeleaf的主要目標在於提供一種可被瀏覽器正確顯示的、格式良好的模板建立方式,所以也能夠用做靜態建模。你可使用它建立通過驗證的XML與HTML模板。相對於編寫邏輯或代碼,開發者只需將標籤屬性添加到模板中便可。接下來,這些標籤屬性就會在DOM(文檔對象模型)上執行預先制定好的邏輯。
示例模板:
<table> <thead> <tr> <th th:text="#{msgs.headers.name}">Name</td> <th th:text="#{msgs.headers.price}">Price</td> </tr> </thead> <tbody> <tr th:each="prod : ${allProducts}"> <td th:text="${prod.name}">Oranges</td> <td th:text="${#numbers.formatDecimal(prod.price,1,2)}">0.99</td> </tr> </tbody> </table>
能夠看到Thymeleaf主要以屬性的方式加入到html標籤中,瀏覽器在解析html時,當檢查到沒有的屬性時候會忽略,因此Thymeleaf的模板能夠經過瀏覽器直接打開展示,這樣很是有利於先後端的分離。
在Spring Boot中使用Thymeleaf,只須要引入下面依賴,並在默認的模板路徑src/main/resources/templates
下編寫模板文件便可完成。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
在完成配置以後,舉一個簡單的例子,在前面工程的基礎上,舉一個簡單的示例來經過Thymeleaf渲染一個頁面。
定義Controller
@Controller @RequestMapping("/view") public class ViewController { @RequestMapping(value = "/",method = RequestMethod.GET) public String index(Model model) { // 加入一個屬性,用來在模板中讀取 model.addAttribute("host","http://www.dengcl.com"); // return模板文件的名稱,對應src/main/resources/templates/index.html return "index"; } }
定義Thymeleaf模板頁面index.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" > <head> <meta charset="UTF-8"/> <title>Title</title> </head> <body> <h1 th:text="${host}">Hello World</h1> </body> </html>
注意:
index.html
文件在/src/main/resource/templates/
目錄下。
如上頁面,直接打開html頁面展示Hello World
,可是啓動程序後,訪問http://localhost:8080/view
,則是展現Controller中host的值:·http://blog.didispace.com·,作到了不破壞HTML自身內容的數據邏輯分離。
Thymeleaf的默認參數配置
若有須要修改默認配置的時候,只需複製下面要修改的屬性到application.properties中,並修改爲須要的值,如修改模板文件的擴展名,修改默認的模板路徑等。
# Enable template caching. spring.thymeleaf.cache=true # Check that the templates location exists. spring.thymeleaf.check-template-location=true # Content-Type value. spring.thymeleaf.content-type=text/html # Enable MVC Thymeleaf view resolution. spring.thymeleaf.enabled=true # Template encoding. spring.thymeleaf.encoding=UTF-8 # Comma-separated list of view names that should be excluded from resolution. spring.thymeleaf.excluded-view-names= # Template mode to be applied to templates. See also StandardTemplateModeHandlers. spring.thymeleaf.mode=HTML5 # Prefix that gets prepended to view names when building a URL. spring.thymeleaf.prefix=classpath:/templates/ # Suffix that gets appended to view names when building a URL.
雖然spring boot支持這麼多模板引擎實現視圖渲染,可是全部的這些都是後端生成UI,在互聯網的應用中仍是建議設計成基於api的系統,後臺只提供api,前端開發html app。基於vue,react均可以,不過vue更輕量級。
因爲Spring Boot可以快速開發、便捷部署等特性,相信有很大一部分Spring Boot的用戶會用來構建RESTful API。而咱們構建RESTful API的目的一般都是因爲多終端的緣由,這些終端會共用不少底層業務邏輯,所以咱們會抽象出這樣一層來同時服務於多個移動端或者Web前端。
這樣一來,咱們的RESTful API就有可能要面對多個開發人員或多個開發團隊:IOS開發、Android開發或是Web開發等。爲了減小與其餘團隊平時開發期間的頻繁溝通成本,傳統作法咱們會建立一份RESTful API文檔來記錄全部接口細節,然而這樣的作法有如下幾個問題:
因爲接口衆多,而且細節複雜(須要考慮不一樣的HTTP請求類型、HTTP頭部信息、HTTP請求內容等),高質量地建立這份文檔自己就是件很是吃力的事,下游的抱怨聲不絕於耳。
隨着時間推移,不斷修改接口實現的時候都必須同步修改接口文檔,而文檔與代碼又處於兩個不一樣的媒介,除非有嚴格的管理機制,否則很容易致使不一致現象。
爲了解決上面這樣的問題,本文將介紹RESTful API的重磅好夥伴Swagger2,它能夠輕鬆的整合到Spring Boot中,並與Spring MVC程序配合組織出強大RESTful API文檔。它既能夠減小咱們建立文檔的工做量,同時說明內容又整合入實現代碼中,讓維護文檔和修改代碼整合爲一體,可讓咱們在修改代碼邏輯的同時方便的修改文檔說明。另外Swagger2也提供了強大的頁面測試功能來調試每一個RESTful API。具體效果以下圖所示:
下面來具體介紹,若是在Spring Boot中使用Swagger2。首先,咱們須要一個Spring Boot實現的RESTful API工程,這裏使用前面的爲用戶構建RESTFul API的那個工程來持續開發。
在pom.xml
中加入Swagger2的依賴
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.2.2</version> </dependency>
在Springboot的入口函數類SbDemoApplication
同級建立Swagger2的配置類SwaggerConfig
。其代碼以下:
@Configuration //定義這是一個spring的配置類 @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo())//API的說明信息 .select() //選定要生成API的接口或者類的父包 .apis(RequestHandlerSelectors.basePackage("com.dengcl.sb_demo.controller")) .paths(PathSelectors.any()) //路徑規則,這裏是包中全部 .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("XXXX項目接口說明文檔,使用Swagger2實現") .description("XXXX項目接口說明文檔的描述信息") .termsOfServiceUrl("http://127.0.0.1:8080") .contact("後臺研發團隊") .version("0.0.1-SNAPSHOT") .build(); } }
如上代碼所示,經過@Configuration
註解,讓Spring來加載該類配置。再經過@EnableSwagger2
註解來啓用Swagger2。
再經過createRestApi
函數建立Docket
的Bean
以後,apiInfo()
用來建立該Api的基本信息(這些基本信息會展示在文檔頁面中)。select()
函數返回一個ApiSelectorBuilder
實例用來控制哪些接口暴露給Swagger來展示,本例採用指定掃描的包路徑來定義,Swagger會掃描該包下全部Controller
定義的API,併產生文檔內容(除了被@ApiIgnore
指定的請求)
啓動spring boot應用,在瀏覽器輸入:localhost:8080/swagger-ui.html 就能夠查看接口文檔了,而且能夠在該文檔上執行API進行測試驗證。以下圖所示:
雖然在完成了上述配置後,已經能夠生產文檔內容,可是這樣的文檔主要針對請求自己,而描述主要來源於函數等命名產生,對用戶並不友好,咱們一般須要本身增長一些說明來豐富文檔內容。以下所示,咱們經過@ApiOperation
註解來給API增長說明、經過@ApiImplicitParams
、@ApiImplicitParam
註解來給參數增長說明。
@RestController @RequestMapping(value="/users") // 經過這裏配置使下面的映射都在/users下 public class UserController { // 建立線程安全的Map static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); @ApiOperation(value="獲取用戶列表", notes="") @RequestMapping(value={""}, method=RequestMethod.GET) public List<User> getUserList() { List<User> r = new ArrayList<User>(users.values()); return r; } @ApiOperation(value="建立用戶", notes="根據User對象建立用戶") @ApiImplicitParam(name = "user", value = "用戶詳細實體user", required = true, dataType = "User") @RequestMapping(value="", method=RequestMethod.POST) public String postUser(@RequestBody User user) { users.put(user.getId(), user); return "success"; } @ApiOperation(value="獲取用戶詳細信息", notes="根據url的id來獲取用戶詳細信息") @ApiImplicitParam(name = "id", value = "用戶ID", required = true, dataType = "Long") @RequestMapping(value="/{id}", method=RequestMethod.GET) public User getUser(@PathVariable Long id) { return users.get(id); } @ApiOperation(value="更新用戶詳細信息", notes="根據url的id來指定更新對象,並根據傳過來的user信息來更新用戶詳細信息") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "用戶ID", required = true, dataType = "Long"), @ApiImplicitParam(name = "user", value = "用戶詳細實體user", required = true, dataType = "User") }) @RequestMapping(value="/{id}", method=RequestMethod.PUT) public String putUser(@PathVariable Long id, @RequestBody User user) { User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } @ApiOperation(value="刪除用戶", notes="根據url的id來指定刪除對象") @ApiImplicitParam(name = "id", value = "用戶ID", required = true, dataType = "Long") @RequestMapping(value="/{id}", method=RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { users.remove(id); return "success"; } }
完成上述代碼添加上,啓動Spring Boot程序,訪問:http://localhost:8080/swagger-ui.html
。就能看到前文所展現的RESTful API的頁面。咱們能夠再點開具體的API請求,以POST類型的/users請求爲例,可找到上述代碼中咱們配置的Notes信息以及參數user的描述信息,以下圖所示。
在上圖請求的頁面中,咱們看到user的Value是個輸入框?是的,Swagger除了查看接口功能外,還提供了調試測試功能,咱們能夠點擊上圖中右側的Model Schema(黃色區域:它指明瞭User的數據結構),此時Value中就有了user對象的模板,咱們只須要稍適修改,點擊下方「Try it out!」按鈕,便可完成了一次請求調用!
此時,你也能夠經過幾個GET請求來驗證以前的POST請求是否正確。
相比爲這些接口編寫文檔的工做,咱們增長的配置內容是很是少並且精簡的,對於原有代碼的侵入也在忍受範圍以內。所以,在構建RESTful API的同時,加入swagger來對API文檔進行管理,是個不錯的選擇。
咱們在作Web應用的時候,請求處理過程當中發生錯誤是很是常見的狀況。Spring Boot提供了一個默認的映射:/error
,當處理中拋出異常以後,會轉到該請求中處理,而且該請求有一個全局的錯誤頁面用來展現異常內容。
選擇上一章節實現過的Web應用爲基礎,啓動該應用,訪問一個不存在的URL,或是修改處理內容,直接拋出異常,如:
@RestController public class HelloWorld { @RequestMapping("/hello") public String sayHello(){ return "Hello World!"; } @RequestMapping("/happenError") public String happenError() throws Exception { throw new Exception("發生錯誤"); } }
此時啓動spring boot,訪問/happenError
,能夠看到相似下面的報錯頁面,該頁面就是Spring Boot提供的默認error映射頁面。
雖然,Spring Boot中實現了默認的error映射,可是在實際應用中,上面你的錯誤頁面對用戶來講並不夠友好,咱們一般須要去實現咱們本身的異常提示。
下面咱們在上一章的應用中來進行統一異常處理的改造。
建立全局異常處理類:經過使用@ControllerAdvice
定義統一的異常處理類,而不是在每一個Controller中逐個定義。@ExceptionHandler
用來定義函數針對的異常類型,最後將Exception
對象和請求URL映射到error.html
中
@ControllerAdvice class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; } }
實現error.html
頁面展現:在templates
目錄下建立error.html
,將請求的URL
和Exception
對象的message
輸出。
<!DOCTYPE html> <html> <head lang="en" > <meta charset="UTF-8" /> <title>統一異常處理</title> </head> <body> <h1>Error Handler</h1> <div th:text="${url}"></div> <div th:text="${exception.message}"></div> </body> </html>
啓動該應用,訪問:http://localhost:8080/happenError,能夠看到以下錯誤提示頁面。
經過實現上述內容以後,咱們只須要在Controller
中拋出Exception
,固然咱們可能會有多種不一樣的Exception
。而後在@ControllerAdvice
類中,根據拋出的具體Exception
類型匹配@ExceptionHandle
r中配置的異常類型來匹配錯誤映射和處理。
在上述例子中,經過@ControllerAdvice
統必定義不一樣Exception
映射到不一樣錯誤處理頁面。而當咱們要實現RESTful API時,返回的錯誤是JSON
格式的數據,而不是HTML
頁面,這時候咱們也能輕鬆支持。
本質上,只需在@ExceptionHandler
以後加入@ResponseBody
,就能讓處理函數return的內容轉換爲JSON
格式。
下面以一個具體示例來實現返回JSON
格式的異常處理。
public class ErrorInfo<T> { public static final Integer OK = 0; public static final Integer ERROR = 100; private Integer code; private String message; private String url; private T data; // 省略getter和setter }
@ControllerAdvice class GlobalExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class) @ResponseBody //異常時響應JSON public ErrorInfo defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { ErrorInfo errorInfo = new ErrorInfo(); errorInfo.setUrl(req.getRequestURL().toString()); //NoHandlerFoundException對應的是404異常 if(e instanceof NoHandlerFoundException){ errorInfo.setCode(404); errorInfo.setMessage("頁面找不到"); }else{ errorInfo.setCode(500); errorInfo.setMessage(e.getMessage()); } return errorInfo; } }
#出現錯誤時, 直接拋出異常 spring.mvc.throw-exception-if-no-handler-found=true #不要爲咱們工程中的資源文件創建映射, spring.resources.add-mappings=false
說明: spring boot中發生404,默認處理是servlet容器的404處理方案,而不是拋出異常,須要增長上面的配置後才能處理404.
設置spring.resources.add-mappings=false
後,客戶端不能直接訪問工程中的資源文件了。
若是咱們訪問一個不存在映射地址時,以下:
至此,已完成在Spring Boot中建立統一的異常處理,實際實現仍是依靠Spring MVC的註解,更多更深刻的使用可參考Spring MVC的文檔。
Spring Boot在全部內部日誌中默認使用Commons Logging,可是默認配置也提供了對經常使用日誌的支持,如:Java Util Logging
,Log4J
, Log4J2
和Logback
。每種Logger均可以經過配置使用控制檯或者文件輸出日誌內容。
默認的日誌輸出以下:
2018-03-16 11:01:19.728 INFO 10600 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
輸出內容元素具體以下:
在Spring Boot中默認配置了ERROR
、WARN
和INFO
級別的日誌輸出到控制檯。
咱們能夠經過兩種方式切換至DEBUG
級別:
--debug
標誌,如:java -jar myapp.jar --debug
application.properties
中配置debug=true
,該屬性置爲true
的時候,核心Logger(包含嵌入式容器、hibernate、spring)會輸出更多內容,可是你本身應用的日誌並不會輸出爲DEBUG級別。若是你的終端支持ANSI
,設置彩色輸出會讓日誌更具可讀性。經過在application.properties
中設置spring.output.ansi.enabled
參數來支持。
Spring Boot默認配置只會輸出到控制檯,並不會記錄到文件中,可是咱們一般生產環境使用時都須要以文件方式記錄。
若要增長文件輸出,須要在application.properties
中配置logging.file
或logging.path
屬性。
logging.file=my.log
logging.path=/var/log
日誌文件會在10Mb大小的時候被截斷,產生新的日誌文件,默認級別爲:ERROR、WARN、INFO
在Spring Boot中只須要在application.properties
中進行配置完成日誌記錄的級別控制。
配置格式:logging.level.*=LEVEL
*
爲包名或Logger名舉例:
logging.level.com.dengcl=DEBUG
:com.dengcl
包下全部class以DEBUG級別輸出logging.level.root=WARN
:root日誌以WARN級別輸出因爲日誌服務通常都在ApplicationContext
建立前就初始化了,它並非必須經過Spring的配置文件控制。所以經過系統屬性和傳統的Spring Boot外部配置文件依然能夠很好的支持日誌控制和管理。
根據不一樣的日誌系統,你能夠按以下規則組織配置文件名,就能被正確加載:
|日誌系統|配置文件名|
|---|----|
|Logback|logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy|
|Log4j|log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml|
|Log4j2|log4j2-spring.xml, log4j2.xml|
|JDK (Java Util Logging)|logging.properties|
Spring Boot官方推薦優先使用帶有-spring
的文件名做爲你的日誌配置(如使用logback-spring.xml
,而不是logback.xml
)
在Spring Boot中能夠經過在application.properties
配置以下參數控制輸出格式:
logging.pattern.console
:定義輸出到控制檯的樣式(不支持JDK Logger)logging.pattern.file
:定義輸出到文件的樣式(不支持JDK Logger)AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是Spring框架中的一個重要內容,它經過對既有程序定義一個切入點,而後在其先後切入不一樣的執行內容,好比常見的有:打開數據庫鏈接/關閉數據庫鏈接、打開事務/關閉事務、記錄日誌等。基於AOP不會破壞原來程序邏輯,所以它能夠很好的對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。
下面主要講兩個內容,一個是如何在Spring Boot中引入Aop功能,二是如何使用Aop作切面去統一處理Web請求的日誌。
由於須要對web請求作切面來記錄日誌,因此先引入web模塊,並建立一個簡單的hello請求的處理。
pom.xml中引入web模塊
在Spring Boot中引入AOP就跟引入其餘模塊同樣,很是簡單,只須要在pom.xml中加入以下依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
在完成了引入AOP依賴包後,通常來講並不須要去作其餘配置。也許在Spring中使用過註解配置方式的人會問是否須要在程序主類中增長@EnableAspectJAutoProxy
來啓用,實際並不須要。
能夠看下面關於AOP的默認配置屬性,其中spring.aop.auto
屬性默認是開啓的,也就是說只要引入了AOP依賴後,默認已經增長了@EnableAspectJAutoProxy
。
# AOP spring.aop.auto=true # Add @EnableAspectJAutoProxy. spring.aop.proxy-target-class=false # Whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false).
而當咱們須要使用CGLIB來實現AOP的時候,須要配置spring.aop.proxy-target-class=true
,否則默認使用的是標準Java的實現AOP。
實現AOP的切面主要有如下幾個要素:
@Aspect
註解將一個java類定義爲切面類@Pointcut
定義一個切入點,能夠是一個規則表達式,好比下例中某個package下的全部函數,也能夠是一個註解等。@Before
在切入點開始處切入內容@After
在切入點結尾處切入內容@AfterReturning
在切入點return內容以後切入內容(能夠用來對處理返回值作一些加工處理)@Around
在切入點先後切入內容,並本身控制什麼時候執行切入點自身的內容@AfterThrowing
用來處理當切入內容部分拋出異常以後的處理邏輯切面類代碼以下:
@Aspect @Component public class WebLogAspect { private Logger logger = Logger.getLogger(this.getClass()); @Pointcut("execution(public * com.dengcl.sb_demo.controller.*.*(..))") public void pt(){} @Before("pt()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 接收到請求,記錄請求內容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 記錄下請求內容 logger.info("URL : " + request.getRequestURL().toString()); logger.info("HTTP_METHOD : " + request.getMethod()); logger.info("IP : " + request.getRemoteAddr()); logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs())); } @AfterReturning(returning = "ret", pointcut = "pt()") public void doAfterReturning(Object ret) throws Throwable { // 處理完請求,返回內容 logger.info("RESPONSE : " + ret); } }
能夠看上面的例子,經過·@Pointcut·定義的切入點爲com.dengcl.sb_demo.controller
包下的全部函數(對web層全部請求處理作切入點),而後經過@Befor
e實現,對請求內容的日誌記錄(本文只是說明過程,能夠根據須要調整內容),最後經過@AfterReturning
記錄請求返回的對象。
經過運行程序並訪問:http://localhost:8080/users/
,能夠得到下面的日誌輸出
2018-03-16 11:01:32.662 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : URL : http://localhost:8080/users 2018-03-16 11:01:32.662 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : HTTP_METHOD : GET 2018-03-16 11:01:32.663 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : IP : 0:0:0:0:0:0:0:1 2018-03-16 11:01:32.663 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : CLASS_METHOD : com.dengcl.sb_demo.controller.UserController.getUserList 2018-03-16 11:01:32.665 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : ARGS : [] 2018-03-16 11:01:32.675 INFO 10600 --- [nio-8080-exec-1] com.dengcl.sb_demo.advice.WebLogAspect : RESPONSE : []
在WebLogAspect切面中,分別經過doBefore
和doAfterReturning
兩個獨立函數實現了切點頭部和切點返回後執行的內容,若咱們想統計請求的處理時間,就須要在doBefore
處記錄時間,並在doAfterReturning
處經過當前時間與開始處記錄的時間計算獲得請求處理的消耗時間。
那麼咱們是否能夠在WebLogAspect
切面中定義一個成員變量來給doBefore
和doAfterReturning
一塊兒訪問呢?是否會有同步問題呢?
的確,直接在這裏定義基本類型會有同步問題,因此咱們能夠引入ThreadLocal
對象,像下面這樣進行記錄:
@Aspect @Component public class WebLogAspect { private Logger logger = Logger.getLogger(getClass()); ThreadLocal<Long> startTime = new ThreadLocal<>(); @Pointcut("execution(public * com.dengcl.sb_demo.controller.*.*(..))") public void webLog(){} @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { startTime.set(System.currentTimeMillis()); // 省略日誌記錄內容 } @AfterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) throws Throwable { // 處理完請求,返回內容 logger.info("RESPONSE : " + ret); logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get())); } }
因爲經過AOP實現,程序獲得了很好的解耦,可是也會帶來一些問題,好比:咱們可能會對Web層作多個切面,校驗用戶,校驗頭信息等等,這個時候常常會碰到切面的處理順序問題。
因此,咱們須要定義每一個切面的優先級,咱們須要@Order(i)
註解來標識切面的優先級。i的值越小,優先級越高。假設咱們還有一個切面是CheckNameAspect
用來校驗name
必須爲didi
,咱們爲其設置@Order(10)
,而上文中WebLogAspect
設置爲@Order(5)
,因此WebLogAspect
有更高的優先級,這個時候執行順序是這樣的:
@Before
中優先執行@Order(5)
的內容,再執行@Order(10)
的內容@After
和@AfterReturning
中優先執行@Order(10)
的內容,再執行@Order(5)
的內容因此咱們能夠這樣子總結:
pom.xml
中引入依賴
引入鏈接mysql
的必要依賴mysql-connector-java
引入整合MyBatis
的核心依賴mybatis-spring-boot-starter
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.45</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency>
在application.properties
中配置mysql
的鏈接配置
spring.datasource.url=jdbc:mysql://localhost:3306/users spring.datasource.username=root spring.datasource.password=12345678 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
簡單且簡潔的的完成了基本配置,下面看看如何在這個基礎下輕鬆方便的使用MyBatis訪問數據庫。
在Mysql中建立User
表,包含id(BIGINT)、name(INT)、age(VARCHAR)字段。同時,建立映射對象User
,這裏再也不列出建表和映射對象的代碼。
建立User映射的操做UserMapper.java
,爲了後續單元測試驗證,實現插入和查詢操做。
@Mapper public interface UserMapper { @Select("SELECT * FROM USERS WHERE NAME = #{name}") User findByName(@Param("name") String name); @Insert("INSERT INTO USERS(NAME, AGE) VALUES(#{name}, #{age})") int insert(@Param("name") String name, @Param("age") Integer age); }
@Mapper
定義本接口是一個mybatis的mapper接口類。
建立單元測試
@RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper mapper; @Test @Transactional @Rollback public void findByName() throws Exception { mapper.insert("AAA", 20); User u = mapper.findByName("AAA"); Assert.assertEquals(20, u.getAge().intValue()); } }
前面的示例是經過註解的方式來實現mybatis的mapper,可是在複雜的應用場景下,基於xml的mapper更加靈活,更方便維護。
UserMapper
,去掉註解@Mapper public interface UserMapper { // @Select("SELECT * FROM USERS WHERE NAME = #{name}") User findByName(@Param("name") String name); // @Insert("INSERT INTO USERS(NAME, AGE) VALUES(#{name}, #{age})") int insert(@Param("name") String name, @Param("age") Integer age); }
UserMapper.xml
,實現mybatis映射<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.dengcl.sb_demo.mapper.UserMapper"> <select id="findByName" resultType="com.dengcl.sb_demo.pojo.User"> SELECT * FROM USERS WHERE NAME = #{name} </select> <insert id="insert" > INSERT INTO USERS(NAME, AGE) VALUES(#{name}, #{age}) </insert> </mapper>
在application.properties
文件中增長以下配置:
mybatis.mapper-locations=classpath:/com/dengcl/sb_demo/mapper/*.xml
在Spring Boot中,當咱們使用了spring-boot-starter-jdbc
依賴的時候,框架會自動默認分別注入DataSourceTransactionManager
或JpaTransactionManager
。因此咱們不須要任何額外配置就能夠用@Transactional
註解進行事務的使用。
注意:在上面的示例中咱們雖然未使用
spring-boot-starter-jdbc
依賴,但也能夠直接使用事務,緣由是mybatis-spring-boot-starter
中已經包含了此依賴
例如:
@Test @Transactional public void batchAdd(){ mapper.insert("aaa",12); mapper.insert("aaaa",13); mapper.insert("aaaaa",14); mapper.insert("aaaaaa",15); mapper.insert("aaaaaaa",16); }
該方法中的多個數據操做就在同一個事務中。
這裏是經過單元測試演示瞭如何使用@Transactional
註解來聲明一個方法須要被事務管理,一般咱們單元測試爲了保證每一個測試之間的數據獨立,會使用@Rollback
註解讓每一個單元測試都能在結束時回滾。而真正在開發業務邏輯時,咱們一般在service
層接口中使用@Transactional
來對各個業務邏輯進行事務管理的配置,例如:
public interface UserService { @Transactional User login(String name, String password); }
上面的例子中咱們使用了默認的事務配置,能夠知足一些基本的事務需求,可是當咱們項目較大較複雜時(好比,有多個數據源等),這時候須要在聲明事務時,指定不一樣的事務管理器。在聲明事務時,只須要經過value屬性指定配置的事務管理器名便可,例如:@Transactional(value="transactionManagerPrimary")
。
除了指定不一樣的事務管理器以後,還能對事務進行隔離級別和傳播行爲的控制,下面分別詳細解釋:
隔離級別是指若干個併發的事務之間的隔離程度,與咱們開發時候主要相關的場景包括:髒讀取、重複讀、幻讀。
咱們能夠看org.springframework.transaction.annotation.Isolation枚舉類中定義了五個表示隔離級別的值:
public enum Isolation { DEFAULT(-1), READ_UNCOMMITTED(1), READ_COMMITTED(2), REPEATABLE_READ(4), SERIALIZABLE(8); }
DEFAULT
:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,一般這值就是READ_COMMITTED
。READ_UNCOMMITTED
:該隔離級別表示一個事務能夠讀取另外一個事務修改但尚未提交的數據。該級別不能防止髒讀和不可重複讀,所以不多使用該隔離級別。READ_COMMITTED
:該隔離級別表示一個事務只能讀取另外一個事務已經提交的數據。該級別能夠防止髒讀,這也是大多數狀況下的推薦值。REPEATABLE_READ
:該隔離級別表示一個事務在整個過程當中能夠屢次重複執行某個查詢,而且每次返回的記錄都相同。即便在屢次查詢之間有新增的數據知足該查詢,這些新增的記錄也會被忽略。該級別能夠防止髒讀和不可重複讀。SERIALIZABLE
:全部的事務依次逐個執行,這樣事務之間就徹底不可能產生干擾,也就是說,該級別能夠防止髒讀、不可重複讀以及幻讀。可是這將嚴重影響程序的性能。一般狀況下也不會用到該級別。指定方法:經過使用isolation
屬性設置事務的隔離級別,例如:
@Transactional(isolation = Isolation.DEFAULT)
所謂事務的傳播行爲是指,若是在開始當前事務以前,一個事務上下文已經存在,此時有若干選項能夠指定一個事務性方法的執行行爲。
咱們能夠看org.springframework.transaction.annotation.Propagation
枚舉類中定義了6個表示傳播行爲的枚舉值:
public enum Propagation { REQUIRED(0), SUPPORTS(1), MANDATORY(2), REQUIRES_NEW(3), NOT_SUPPORTED(4), NEVER(5), NESTED(6); }
REQUIRED
:若是當前存在事務,則加入該事務;若是當前沒有事務,則建立一個新的事務。SUPPORTS
:若是當前存在事務,則加入該事務;若是當前沒有事務,則以非事務的方式繼續運行。MANDATORY
:若是當前存在事務,則加入該事務;若是當前沒有事務,則拋出異常。REQUIRES_NEW
:建立一個新的事務,若是當前存在事務,則把當前事務掛起。NOT_SUPPORTED
:以非事務方式運行,若是當前存在事務,則把當前事務掛起。NEVER
:以非事務方式運行,若是當前存在事務,則拋出異常。NESTED
:若是當前存在事務,則建立一個事務做爲當前事務的嵌套事務來運行;若是當前沒有事務,則該取值等價於REQUIRED
。指定方法:經過使用propagation屬性設置事務的傳播行爲,例如:
@Transactional(propagation = Propagation.REQUIRED)