Spring Boot學習3:web篇(中)-Spring boot Rest學習
--REST理論基礎
1.架構屬性
效能
可伸縮性
統一結構簡化性:如URI,RequestHeader,RequestBody等等
元件可修改性:
元件通訊可見性
元件可移植性
可靠性
2.架構約束
C/S架構
無狀態
可快取:兩個方面,服務端和客戶端
分層系統
按需程式碼
統一介面
3.統一幾口(Uniform Interface)
資源識別:URI
資源操作:GET(取資源),POST(非冪等性,實現的時候需要實現冪等性),PUT(更新資源),DELETE
自描述資訊:ContentType,MIME-Type,Meida-Type
超媒體
--REST服務端實踐
1.Spring Boot Rest
核心介面:
定義相關
@Controller
@RestController
對映相關
@RequestMapping
@PathVariable 路徑變數
請求相關
@RequestParam
@RequestHeader
@CookieValue
RequestEntity
響應相關
@ResponseBody
ResponseEntity
實踐:
在start.spring.io上建立專案,並Generate Project
Group:com.segmentfault
Artifact:spring-boot-lesson-3
Dependencies:Web,Actuator
將匯出的工程匯入到IDEA中。
rest支援多種返回格式,不僅限於JSON,XML,HTML,還可以自定義的格式
例子1:HTML例子
@Controller
public class RestDemoController {
//HTML
@RequestMapping("/html/demo")
@ResponseBody
public String htmlCode(){
return "<html><body><h1>hello</h1></body></html>";
}
}
訪問http://localhost:8080/html/demo地址,可以看到返回的html頁面資訊
例子2:多種Mapping語法
//HTML,可以對映到多個方法上
//@RequestMapping(value={"/html/demo","/html/demo2"},method = {RequestMethod.GET})
@GetMapping(path={"/html/demo3"}) //相對於 @RequestMapping(value={"/html/demo3"},method = {RequestMethod.GET})
@ResponseBody
public String htmlCode(){
return "<html><body><h1>hello</h1></body></html>";
}
@RequestMapping和@GetMapping是會衝突的,兩者不能共存
重新啟動SpringBoot
Mapped "{[/html/demo3],methods=[GET]}" onto public java.lang.String com.segmentfault.springbootlesson3.controller.RestDemoController.htmlCode()
例子3: @RestController
@RestController 相對於 @Controller和@ResponseBody的組合
//@Controller
@RestController
public class RestDemoController {
//HTML,可以對映到多個方法上
//@RequestMapping(value={"/html/demo","/html/demo2"},method = {RequestMethod.GET})
@GetMapping(path={"/html/demo3"})
// @PostMapping(path={"/html/demo2"})
// @ResponseBody
public String htmlCode(){
return "<html><body><h1>hello</h1></body></html>";
}
}
此時訪問網站,可以看到html返回的頁面效果。
如果去除@RestController,改成@Controller,此時因為沒有@ResponseBody,所以:
return "<html><body><h1>hello</h1></body></html>";時,springboot就去template目錄中找對應的地址找模板了,但是因為找不到,所以在頁面上返回:Whitelabel Error Page
例子4:@PathVariable
//@Controller
@RestController
public class RestDemoController {
//HTML,可以對映到多個方法上
//@RequestMapping(value={"/html/demo","/html/demo2"},method = {RequestMethod.GET})
@GetMapping(path={"/html/demo3"})
// @PostMapping(path={"/html/demo2"})
// @ResponseBody
public String htmlCode(){
return "<html><body><h1>hello</h1></body></html>";
}
@GetMapping(path={"/html/demo/{messages}"})
public String htmlCode(@PathVariable String messages){
return "<html><body>"+messages+"</body></html>";
}
}
啟動專案,在瀏覽器中輸入地址:http://localhost:8080/html/demo/hellohello
那麼在頁面上顯示了hellohello
例子5:@RequestParam
@GetMapping(path={"/html/demo/param"})
public String htmlParam(@RequestParam String param){
return "<html><body>"+param+"</body></html>";
}
在瀏覽器中輸入:http://localhost:8080/html/demo/param?param=helloworld
瀏覽器返回結果:helloworld
方法可以進一步修改為:
@GetMapping(path={"/html/demo/param"})
//指定該引數不是必須的
public String htmlParam(@RequestParam(name="p",required=false,defaultValue="abc") String param){
return "<html><body>"+param+"</body></html>";
}
name="p",required=false,name是指引數的名稱,required是設定引數是否必需,defaultValue是設定預設值
當輸入http://localhost:8080/html/demo/param沒有攜帶引數的時候,就會返回abc
當然,我們也是可以使用Servlet來獲取對應的parameter的:
@GetMapping(path={"/html/demo/param"})
//指定該引數不是必須的
public String htmlParam(@RequestParam(name="p",required=false,defaultValue="Empty") String param, HttpServletRequest request){
String param2 = request.getParameter("p");
return "<html><body>requestparam:"+param+",request:"+param2+"</body></html>";
}
自動轉換功能:當我們設定了@ReqeustParam的引數型別為Integer的時候,那麼傳入的引數就會自動轉換型別
@GetMapping(path={"/html/demo/param"})
//指定該引數不是必須的
public String htmlParam(@RequestParam(name="p",required=false,defaultValue="Empty") String param, HttpServletRequest request,
@RequestParam(name="age",required = false,defaultValue = "0") Integer age){
String param2 = request.getParameter("p");
System.out.println(age+1);
return "<html><body>requestparam:"+param+",request:"+param2+"</body></html>";
}
輸入地址:http://localhost:8080/html/demo/param?age=100
在Console中自動列印了101,說明引數型別字串已經自動轉換未Integer型別了
例子6:@RequestHeader
@GetMapping(path={"/html/demo/header"})
//指定該引數不是必須的
public String htmlHeader(@RequestHeader(value="Accept") String acceptHeader, HttpServletRequest request){
return "<html><body>accept header:"+acceptHeader+"</body></html>";
}
在瀏覽器中輸入:http://localhost:8080/html/demo/header
返回結果:accept header:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
可以看到請求頭彙總的Accept
例子7:RequestEntity
可以獲取到請求頭和請求body的全部資訊,相比於Requestheader更多的資訊:
@GetMapping(path={"/html/demo/response/entity"})
//ResponseEntity是
public ResponseEntity<String> htmlResponseEntity(RequestEntity request){
System.out.println(request.getUrl());
HttpHeaders header = new HttpHeaders();
header.add("myheader","helloworld");
ResponseEntity entity = new ResponseEntity("<html><body>html ResponseEntity</body></html>",header, HttpStatus.OK);
return entity;
}
訪問後結果:http://localhost:8080/html/demo/response/entity
http://localhost:8080/html/demo/response/entity
也可以指定獲取請求頭中的資訊:
request.getHeaders().get("Accept")
執行結果:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8]
例子8:ResponseEntity
ResponseEntity可以管理頭和body,而ResponseBody只能管理Body部分
@GetMapping(path={"/html/demo/response/entity"})
//ResponseEntity是泛型
public ResponseEntity<String> htmlResponseEntity(){
return ResponseEntity.ok("<html><body>html ResponseEntity</body></html>");
}
其中ResponseEntity.ok指定了返回的status
同樣的,也可以自定義返回的Header
@GetMapping(path={"/html/demo/response/entity"})
//ResponseEntity是
public ResponseEntity<String> htmlResponseEntity(){
HttpHeaders header = new HttpHeaders();
header.add("myheader","helloworld");
ResponseEntity entity = new ResponseEntity("<html><body>html ResponseEntity</body></html>",header, HttpStatus.OK);
return entity;
}
訪問該地址:http://localhost:8080/html/demo/response/entity
頁面上顯示html ResponseEntity
開啟瀏覽器Network檢視請求頭資訊
myheader:helloworld
例子9:Json格式的例子
建立User類
package com.segmentfault.springbootlesson3.pojo;
public class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
建立JsonDemoController:
@RestController
public class JsonDemoController {
@GetMapping("/json/user")
public User JsonMethod(){
User user = new User();
user.setName("xiaoming");
user.setAge(10);
return user;
}
}
訪問地址:
http://localhost:8080/json/user
返回結果:
{"name":"xiaoming","age":10}
那麼我們如何改成xml格式的呢?
配置pom
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
重啟後,訪問地址:http://localhost:8080/json/user
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<User>
<name>xiaoming</name>
<age>10</age>
</User>
因為這個時候系統訪問時,會先精確匹配是否有xml,如果有,優先匹配返回xml格式,其次才會考慮json格式
可以在請求頭中找到Accept:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
我們也可以指定某個Controller的某個方法的返回格式是某種格式,如Json格式等,通過produces=MediaType.xxx來指定
@RestController
public class JsonDemoController {
@GetMapping(value="/json/user",produces= MediaType.APPLICATION_JSON_VALUE)
public User JsonMethod(){
User user = new User();
user.setName("xiaoming");
user.setAge(10);
return user;
}
}
此時再次訪問同樣的地址:返回結果
{"name":"xiaoming","age":10}
例子10:建立一個XMLDemoController
@RestController
public class XMLDemoController {
@GetMapping(value="/xml/user",produces= MediaType.APPLICATION_XML_VALUE)
public User JsonMethod(){
User user = new User();
user.setName("xiaoming XML");
user.setAge(30);
return user;
}
}
啟動過程中,可以看列印控制檯中的資訊,可以看到對映資訊
Mapped "{[/json/user],methods=[GET],produces=[application/json]}"
Mapped "{[/xml/user],methods=[GET],produces=[application/xml]}"
11)我們可以手動指定消費者的處理型別,如我們上面看到的Accept中支援處理xml格式等型別,我們也可以要求消費者的處理型別
例子11:
@RestController
public class XMLDemoController {
@GetMapping(value="/xml/user",
produces = MediaType.APPLICATION_XML_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE
)
public User JsonMethod(){
User user = new User();
user.setName("xiaoming XML");
user.setAge(30);
return user;
}
}
此時瀏覽器訪問地址localhost:8080/xml/user,提示不支援型別,因為Accept中沒有支援Json型別的處理
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Feb 05 11:25:21 CST 2018
There was an unexpected error (type=Unsupported Media Type, status=415).
Content type 'null' not supported
那麼我們如果需要讓瀏覽器處理,我們需要在程式中配置consumes中,加上瀏覽器支援的型別
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
修改後的程式碼:
@RestController
public class XMLDemoController {
@GetMapping(value="/xml/user",
produces = MediaType.APPLICATION_XML_VALUE,
consumes = {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_XML_VALUE,MediaType.TEXT_HTML_VALUE,"application/xhtml+xml"}
)
public User JsonMethod(){
User user = new User();
user.setName("xiaoming XML");
user.setAge(30);
return user;
}
}
2.HATEOAS
我們少了一個發現服務的入口!!!外面的人不知道我們釋出了服務!!!
HATEOAS提供了服務發現的功能
找到spring.io中的Spring HATEOAS,找到引包的配置
<dependencies>
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>0.24.0.RELEASE</version>
</dependency>
</dependencies>
引入成功後,就可以使用HATEOAS了。
參考:https://docs.spring.io/spring-hateoas/docs/0.24.0.RELEASE/reference/html/#fundamentals.links
按Alt+Enter,選擇Import static method,注入mvc相關的依賴
package com.segmentfault.springbootlesson3.controller;
import com.segmentfault.springbootlesson3.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.awt.*;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
@RestController
public class JsonDemoController {
@Bean
public User currentUser(){
User user = new User();
user.setName("Json");
user.setAge(20);
return user;
}
@Autowired
@Qualifier(value="currentUser") //等於currentUser
public User user;
@GetMapping(path="/json/user",produces= MediaType.APPLICATION_JSON_VALUE)
public User user(){
return user;
}
//setName
@GetMapping(path="/json/user/set/name",
produces = MediaType.APPLICATION_JSON_VALUE
)
public User setUserName(@RequestParam String name){
user.setName(name);
//暴露服務介面
user.add(linkTo(methodOn(JsonDemoController.class).setUserName(name)).withSelfRel());
return user;
}
//setAge
@GetMapping(path="/json/user/set/age",
produces = MediaType.APPLICATION_JSON_VALUE
)
public User setUserAge(@RequestParam Integer age){
user.setAge(age);
user.add(linkTo(methodOn(JsonDemoController.class).setUserAge(age)).withSelfRel());
return user;
}
}
此時訪問:http://localhost:8080/json/user
返回:{"name":"Json","age":20,"links":[]}
訪問:http://localhost:8080/json/user/set/name?name=Hello123
{"name":"Hello123","age":20,"links":[{"rel":"self","href":"http://localhost:8080/json/user/set/name?name=Hello123","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}
此時再次訪問:http://localhost:8080/json/user ,此時的user就有狀態了
{"name":"Hello123","age":20,"links":[{"rel":"self","href":"http://localhost:8080/json/user/set/name?name=Hello123","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}
此時訪問setAge:http://localhost:8080/json/user/set/age?age=120
{"name":"Hello123","age":120,"links":[{"rel":"self","href":"http://localhost:8080/json/user/set/name?name=Hello123","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null},{"rel":"self","href":"http://localhost:8080/json/user/set/age?age=120","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}
可以看到user上有兩個url了
上面的註冊服務介面可以開始的時候就放到User獲取的時候進行注入
修改:
@GetMapping(path="/json/user",produces= MediaType.APPLICATION_JSON_VALUE)
public User user(){
//暴露服務介面
user.add(linkTo(methodOn(JsonDemoController.class).setUserName(user.getName())).withSelfRel());
user.add(linkTo(methodOn(JsonDemoController.class).setUserAge(user.getAge())).withSelfRel());
return user;
}
去除方法中的暴露服務介面的方法
這樣,我們在啟動的時候,就可以獲取到服務的入口。告訴了我們連結的相關操作
重啟專案,輸入:http://localhost:8080/json/user
{"name":"Json","age":20,"links":[{"rel":"self","href":"http://localhost:8080/json/user/set/name?name=Json","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null},{"rel":"self","href":"http://localhost:8080/json/user/set/age?age=20","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}
3.REST文件生成
1)spring提供了Spring REST Docs,可以生成rest介面文件
2)swagger,動態生成!
3)spring boot/mappings endpoint, spring boot釋出後提供的功能
檢視的時候,需要配置,因為高版本的springboot對許可權的認證比較嚴格
在EndpointProperties中可以看到:
public class EndpointProperties {
private static final String ENDPOINTS_ENABLED_PROPERTY = "endpoints.enabled";
private static final String ENDPOINTS_SENSITIVE_PROPERTY = "endpoints.sensitive";
。。。
在application.properties中配置
endpoints.enabled=true
endpoints.sensitive = false
重啟專案後,輸入:http://localhost:8080/mappings
可以看到專案中所有的地址對映
{[/xml/user],methods=[GET],consumes=[text/html || application/xhtml+xml || application/xml],produces=[application/xml]}
bean "requestMappingHandlerMapping"
method
{[/json/user/set/name],methods=[GET],produces=[application/json]}
bean "requestMappingHandlerMapping"
method
可以看到,基本的資訊都已經提供了,只是引數的名稱和型別沒有提供
4.REST客戶端實踐
1)Web瀏覽器
2)Apache HttpClient
在user中註冊xmlDemoController中的返回xml格式的方法:
@RestController
public class XMLDemoController {
@GetMapping(value="/xml/user",
produces = MediaType.APPLICATION_XML_VALUE
)
public User JsonMethod(){
User user = new User();
user.setName("xiaoming XML");
user.setAge(30);
return user;
}
}
@GetMapping(path="/json/user",produces= MediaType.APPLICATION_JSON_VALUE)
public User user(){
//暴露服務介面
user.add(linkTo(methodOn(JsonDemoController.class).setUserName(user.getName())).withSelfRel());
user.add(linkTo(methodOn(JsonDemoController.class).setUserAge(user.getAge())).withSelfRel());
user.add(linkTo(methodOn(XMLDemoController.class).JsonMethod()).withSelfRel());
return user;
}
重啟程式:http://localhost:8080/xml/user
<User><name>xiaoming XML</name><age>30</age><links/></User>
3)Spring RestTemplate
我們再來看一下RestTemplate這個模板類,
查詢HttpComponentsClientHttpRequestFactory類,
其中一個建構函式:org.apache.http.impl.client.AbstractHttpClient的引數類
我們在search.maven.org中查詢這個類的原始碼,引用對應的maven配置
在pom.xml中加入引用:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
我們可整合Apache Client和HttpClient,適配底層的方法HttpClient
修改RestClient中的方法:
package com.segmentfault.springbootlesson3.client;
import com.segmentfault.springbootlesson3.pojo.User;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
public class RestClient {
public static void main(String[] args) {
HttpClientBuilder builder = HttpClientBuilder.create();
HttpClient httpClient = builder.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(factory);
// String result = restTemplate.getForObject("http://localhost:8080/json/user",String.class);
//執行反序列化
User user = restTemplate.getForObject("http://localhost:8080/json/user",User.class);
System.out.println(user);
}
}
此時再次啟動springboot,然後使用RestTemplate訪問底層介面
看控制檯列印:可以看到和之前的不同,列印出了底層的呼叫方式
"C:\Program Files (x86)\Java\jdk1.8.0_91\bin\java" -javaagent:E:\ideaIU\lib\idea_rt.jar=25583:E:\ideaIU\bin -Dfile.encoding=UTF-8 -classpath "C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\charsets.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\deploy.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\access-bridge-32.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\cldrdata.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\dnsns.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\jaccess.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\jfxrt.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\localedata.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\nashorn.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunec.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunjce_provider.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunmscapi.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunpkcs11.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\zipfs.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\javaws.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jfr.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jfxswt.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jsse.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\management-agent.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\plugin.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\rt.jar;E:\springboot\spring-boot-lesson-3\target\classes;E:\maven\localwarehouse\org\springframework\boot\spring-boot-starter-actuator\1.5.10.RELEASE\spring-boot-starter-actuator-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-starter\1.5.10.RELEASE\spring-boot-starter-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot\1.5.10.RELEASE\spring-boot-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-autoconfigure\1.5.10.RELEASE\spring-boot-autoconfigure-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-starter-logging\1.5.10.RELEASE\spring-boot-starter-logging-1.5.10.RELEASE.jar;E:\maven\localwarehouse\ch\qos\logback\logback-classic\1.1.11\logback-classic-1.1.11.jar;E:\maven\localwarehouse\ch\qos\logback\logback-core\1.1.11\logback-core-1.1.11.jar;E:\maven\localwarehouse\org\slf4j\jcl-over-slf4j\1.7.25\jcl-over-slf4j-1.7.25.jar;E:\maven\localwarehouse\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;E:\maven\localwarehouse\org\slf4j\log4j-over-slf4j\1.7.25\log4j-over-slf4j-1.7.25.jar;E:\maven\localwarehouse\org\yaml\snakeyaml\1.17\snakeyaml-1.17.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-actuator\1.5.10.RELEASE\spring-boot-actuator-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-starter-web\1.5.10.RELEASE\spring-boot-starter-web-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-starter-tomcat\1.5.10.RELEASE\spring-boot-starter-tomcat-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\apache\tomcat\embed\tomcat-embed-core\8.5.27\tomcat-embed-core-8.5.27.jar;E:\maven\localwarehouse\org\apache\tomcat\tomcat-annotations-api\8.5.27\tomcat-annotations-api-8.5.27.jar;E:\maven\localwarehouse\org\apache\tomcat\embed\tomcat-embed-el\8.5.27\tomcat-embed-el-8.5.27.jar;E:\maven\localwarehouse\org\apache\tomcat\embed\tomcat-embed-websocket\8.5.27\tomcat-embed-websocket-8.5.27.jar;E:\maven\localwarehouse\org\hibernate\hibernate-validator\5.3.6.Final\hibernate-validator-5.3.6.Final.jar;E:\maven\localwarehouse\javax\validation\validation-api\1.1.0.Final\validation-api-1.1.0.Final.jar;E:\maven\localwarehouse\org\jboss\logging\jboss-logging\3.3.1.Final\jboss-logging-3.3.1.Final.jar;E:\maven\localwarehouse\com\fasterxml\classmate\1.3.4\classmate-1.3.4.jar;E:\maven\localwarehouse\com\fasterxml\jackson\core\jackson-databind\2.8.10\jackson-databind-2.8.10.jar;E:\maven\localwarehouse\org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-webmvc\4.3.14.RELEASE\spring-webmvc-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-expression\4.3.14.RELEASE\spring-expression-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-core\4.3.14.RELEASE\spring-core-4.3.14.RELEASE.jar;E:\maven\localwarehouse\com\fasterxml\jackson\dataformat\jackson-dataformat-xml\2.8.10\jackson-dataformat-xml-2.8.10.jar;E:\maven\localwarehouse\com\fasterxml\jackson\core\jackson-core\2.8.10\jackson-core-2.8.10.jar;E:\maven\localwarehouse\com\fasterxml\jackson\core\jackson-annotations\2.8.0\jackson-annotations-2.8.0.jar;E:\maven\localwarehouse\com\fasterxml\jackson\module\jackson-module-jaxb-annotations\2.8.10\jackson-module-jaxb-annotations-2.8.10.jar;E:\maven\localwarehouse\org\codehaus\woodstox\stax2-api\3.1.4\stax2-api-3.1.4.jar;E:\maven\localwarehouse\com\fasterxml\woodstox\woodstox-core\5.0.3\woodstox-core-5.0.3.jar;E:\maven\localwarehouse\org\springframework\hateoas\spring-hateoas\0.24.0.RELEASE\spring-hateoas-0.24.0.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-aop\4.3.14.RELEASE\spring-aop-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-beans\4.3.14.RELEASE\spring-beans-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-context\4.3.14.RELEASE\spring-context-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;E:\maven\localwarehouse\org\apache\httpcomponents\httpclient\4.5.5\httpclient-4.5.5.jar;E:\maven\localwarehouse\org\apache\httpcomponents\httpcore\4.4.9\httpcore-4.4.9.jar;E:\maven\localwarehouse\commons-codec\commons-codec\1.10\commons-codec-1.10.jar" com.segmentfault.springbootlesson3.client.RestClient
15:45:58.371 [main] DEBUG org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:8080/json/user"
15:45:58.540 [main] DEBUG org.springframework.web.client.RestTemplate - Setting request Accept header to [application/xml, text/xml, application/json, application/*+xml, application/*+json]
15:45:58.564 [main] DEBUG org.apache.http.client.protocol.RequestAddCookies - CookieSpec selected: default
15:45:58.593 [main] DEBUG org.apache.http.client.protocol.RequestAuthCache - Auth cache not set in the context
15:45:58.597 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection request: [route: {}->http://localhost:8080][total kept alive: 0; route allocated: 0 of 2; total allocated: 0 of 20]
15:45:58.638 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {}->http://localhost:8080][total kept alive: 0; route allocated: 1 of 2; total allocated: 1 of 20]
15:45:58.643 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Opening connection {}->http://localhost:8080
15:45:58.654 [main] DEBUG org.apache.http.impl.conn.DefaultHttpClientConnectionOperator - Connecting to localhost/127.0.0.1:8080
15:45:58.662 [main] DEBUG org.apache.http.impl.conn.DefaultHttpClientConnectionOperator - Connection established 127.0.0.1:25590<->127.0.0.1:8080
15:45:58.663 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Executing request GET /json/user HTTP/1.1
15:45:58.663 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED
15:45:58.665 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /json/user HTTP/1.1
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept: application/xml, text/xml, application/json, application/*+xml, application/*+json
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: localhost:8080
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: Keep-Alive
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.5 (Java/1.8.0_91)
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "GET /json/user HTTP/1.1[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept: application/xml, text/xml, application/json, application/*+xml, application/*+json[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: localhost:8080[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.5 (Java/1.8.0_91)[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "X-Application-Context: application[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Type: application/json;charset=UTF-8[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Transfer-Encoding: chunked[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Date: Mon, 05 Feb 2018 07:45:59 GMT[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "1c6[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "{"name":"Json","age":20,"links":[{"rel":"self","href":"http://localhost:8080/json/user/set/name?name=Json","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null},{"rel":"self","href":"http://localhost:8080/json/user/set/age?age=20","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null},{"rel":"self","href":"http://localhost:8080/xml/user","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}[\r][\n]"
15:45:59.072 [main] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200
15:45:59.072 [main] DEBUG org.apache.http.headers - http-outgoing-0 << X-Application-Context: application
15:45:59.072 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: application/json;charset=UTF-8
15:45:59.072 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Transfer-Encoding: chunked
15:45:59.072 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Mon, 05 Feb 2018 07:45:59 GMT
15:45:59.086 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Connection can be kept alive indefinitely
15:45:59.094 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:8080/json/user" resulted in 200 ()
15:45:59.096 [main] DEBUG org.springframework.web.client.RestTemplate - Reading [class com.segmentfault.springbootlesson3.pojo.User] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@15c2241]
15:45:59.121 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "0[\r][\n]"
15:45:59.121 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
15:45:59.121 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://localhost:8080] can be kept alive indefinitely
15:45:59.122 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
15:45:59.122 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://localhost:8080][total kept alive: 1; route allocated: 1 of 2; total allocated: 1 of 20]
links: [<http://localhost:8080/json/user/set/name?name=Json>;rel="self", <http://localhost:8080/json/user/set/age?age=20>;rel="self", <http://localhost:8080/xml/user>;rel="self"]
Process finished with exit code 0
這個在後面的Spring Cloud的負載均衡中會用到,修改底層的訪問方式。
5.Q&A
rpc框架難點:負載均衡,註冊服務發現服務,效能問題
1.架構屬性
效能
可伸縮性
統一結構簡化性:如URI,RequestHeader,RequestBody等等
元件可修改性:
元件通訊可見性
元件可移植性
可靠性
2.架構約束
C/S架構
無狀態
可快取:兩個方面,服務端和客戶端
分層系統
按需程式碼
統一介面
3.統一幾口(Uniform Interface)
資源識別:URI
資源操作:GET(取資源),POST(非冪等性,實現的時候需要實現冪等性),PUT(更新資源),DELETE
自描述資訊:ContentType,MIME-Type,Meida-Type
超媒體
--REST服務端實踐
1.Spring Boot Rest
核心介面:
定義相關
@Controller
@RestController
對映相關
@RequestMapping
@PathVariable 路徑變數
請求相關
@RequestParam
@RequestHeader
@CookieValue
RequestEntity
響應相關
@ResponseBody
ResponseEntity
實踐:
在start.spring.io上建立專案,並Generate Project
Group:com.segmentfault
Artifact:spring-boot-lesson-3
Dependencies:Web,Actuator
將匯出的工程匯入到IDEA中。
rest支援多種返回格式,不僅限於JSON,XML,HTML,還可以自定義的格式
例子1:HTML例子
@Controller
public class RestDemoController {
//HTML
@RequestMapping("/html/demo")
@ResponseBody
public String htmlCode(){
return "<html><body><h1>hello</h1></body></html>";
}
}
訪問http://localhost:8080/html/demo地址,可以看到返回的html頁面資訊
例子2:多種Mapping語法
//HTML,可以對映到多個方法上
//@RequestMapping(value={"/html/demo","/html/demo2"},method = {RequestMethod.GET})
@GetMapping(path={"/html/demo3"}) //相對於 @RequestMapping(value={"/html/demo3"},method = {RequestMethod.GET})
@ResponseBody
public String htmlCode(){
return "<html><body><h1>hello</h1></body></html>";
}
@RequestMapping和@GetMapping是會衝突的,兩者不能共存
重新啟動SpringBoot
Mapped "{[/html/demo3],methods=[GET]}" onto public java.lang.String com.segmentfault.springbootlesson3.controller.RestDemoController.htmlCode()
例子3: @RestController
@RestController 相對於 @Controller和@ResponseBody的組合
//@Controller
@RestController
public class RestDemoController {
//HTML,可以對映到多個方法上
//@RequestMapping(value={"/html/demo","/html/demo2"},method = {RequestMethod.GET})
@GetMapping(path={"/html/demo3"})
// @PostMapping(path={"/html/demo2"})
// @ResponseBody
public String htmlCode(){
return "<html><body><h1>hello</h1></body></html>";
}
}
此時訪問網站,可以看到html返回的頁面效果。
如果去除@RestController,改成@Controller,此時因為沒有@ResponseBody,所以:
return "<html><body><h1>hello</h1></body></html>";時,springboot就去template目錄中找對應的地址找模板了,但是因為找不到,所以在頁面上返回:Whitelabel Error Page
例子4:@PathVariable
//@Controller
@RestController
public class RestDemoController {
//HTML,可以對映到多個方法上
//@RequestMapping(value={"/html/demo","/html/demo2"},method = {RequestMethod.GET})
@GetMapping(path={"/html/demo3"})
// @PostMapping(path={"/html/demo2"})
// @ResponseBody
public String htmlCode(){
return "<html><body><h1>hello</h1></body></html>";
}
@GetMapping(path={"/html/demo/{messages}"})
public String htmlCode(@PathVariable String messages){
return "<html><body>"+messages+"</body></html>";
}
}
啟動專案,在瀏覽器中輸入地址:http://localhost:8080/html/demo/hellohello
那麼在頁面上顯示了hellohello
例子5:@RequestParam
@GetMapping(path={"/html/demo/param"})
public String htmlParam(@RequestParam String param){
return "<html><body>"+param+"</body></html>";
}
在瀏覽器中輸入:http://localhost:8080/html/demo/param?param=helloworld
瀏覽器返回結果:helloworld
方法可以進一步修改為:
@GetMapping(path={"/html/demo/param"})
//指定該引數不是必須的
public String htmlParam(@RequestParam(name="p",required=false,defaultValue="abc") String param){
return "<html><body>"+param+"</body></html>";
}
name="p",required=false,name是指引數的名稱,required是設定引數是否必需,defaultValue是設定預設值
當輸入http://localhost:8080/html/demo/param沒有攜帶引數的時候,就會返回abc
當然,我們也是可以使用Servlet來獲取對應的parameter的:
@GetMapping(path={"/html/demo/param"})
//指定該引數不是必須的
public String htmlParam(@RequestParam(name="p",required=false,defaultValue="Empty") String param, HttpServletRequest request){
String param2 = request.getParameter("p");
return "<html><body>requestparam:"+param+",request:"+param2+"</body></html>";
}
自動轉換功能:當我們設定了@ReqeustParam的引數型別為Integer的時候,那麼傳入的引數就會自動轉換型別
@GetMapping(path={"/html/demo/param"})
//指定該引數不是必須的
public String htmlParam(@RequestParam(name="p",required=false,defaultValue="Empty") String param, HttpServletRequest request,
@RequestParam(name="age",required = false,defaultValue = "0") Integer age){
String param2 = request.getParameter("p");
System.out.println(age+1);
return "<html><body>requestparam:"+param+",request:"+param2+"</body></html>";
}
輸入地址:http://localhost:8080/html/demo/param?age=100
在Console中自動列印了101,說明引數型別字串已經自動轉換未Integer型別了
例子6:@RequestHeader
@GetMapping(path={"/html/demo/header"})
//指定該引數不是必須的
public String htmlHeader(@RequestHeader(value="Accept") String acceptHeader, HttpServletRequest request){
return "<html><body>accept header:"+acceptHeader+"</body></html>";
}
在瀏覽器中輸入:http://localhost:8080/html/demo/header
返回結果:accept header:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
可以看到請求頭彙總的Accept
例子7:RequestEntity
可以獲取到請求頭和請求body的全部資訊,相比於Requestheader更多的資訊:
@GetMapping(path={"/html/demo/response/entity"})
//ResponseEntity是
public ResponseEntity<String> htmlResponseEntity(RequestEntity request){
System.out.println(request.getUrl());
HttpHeaders header = new HttpHeaders();
header.add("myheader","helloworld");
ResponseEntity entity = new ResponseEntity("<html><body>html ResponseEntity</body></html>",header, HttpStatus.OK);
return entity;
}
訪問後結果:http://localhost:8080/html/demo/response/entity
http://localhost:8080/html/demo/response/entity
也可以指定獲取請求頭中的資訊:
request.getHeaders().get("Accept")
執行結果:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8]
例子8:ResponseEntity
ResponseEntity可以管理頭和body,而ResponseBody只能管理Body部分
@GetMapping(path={"/html/demo/response/entity"})
//ResponseEntity是泛型
public ResponseEntity<String> htmlResponseEntity(){
return ResponseEntity.ok("<html><body>html ResponseEntity</body></html>");
}
其中ResponseEntity.ok指定了返回的status
同樣的,也可以自定義返回的Header
@GetMapping(path={"/html/demo/response/entity"})
//ResponseEntity是
public ResponseEntity<String> htmlResponseEntity(){
HttpHeaders header = new HttpHeaders();
header.add("myheader","helloworld");
ResponseEntity entity = new ResponseEntity("<html><body>html ResponseEntity</body></html>",header, HttpStatus.OK);
return entity;
}
訪問該地址:http://localhost:8080/html/demo/response/entity
頁面上顯示html ResponseEntity
開啟瀏覽器Network檢視請求頭資訊
myheader:helloworld
例子9:Json格式的例子
建立User類
package com.segmentfault.springbootlesson3.pojo;
public class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
建立JsonDemoController:
@RestController
public class JsonDemoController {
@GetMapping("/json/user")
public User JsonMethod(){
User user = new User();
user.setName("xiaoming");
user.setAge(10);
return user;
}
}
訪問地址:
http://localhost:8080/json/user
返回結果:
{"name":"xiaoming","age":10}
那麼我們如何改成xml格式的呢?
配置pom
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
重啟後,訪問地址:http://localhost:8080/json/user
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<User>
<name>xiaoming</name>
<age>10</age>
</User>
因為這個時候系統訪問時,會先精確匹配是否有xml,如果有,優先匹配返回xml格式,其次才會考慮json格式
可以在請求頭中找到Accept:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
我們也可以指定某個Controller的某個方法的返回格式是某種格式,如Json格式等,通過produces=MediaType.xxx來指定
@RestController
public class JsonDemoController {
@GetMapping(value="/json/user",produces= MediaType.APPLICATION_JSON_VALUE)
public User JsonMethod(){
User user = new User();
user.setName("xiaoming");
user.setAge(10);
return user;
}
}
此時再次訪問同樣的地址:返回結果
{"name":"xiaoming","age":10}
例子10:建立一個XMLDemoController
@RestController
public class XMLDemoController {
@GetMapping(value="/xml/user",produces= MediaType.APPLICATION_XML_VALUE)
public User JsonMethod(){
User user = new User();
user.setName("xiaoming XML");
user.setAge(30);
return user;
}
}
啟動過程中,可以看列印控制檯中的資訊,可以看到對映資訊
Mapped "{[/json/user],methods=[GET],produces=[application/json]}"
Mapped "{[/xml/user],methods=[GET],produces=[application/xml]}"
11)我們可以手動指定消費者的處理型別,如我們上面看到的Accept中支援處理xml格式等型別,我們也可以要求消費者的處理型別
例子11:
@RestController
public class XMLDemoController {
@GetMapping(value="/xml/user",
produces = MediaType.APPLICATION_XML_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE
)
public User JsonMethod(){
User user = new User();
user.setName("xiaoming XML");
user.setAge(30);
return user;
}
}
此時瀏覽器訪問地址localhost:8080/xml/user,提示不支援型別,因為Accept中沒有支援Json型別的處理
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Feb 05 11:25:21 CST 2018
There was an unexpected error (type=Unsupported Media Type, status=415).
Content type 'null' not supported
那麼我們如果需要讓瀏覽器處理,我們需要在程式中配置consumes中,加上瀏覽器支援的型別
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
修改後的程式碼:
@RestController
public class XMLDemoController {
@GetMapping(value="/xml/user",
produces = MediaType.APPLICATION_XML_VALUE,
consumes = {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_XML_VALUE,MediaType.TEXT_HTML_VALUE,"application/xhtml+xml"}
)
public User JsonMethod(){
User user = new User();
user.setName("xiaoming XML");
user.setAge(30);
return user;
}
}
2.HATEOAS
我們少了一個發現服務的入口!!!外面的人不知道我們釋出了服務!!!
HATEOAS提供了服務發現的功能
找到spring.io中的Spring HATEOAS,找到引包的配置
<dependencies>
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>0.24.0.RELEASE</version>
</dependency>
</dependencies>
引入成功後,就可以使用HATEOAS了。
參考:https://docs.spring.io/spring-hateoas/docs/0.24.0.RELEASE/reference/html/#fundamentals.links
按Alt+Enter,選擇Import static method,注入mvc相關的依賴
package com.segmentfault.springbootlesson3.controller;
import com.segmentfault.springbootlesson3.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.awt.*;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
@RestController
public class JsonDemoController {
@Bean
public User currentUser(){
User user = new User();
user.setName("Json");
user.setAge(20);
return user;
}
@Autowired
@Qualifier(value="currentUser") //等於currentUser
public User user;
@GetMapping(path="/json/user",produces= MediaType.APPLICATION_JSON_VALUE)
public User user(){
return user;
}
//setName
@GetMapping(path="/json/user/set/name",
produces = MediaType.APPLICATION_JSON_VALUE
)
public User setUserName(@RequestParam String name){
user.setName(name);
//暴露服務介面
user.add(linkTo(methodOn(JsonDemoController.class).setUserName(name)).withSelfRel());
return user;
}
//setAge
@GetMapping(path="/json/user/set/age",
produces = MediaType.APPLICATION_JSON_VALUE
)
public User setUserAge(@RequestParam Integer age){
user.setAge(age);
user.add(linkTo(methodOn(JsonDemoController.class).setUserAge(age)).withSelfRel());
return user;
}
}
此時訪問:http://localhost:8080/json/user
返回:{"name":"Json","age":20,"links":[]}
訪問:http://localhost:8080/json/user/set/name?name=Hello123
{"name":"Hello123","age":20,"links":[{"rel":"self","href":"http://localhost:8080/json/user/set/name?name=Hello123","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}
此時再次訪問:http://localhost:8080/json/user ,此時的user就有狀態了
{"name":"Hello123","age":20,"links":[{"rel":"self","href":"http://localhost:8080/json/user/set/name?name=Hello123","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}
此時訪問setAge:http://localhost:8080/json/user/set/age?age=120
{"name":"Hello123","age":120,"links":[{"rel":"self","href":"http://localhost:8080/json/user/set/name?name=Hello123","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null},{"rel":"self","href":"http://localhost:8080/json/user/set/age?age=120","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}
可以看到user上有兩個url了
上面的註冊服務介面可以開始的時候就放到User獲取的時候進行注入
修改:
@GetMapping(path="/json/user",produces= MediaType.APPLICATION_JSON_VALUE)
public User user(){
//暴露服務介面
user.add(linkTo(methodOn(JsonDemoController.class).setUserName(user.getName())).withSelfRel());
user.add(linkTo(methodOn(JsonDemoController.class).setUserAge(user.getAge())).withSelfRel());
return user;
}
去除方法中的暴露服務介面的方法
這樣,我們在啟動的時候,就可以獲取到服務的入口。告訴了我們連結的相關操作
重啟專案,輸入:http://localhost:8080/json/user
{"name":"Json","age":20,"links":[{"rel":"self","href":"http://localhost:8080/json/user/set/name?name=Json","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null},{"rel":"self","href":"http://localhost:8080/json/user/set/age?age=20","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}
3.REST文件生成
1)spring提供了Spring REST Docs,可以生成rest介面文件
2)swagger,動態生成!
3)spring boot/mappings endpoint, spring boot釋出後提供的功能
檢視的時候,需要配置,因為高版本的springboot對許可權的認證比較嚴格
在EndpointProperties中可以看到:
public class EndpointProperties {
private static final String ENDPOINTS_ENABLED_PROPERTY = "endpoints.enabled";
private static final String ENDPOINTS_SENSITIVE_PROPERTY = "endpoints.sensitive";
。。。
在application.properties中配置
endpoints.enabled=true
endpoints.sensitive = false
重啟專案後,輸入:http://localhost:8080/mappings
可以看到專案中所有的地址對映
{[/xml/user],methods=[GET],consumes=[text/html || application/xhtml+xml || application/xml],produces=[application/xml]}
bean "requestMappingHandlerMapping"
method
{[/json/user/set/name],methods=[GET],produces=[application/json]}
bean "requestMappingHandlerMapping"
method
可以看到,基本的資訊都已經提供了,只是引數的名稱和型別沒有提供
4.REST客戶端實踐
1)Web瀏覽器
2)Apache HttpClient
在user中註冊xmlDemoController中的返回xml格式的方法:
@RestController
public class XMLDemoController {
@GetMapping(value="/xml/user",
produces = MediaType.APPLICATION_XML_VALUE
)
public User JsonMethod(){
User user = new User();
user.setName("xiaoming XML");
user.setAge(30);
return user;
}
}
@GetMapping(path="/json/user",produces= MediaType.APPLICATION_JSON_VALUE)
public User user(){
//暴露服務介面
user.add(linkTo(methodOn(JsonDemoController.class).setUserName(user.getName())).withSelfRel());
user.add(linkTo(methodOn(JsonDemoController.class).setUserAge(user.getAge())).withSelfRel());
user.add(linkTo(methodOn(XMLDemoController.class).JsonMethod()).withSelfRel());
return user;
}
重啟程式:http://localhost:8080/xml/user
<User><name>xiaoming XML</name><age>30</age><links/></User>
3)Spring RestTemplate
我們再來看一下RestTemplate這個模板類,
查詢HttpComponentsClientHttpRequestFactory類,
其中一個建構函式:org.apache.http.impl.client.AbstractHttpClient的引數類
我們在search.maven.org中查詢這個類的原始碼,引用對應的maven配置
在pom.xml中加入引用:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
我們可整合Apache Client和HttpClient,適配底層的方法HttpClient
修改RestClient中的方法:
package com.segmentfault.springbootlesson3.client;
import com.segmentfault.springbootlesson3.pojo.User;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
public class RestClient {
public static void main(String[] args) {
HttpClientBuilder builder = HttpClientBuilder.create();
HttpClient httpClient = builder.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(factory);
// String result = restTemplate.getForObject("http://localhost:8080/json/user",String.class);
//執行反序列化
User user = restTemplate.getForObject("http://localhost:8080/json/user",User.class);
System.out.println(user);
}
}
此時再次啟動springboot,然後使用RestTemplate訪問底層介面
看控制檯列印:可以看到和之前的不同,列印出了底層的呼叫方式
"C:\Program Files (x86)\Java\jdk1.8.0_91\bin\java" -javaagent:E:\ideaIU\lib\idea_rt.jar=25583:E:\ideaIU\bin -Dfile.encoding=UTF-8 -classpath "C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\charsets.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\deploy.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\access-bridge-32.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\cldrdata.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\dnsns.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\jaccess.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\jfxrt.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\localedata.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\nashorn.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunec.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunjce_provider.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunmscapi.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunpkcs11.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\zipfs.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\javaws.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jfr.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jfxswt.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jsse.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\management-agent.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\plugin.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\rt.jar;E:\springboot\spring-boot-lesson-3\target\classes;E:\maven\localwarehouse\org\springframework\boot\spring-boot-starter-actuator\1.5.10.RELEASE\spring-boot-starter-actuator-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-starter\1.5.10.RELEASE\spring-boot-starter-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot\1.5.10.RELEASE\spring-boot-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-autoconfigure\1.5.10.RELEASE\spring-boot-autoconfigure-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-starter-logging\1.5.10.RELEASE\spring-boot-starter-logging-1.5.10.RELEASE.jar;E:\maven\localwarehouse\ch\qos\logback\logback-classic\1.1.11\logback-classic-1.1.11.jar;E:\maven\localwarehouse\ch\qos\logback\logback-core\1.1.11\logback-core-1.1.11.jar;E:\maven\localwarehouse\org\slf4j\jcl-over-slf4j\1.7.25\jcl-over-slf4j-1.7.25.jar;E:\maven\localwarehouse\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;E:\maven\localwarehouse\org\slf4j\log4j-over-slf4j\1.7.25\log4j-over-slf4j-1.7.25.jar;E:\maven\localwarehouse\org\yaml\snakeyaml\1.17\snakeyaml-1.17.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-actuator\1.5.10.RELEASE\spring-boot-actuator-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-starter-web\1.5.10.RELEASE\spring-boot-starter-web-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\springframework\boot\spring-boot-starter-tomcat\1.5.10.RELEASE\spring-boot-starter-tomcat-1.5.10.RELEASE.jar;E:\maven\localwarehouse\org\apache\tomcat\embed\tomcat-embed-core\8.5.27\tomcat-embed-core-8.5.27.jar;E:\maven\localwarehouse\org\apache\tomcat\tomcat-annotations-api\8.5.27\tomcat-annotations-api-8.5.27.jar;E:\maven\localwarehouse\org\apache\tomcat\embed\tomcat-embed-el\8.5.27\tomcat-embed-el-8.5.27.jar;E:\maven\localwarehouse\org\apache\tomcat\embed\tomcat-embed-websocket\8.5.27\tomcat-embed-websocket-8.5.27.jar;E:\maven\localwarehouse\org\hibernate\hibernate-validator\5.3.6.Final\hibernate-validator-5.3.6.Final.jar;E:\maven\localwarehouse\javax\validation\validation-api\1.1.0.Final\validation-api-1.1.0.Final.jar;E:\maven\localwarehouse\org\jboss\logging\jboss-logging\3.3.1.Final\jboss-logging-3.3.1.Final.jar;E:\maven\localwarehouse\com\fasterxml\classmate\1.3.4\classmate-1.3.4.jar;E:\maven\localwarehouse\com\fasterxml\jackson\core\jackson-databind\2.8.10\jackson-databind-2.8.10.jar;E:\maven\localwarehouse\org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-webmvc\4.3.14.RELEASE\spring-webmvc-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-expression\4.3.14.RELEASE\spring-expression-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-core\4.3.14.RELEASE\spring-core-4.3.14.RELEASE.jar;E:\maven\localwarehouse\com\fasterxml\jackson\dataformat\jackson-dataformat-xml\2.8.10\jackson-dataformat-xml-2.8.10.jar;E:\maven\localwarehouse\com\fasterxml\jackson\core\jackson-core\2.8.10\jackson-core-2.8.10.jar;E:\maven\localwarehouse\com\fasterxml\jackson\core\jackson-annotations\2.8.0\jackson-annotations-2.8.0.jar;E:\maven\localwarehouse\com\fasterxml\jackson\module\jackson-module-jaxb-annotations\2.8.10\jackson-module-jaxb-annotations-2.8.10.jar;E:\maven\localwarehouse\org\codehaus\woodstox\stax2-api\3.1.4\stax2-api-3.1.4.jar;E:\maven\localwarehouse\com\fasterxml\woodstox\woodstox-core\5.0.3\woodstox-core-5.0.3.jar;E:\maven\localwarehouse\org\springframework\hateoas\spring-hateoas\0.24.0.RELEASE\spring-hateoas-0.24.0.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-aop\4.3.14.RELEASE\spring-aop-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-beans\4.3.14.RELEASE\spring-beans-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\springframework\spring-context\4.3.14.RELEASE\spring-context-4.3.14.RELEASE.jar;E:\maven\localwarehouse\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;E:\maven\localwarehouse\org\apache\httpcomponents\httpclient\4.5.5\httpclient-4.5.5.jar;E:\maven\localwarehouse\org\apache\httpcomponents\httpcore\4.4.9\httpcore-4.4.9.jar;E:\maven\localwarehouse\commons-codec\commons-codec\1.10\commons-codec-1.10.jar" com.segmentfault.springbootlesson3.client.RestClient
15:45:58.371 [main] DEBUG org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:8080/json/user"
15:45:58.540 [main] DEBUG org.springframework.web.client.RestTemplate - Setting request Accept header to [application/xml, text/xml, application/json, application/*+xml, application/*+json]
15:45:58.564 [main] DEBUG org.apache.http.client.protocol.RequestAddCookies - CookieSpec selected: default
15:45:58.593 [main] DEBUG org.apache.http.client.protocol.RequestAuthCache - Auth cache not set in the context
15:45:58.597 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection request: [route: {}->http://localhost:8080][total kept alive: 0; route allocated: 0 of 2; total allocated: 0 of 20]
15:45:58.638 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {}->http://localhost:8080][total kept alive: 0; route allocated: 1 of 2; total allocated: 1 of 20]
15:45:58.643 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Opening connection {}->http://localhost:8080
15:45:58.654 [main] DEBUG org.apache.http.impl.conn.DefaultHttpClientConnectionOperator - Connecting to localhost/127.0.0.1:8080
15:45:58.662 [main] DEBUG org.apache.http.impl.conn.DefaultHttpClientConnectionOperator - Connection established 127.0.0.1:25590<->127.0.0.1:8080
15:45:58.663 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Executing request GET /json/user HTTP/1.1
15:45:58.663 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED
15:45:58.665 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /json/user HTTP/1.1
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept: application/xml, text/xml, application/json, application/*+xml, application/*+json
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: localhost:8080
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: Keep-Alive
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.5 (Java/1.8.0_91)
15:45:58.676 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "GET /json/user HTTP/1.1[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept: application/xml, text/xml, application/json, application/*+xml, application/*+json[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: localhost:8080[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.5 (Java/1.8.0_91)[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
15:45:58.676 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "X-Application-Context: application[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Type: application/json;charset=UTF-8[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Transfer-Encoding: chunked[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Date: Mon, 05 Feb 2018 07:45:59 GMT[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "1c6[\r][\n]"
15:45:59.066 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "{"name":"Json","age":20,"links":[{"rel":"self","href":"http://localhost:8080/json/user/set/name?name=Json","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null},{"rel":"self","href":"http://localhost:8080/json/user/set/age?age=20","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null},{"rel":"self","href":"http://localhost:8080/xml/user","hreflang":null,"media":null,"title":null,"type":null,"deprecation":null}]}[\r][\n]"
15:45:59.072 [main] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200
15:45:59.072 [main] DEBUG org.apache.http.headers - http-outgoing-0 << X-Application-Context: application
15:45:59.072 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: application/json;charset=UTF-8
15:45:59.072 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Transfer-Encoding: chunked
15:45:59.072 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Mon, 05 Feb 2018 07:45:59 GMT
15:45:59.086 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Connection can be kept alive indefinitely
15:45:59.094 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:8080/json/user" resulted in 200 ()
15:45:59.096 [main] DEBUG org.springframework.web.client.RestTemplate - Reading [class com.segmentfault.springbootlesson3.pojo.User] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@15c2241]
15:45:59.121 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "0[\r][\n]"
15:45:59.121 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
15:45:59.121 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://localhost:8080] can be kept alive indefinitely
15:45:59.122 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
15:45:59.122 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://localhost:8080][total kept alive: 1; route allocated: 1 of 2; total allocated: 1 of 20]
links: [<http://localhost:8080/json/user/set/name?name=Json>;rel="self", <http://localhost:8080/json/user/set/age?age=20>;rel="self", <http://localhost:8080/xml/user>;rel="self"]
Process finished with exit code 0
這個在後面的Spring Cloud的負載均衡中會用到,修改底層的訪問方式。
5.Q&A
rpc框架難點:負載均衡,註冊服務發現服務,效能問題
相關文章
- Spring Boot學習4:web篇(下)-Spring boot (Servlet,Jsp)學習Spring BootWebServletJS
- Spring Boot學習5:spring-boot web容器Spring BootWeb
- spring boot學習Spring Boot
- Spring Boot 學習Spring Boot
- Spring Boot學習6:Spring Boot JDBCSpring BootJDBC
- Spring Boot學習(一)——Spring Boot介紹Spring Boot
- Spring boot學習(三) Spring boot整合mybatisSpring BootMyBatis
- Spring Boot 學習筆記(3):MyBatisSpring Boot筆記MyBatis
- Spring boot學習(二) Spring boot基礎配置Spring Boot
- Spring boot學習(四)Spring boot整合DruidSpring BootUI
- Spring boot學習(一)開啟Spring boot之旅Spring Boot
- Spring Boot學習筆記:Spring Boot核心配置Spring Boot筆記
- Spring Boot 學習目錄Spring Boot
- Spring Boot 學習-基礎Spring Boot
- Spring boot學習(九)Spring boot配置郵件傳送Spring Boot
- Spring boot學習(八)Spring boot配置ehcache快取框架Spring Boot快取框架
- Spring boot學習(五)Spring boot整合Mybatis Generator以及PageHelperSpring BootMyBatis
- Spring Boot學習之---Spring Boot與檢索 下(十六)Spring Boot
- Spring Boot + JPA學習總結Spring Boot
- 什麼是Spring Boot?為什麼要學習Spring Boot?Spring Boot
- Spring Boot 學習資料 (轉載)Spring Boot
- Spring Boot 學習筆記(2):JDBCSpring Boot筆記JDBC
- Spring boot + Zookeeper + Dubbo學習筆記Springboot筆記
- Java Spring Boot 學習筆記(一)JavaSpring Boot筆記
- Spring Boot學習資料彙總Spring Boot
- 快速學習 Spring Boot 技術棧Spring Boot
- Spring boot學習(六)Spring boot實現AOP記錄操作日誌Spring Boot
- spring boot學習(4): 命令列啟動Spring Boot命令列
- Spring Boot 學習 (1): 初始化工程Spring Boot
- Spring Boot--日誌框架的學習Spring Boot框架
- spring boot學習4 多環境配置Spring Boot
- spring boot學習與踩坑記錄Spring Boot
- Spring Boot 學習筆記(1):快速上手Spring Boot筆記
- spring boot學習簡要筆記1Spring Boot筆記
- spring boot學習(3): SpringApplication 事件監聽Spring BootAPP事件
- Spring Boot學習筆記---Spring Boot 基礎及使用idea搭建專案Spring Boot筆記Idea
- spring boot學習(5): 程式exit code自定義Spring Boot
- Spring Boot 學習筆記(5):日誌配置Spring Boot筆記