文章目录
RESTful开发风格
1.RESTful入门
在传统开发模式的应用中,前端写好静态的html页面交给后端开发,后端把html渲染或重定向到前端界面。这样前端页面看到的效果都是由后端控制的,也就是后端需要控制前端的展示,这样会出现很多弊端:
-
前后端耦合严重,前端会嵌入后端代码,导致代码混乱,可维护性差;
-
开发出的软件响应速度慢,质量差,用户体现差;
-
开发人员需要前后端兼顾,开发效率低下,开发周期变长;
-
与前端开发人员之间沟通成本高,前后端开发进度相互影响,从而大大降低开发效率。
前后端分离并不只是开发模式,也是web应用的一种架构模式。在开发阶段,前后端人员约定好数据交互接口,即可并行开发与测试。前端开发完成可以独自进行mock测试,后端也可以使用postman等接口测试工具进行测试,最后可进行功能联调测试。后端项目里面看不到页面(JSP|HTML),后端给前端提供接口,前端调用后端提供的REST风格接口就行,前端专注写页面(html|jsp)和渲染(JS|CSS|各种前端框架);后端专注写代码就行。 前后端分离的核心:后台提供数据,前端负责显示。
- 什么是 REST?
Representational State Transfer,翻译是“表现层状态转化”。可以总结为一句话:REST 是所有 Web 应用都应该遵守的架构设计指导原则。
面向资源是 REST 最明显的特征,对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。REST 要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。
- 什么是 RESTful API?
符合 REST 设计标准的 API,即 RESTful API。REST 架构设计,遵循的各项标准和准则,就是 HTTP 协议的表现,换句话说,HTTP 协议就是属于 REST 架构的设计模式。比如,无状态,请求-响应。
- 为什么要统一封装接口?
现在大多数项目采用前后分离的模式进行开发,统一返回方便前端进行开发和封装,以及出现时给出响应编码和信息。
-
RESTful最典型的特点:服务器端只返回(JSON/xml)格式的数据,通知要求返回的数据不包含任何与展现相关的内容,通常将基于RESTfull 规则开发的程序称为“前后端分离”。
-
Web服务器默认的只支持Post和Get这两种“只读”的请求方法,Http还支持其他类型的请求。
RESTful开发规范:
-
GET–>执行查询功能的URL
-
POST–>执行新增功能的URL
-
PUT–>执行修改功能的URL
-
DELETE–>执行删除请求的URL
2.RestController注解与路径变量
2.1 @ResponseBody
@Responsebody 注解表示该方法的返回的结果直接写入 HTTP 响应正文(ResponseBody)中,一般在异步获取数据时使用,通常是在使用 @RequestMapping 后,返回值通常解析为跳转路径,加上 @Responsebody 后返回结果不会被解析为跳转路径,而是直接写入HTTP 响应正文中。
**作用:**该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
**使用时机:**返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用。
2.2 @RestController=@Controller + @ResponseBody
@RestController=@Controller + @ResponseBody:
- @Controller 是一种特殊化的*@Component* 类。
- @Controller 习惯于和@RequestMapping绑定来使用,后者是用来指定路由映射的。
- @ResponseBody 是用来把返回对象自动序列化成HttpResponse的。
- @ResponseBody告诉控制器返回对象会被自动序列化成JSON,并且传回HttpResponse这个对象。
- @RequestBody把HttpRequest body映射成一个 transfer or domain object(DTO或者DO),把一个入境(inbound)的HttpRequest的body反序列化成一个Java对象。
- @RestController从Spring 4.0以后产生的,用来将json/xml数据发送到前台页面,而不是返回视图页面。@RestController加在类上面的注解,使得类里面的每个方法都将json/xml返回数据加返回到前台页面中。@Controller加在类上面的注解,使得类里面的每个方法都返回一个视图页面。
2.3 路径变量
路径变量:存放在URL中可变的数值。
- 如果URL为/POST/arcticle/1就是创建id值为1的文章,在id的位置上是可变的,这种变量就是路径变量,restful中经常使用路径变量;
- 只需要在@GetMapping(“/request”)注解中加上{},括号中放入路径变量就能实现,如:@PostMapping(“/request/{rid}”);
- 这样POST请求发送到URI上的时候就能通过rid获取数值,只要匹配到rid的数值就能自动注入到requestId中;
- @PathVariable注解是获取url中的参数,@RequestParam 和 @PathVariable 都是 springMVC的注解,都用于接收请求中的参数,@RequestParam 是从request里面直接拿取值,而 @PathVariable 则是从一个URI模板里面来填充,也就是RESTful风格,RESTful风格的请求中,使方法中的参数与路径变量相匹配,要使用@PathVariable注解。
2.4 简单请求与非简单请求
非简单请求发送过程:
- 首先,发送预检请求,如果服务器可以处理预检请求并返回数据,则浏览器会将“实际请求”发送给服务器。
- 如果服务器不能处理预检请求,则浏览器不会再次发送“实际请求”这样就可以减轻服务器的压力。
注意:简单请求与非简单请求二者的数据结构几乎一致,只是数据的内容不同,非简单请求需要使用表单内容过滤器,对put,delete额外处理,springMVC需要对非简单请求做过滤器配置,在web.xml文件中添加过滤器配置:
<!--内容过滤器对非简单请求进行支持-->
<filter>
<filter-name>formContentFilter</filter-name>
<filter-class>org.springframework.web.filter.FormContentFilter</filter-class>
</filter>
<!--内容过滤器对所有请求地址进行过滤-->
<filter-mapping>
<filter-name>formContentFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.5 JSON序列化
1.pom.xml添加Jackson依赖
<!--添加jackson相关依赖-->
<!--jackson 核心包-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.2</version>
</dependency>
<!--jackson数据绑定 实现jackson与目标对象的交互-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2</version>
</dependency>
<!--jackson注解依赖 简化开发-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.2</version>
</dependency>
jackson与fastjson有什么区别?
Jackson和Fastjson都是Json库,Jackson在国外比较流行,Fastjson在国内用到比较多。Fastjson是由阿里巴巴开源的Json解析库,主要是速度快,为了提高速度在规范兼容性方面做了一些妥协。而Jackson历史悠久,可扩展性强。
使用jackson,spring是则么知道用它来进行序列化的?
在Spring中使用@ResponseBody和@RequestBody注解是会默认自动使用jackson处理json的序列化和反序列化。
注意:Jackson 2.9版本之前有系统漏洞,所以使用时一定要使用2.9以后的版本。
示例代码:
1.RestfulController:对象序列化与集合对象序列化
package com.imooc.restful.controller;
import com.imooc.restful.entity.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/restful")
public class RestfulController {
@GetMapping("/request")
//@ResponseBody
public String doGetRequest(){
return "{\"message\":\"返回查询结果\"}";
}
// POST /article/1
// POST /restful/request/100
@PostMapping("/request/{rid}")
//@ResponseBody
public String doPostRequest(@PathVariable("rid") Integer requestId, Person person){
System.out.println(person.getName() + ":" + person.getAge());
return "{\"message\":\"数据新建成功\",\"id\":" + requestId + "}";
}
@PutMapping("/request")
//@ResponseBody
public String doPutRequest(Person person){
System.out.println(person.getName() + ":" + person.getAge());
return "{\"message\":\"数据更新成功\"}";
}
@DeleteMapping("/request")
//@ResponseBody
public String doDeleteRequest(){
return "{\"message\":\"数据删除成功\"}";
}
//1.对象序列化
@GetMapping("/person")
public Person findByPersonId(Integer id){
Person p = new Person();
if(id==1){
p.setName("lily");
p.setAge(23);
}else if(id==2){
p.setName("smith");
p.setAge(22);
}
return p;
}
//2.集合对象序列化
@GetMapping("/persons")
public List<Person> findPersons(){
List list = new ArrayList();
Person p1 = new Person();
p1.setName("lily");
p1.setAge(23);
p1.setBirthday(new Date());
Person p2 = new Person();
p2.setName("smith");
p2.setAge(22);
p2.setBirthday(new Date());
list.add(p1);
list.add(p2);
return list;
}
}
2.Person实体类:
package com.imooc.restful.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
public class Person {
private String name;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss" , timezone = "GMT+8")
private Date birthday;
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;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
3.client.html:客户端使用传来的JSON数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>RESTful实验室</title>
<script src="jquery-3.3.1.min.js"></script>
<script>
$(function(){
$("#btnGet").click(function () {
$.ajax({
url : "/restful/request",
type : "get" ,
dataType : "json" ,
success : function(json){
$("#message").text(json.message);
}
})
});
})
$(function(){
$("#btnPost").click(function () {
$.ajax({
url : "/restful/request/100",
type : "post" ,
data : "name=lily&age=23",
dataType : "json" ,
success : function(json){
$("#message").text(json.message+":"+json.id);
}
})
});
})
$(function(){
$("#btnPut").click(function () {
$.ajax({
url : "/restful/request",
type : "put" ,
data : "name=lily&age=23",
dataType : "json" ,
success : function(json){
$("#message").text(json.message);
}
})
});
})
$(function(){
$("#btnDelete").click(function () {
$.ajax({
url : "/restful/request",
type : "delete" ,
dataType : "json" ,
success : function(json){
$("#message").text(json.message);
}
})
});
})
$(function () {
$("#btnPersons").click(function () {
$.ajax({
url : "http://localhost/restful/persons",
type : "get",
dataType : "json",
success : function(json){
console.info(json);
for(var i = 0 ; i < json.length;i++){
var p = json[i];
$("#divPersons").append("<h2>" + p.name + "-" + p.age + "-" + p.birthday+ "</h2>")
}
}
})
})
})
</script>
</head>
<body>
<input type="button" id="btnGet" value="发送Get请求">
<input type="button" id="btnPost" value="发送Post请求">
<input type="button" id="btnPut" value="发送Put请求">
<input type="button" id="btnDelete" value="发送Delete请求">
<h1 id="message"></h1>
<hr/>
<input type="button" id="btnPersons" value="查询所有人员">
<div id="divPersons"></div>
</body>
</html>
4.结果示例:查询所有人员
5.JSON在时间处理上不是很好:JSON直接对当前时间进行输出是一长串数字,该数字是1970年到当前时间的毫秒数。Jackson对这种情况有相应的注解来进行输出,json默认处理时间按照个格林尼治时间计算,中国需要+8个时区, @JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss” , timezone = “GMT+8”)。
JSON直接对当前时间进行输出是一长串数字:
输出准确时间:
3.跨域问题
3.1 浏览器同源策略
3.2 Spring MVC跨域访问:
示例代码:将相同的项目拷贝,修改另一个的端口号,通过8080端口访问80端口的url,在80端口中增加@CrossOrigin
package com.imooc.restful.controller;
import com.imooc.restful.entity.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/restful")
@CrossOrigin(origins = {"http://localhost:8080","http://www.imooc.com"})
@CrossOrigin(origins = "*",maxAge = 3600)
public class RestfulController {
@GetMapping("/request")
//@ResponseBody
public String doGetRequest(){
return "{\"message\":\"返回查询结果\"}";
}
// POST /article/1
// POST /restful/request/100
@PostMapping("/request/{rid}")
//@ResponseBody
public String doPostRequest(@PathVariable("rid") Integer requestId, Person person){
System.out.println(person.getName() + ":" + person.getAge());
return "{\"message\":\"数据新建成功\",\"id\":" + requestId + "}";
}
@PutMapping("/request")
//@ResponseBody
public String doPutRequest(Person person){
System.out.println(person.getName() + ":" + person.getAge());
return "{\"message\":\"数据更新成功\"}";
}
@DeleteMapping("/request")
//@ResponseBody
public String doDeleteRequest(){
return "{\"message\":\"数据删除成功\"}";
}
@GetMapping("/person")
public Person findByPersonId(Integer id){
Person p = new Person();
if(id==1){
p.setName("lily");
p.setAge(23);
}else if(id==2){
p.setName("smith");
p.setAge(22);
}
return p;
}
@GetMapping("/persons")
public List<Person> findPersons(){
List list = new ArrayList();
Person p1 = new Person();
p1.setName("lily");
p1.setAge(23);
p1.setBirthday(new Date());
Person p2 = new Person();
p2.setName("smith");
p2.setAge(22);
p2.setBirthday(new Date());
list.add(p1);
list.add(p2);
return list;
}
}
注意:@CrossOrigin(origins = “*”,maxAge = 3600)中maxAge属性设置后,浏览器会对“预检请求”的结果进行缓存,缓存有效期就是maxAge的标签值。有效期内,可以直接向服务器发送“非简单请求”。预检缓存的处理结果是缓存在浏览器中的,在服务器端是没有任何状态,也不缓存任何数据的。
3.3 CORS全局配置
Spring MVC全局跨域配置,使得项目配置中的URL都支持跨域访问。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mv="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.imooc.restful"/>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!-- response.setContentType("text/html;charset=utf-8") -->
<value>text/html;charset=utf-8</value>
<value>application/json;charset=utf-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<mvc:default-servlet-handler/>
<mvc:cors>
<mvc:mapping path="/restful/**"
allowed-origins="http://localhost:8080,http://www.imooc.com"
max-age="3600"/>
</mvc:cors>
</beans>
注解跨域和全局跨域配置在什么场景下使用呢?
- 如果当前应用是专用于webAPI 也就是只对外提供web数据服务时,此时就需要使用mvc:cors标签进行全局配置;
- 如果只是某些Controller需要对外暴露服务,就推荐使用注解;
- 如果即配置了全局跨域设置,又配置了注解跨域设置,则以注解跨域规定范围为准;