--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框架难点:负载均衡,注册服务发现服务,性能问题