RESTful开发风格简介

说明:

(1) 本篇博客主要内容:【为什么会有RESTful开发风格】,【RESTful开发风格简介】;


零:传统的【基于MVC模式开发web应用】的问题:为什么有RESTful开发风格?

说明:

(1)传统的【基于MVC模式开发 web应用】:阐述: 上面的开发模式是典型的B/S架构(Browser/Server,浏览器/服务器模式);

● 上面的开发模式很容易理解;也是我们以前经常经常遇到的;

● 在上面的模式中,客户端必须是支持HTML的浏览器(如谷歌浏览器、IE浏览器等);

(2)传统的【基于MVC模式开发 web应用】:问题: 但是,还有很多客户端不是【支持HTML的浏览器】;

● 但是,目前互联网发展呈多元化的趋势,除了像谷歌浏览器这种B/S(Browser/Server,浏览器/服务器模式)结构下的传统客户端;还有如微信小程序/app或者其他各种各样的客户端,而对于这种客户端是不支持HTML的;

● 对于这种不支持HTML的客户端,我们也希望这种客户端也能和后端通信;

● 为此,一种全新的开发理念产生,即RESTful开发风格;


一:REST与RESTful简介;

1.REST开发理念;

REST:全称Representational State Transfer:表现层状态转换;

● 在web环境下,要想获取某个图片/JS/网页等资源的时候,需要以URL的形式来进行表现;即我们访问一个图片的url,那么返回的资源就是一张图片;如果我们访问一个css的url,那么返回的资源就是一个css;

● 上面的【在web环境下,以url的方式进行资源的传递】,这就是REST提供的一种设计理念;(这是一种理念,不是具体的实现)

2. 基于REST提供的理念,我们派生出了一种开发风格(或称规则):RESTful;(重要)

(1) RESTful是REST理念下的一种开发风格,一种具体的开发规则;

● RESTful开发风格中,客户端不再只是【支持HTML的浏览器】了,还有如【iPhone或者Android中运行的小程序和App】;

(2) 客户端和服务器之间如何交互?

● 服务器返回的数据,要求【不包含任何与展现相关的内容】;

● 当这些数据被送回到客户端后,这些数据由客户端进行渲染和展现:比如【客户端是PC端的浏览器时,客户端会以表格的形式来展现这些数据】,【客户端是iPhone或者Android的移动端的小程序的话,客户端可能以滑动列表的形式来展现数据】;总之,客户端如何展现接收到的数据,是客户端自己的事情了;而作为服务器,不用关心客户端是什么,服务器只管产生数据就行了。

● 这样做的好处是:开发服务器的后端工程师,只用关心数据,不用关心展现;而在前端那边,可以是小程序工程师、前端工程师或者App工程师,这些前端工程师不用关心后台是如何产生数据的,只需要得到数据后解析并展现就可以了;

● 此时,前端工程师和后端工程师,可以同步开发,只要约定好出传输数据的格式和url就可以了;

● 基于RESTful风格开发的程序,又称为前后端分离;

3.RESTful开发规范;

(1) 所有的资源,都是以url作为用户交互的入口:我们访问图片、css、某个页面、后台程序时,都要以url进行交互;

(2) http发送请求时:

● 在web环境下,只支持GET和POST请求;这也是我们以前遇到过的;

● REST定义的语义规范:【GET:查询操作】【POST:新增操作】【PUT:更新操作】【DELETE:删除操作】;

● 即,在向服务器发送请求时,即使是同一个url,如果请求方式不同,那么在服务器端的处理方式也是不一样的;

(PS:其实这样也挺好的,严谨即自由。)

(3) 服务器返回的数据,一般是xml或者JSON数据;(JSON更方便,建议使用。自己以前和同事交互接口的时候,自己返回的也是JSON数据)

(4)RESTful只是一种开发风格,其不是一种新技术;只要满足上面三个规范,都可以看做是Restful风格的程序;

4.RESTful命名要求:主要是URI的命名规范;

(2) uri必须要有明确的语义:

● 通过uri就能大概猜到这个uri的功能是什么;

● RESTful可以把id号附加在uri中:上面的意思是:向服务器查询id=1的student;

(3) uri中出现的单词,需要是名词;

● 前面已经标识了,这是一个POST请求,POST请求本身就代表了新增的意思,所以“createArticle”的create就是多余的;

(4) RESTful的uri应该满足扁平化规则;

● RESTful语法上,是支持多级uri的;但是在实际开发时,如果uri层级过多,会导致uri管理混乱、不易维护;

● 如果uri超过两级,那么就可以将uri中的id参数化;

● 【GET/articles/author?id=1】:这是两级uri,那么这个uri是查询articles还是查询author?:RESTful所代表的资源是由uri中最后一个名词决定的,所以上面查询的是author;

(5) RESTful建议在名词上区分单复数;

● 如【GET /articles?au=lily】:一般查询操作,其查询结果一般是多条,所以这儿的articles使用了复数形式;

● 如【DELETE /article/1】:一般如新增、修改、删除操作,往往是处理一个对象;所以,这儿使用的是article单数形式;

● 这样做的好处:如前端看到后端给的uri是【GET/articles?au=lily】,那么前端工程师会知道后端返回的数据大概率是一个JSON数组;如前端看到后端给的uri是【GET/article?au=lily】,那么前端工程师会知道后端返回的数据大概率是单个JSON字符串;

RESTful开发第一个项目

说明:

(1) 本篇博客通过一个简单的案例,开发第一个RESTful风格的项目;

(2) 本篇博客没什么难点,只是创建一个项目,为后面的演示作准备;同时,初步的演示了RESTful开发风格的程序;


一:准备一个项目;

1.创建一个【maven + WebApp】项目;

本部分可以参考【使用IDEA创建【maven + WebApp】项目】;

2.Spring MVC环境配置;

本部分可以参考【Spring MVC环境配置】

3.【pom.xml,web.xml,applicationContext.xml】,三个关键文件的内容;

其中的pom.xml、web.xml和applicationContext.xml的内容如下:

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>com.imooc</groupId>
     <artifactId>restful</artifactId>
     <version>1.0-SNAPSHOT</version>
 
     <repositories>
         <repository>
             <id>aliyun</id>
             <name>aliyun</name>
             <url>https://maven.aliyun.com/repository/public</url>
         </repository>
     </repositories>
 
     <dependencies>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-webmvc</artifactId>
             <version>5.1.9.RELEASE</version>
         </dependency>
     </dependencies>
 
 </project>

说明:

(1) 设置了国内阿里仓库;引入了【spring-webmvc】模块;


<?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
 
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-
class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:applicationContext.xml</param-value>
            </init-param>
            <load-on-startup>0</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
 
        <filter>
            <filter-name>characterFilter</filter-name>
            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <param-name>encoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>characterFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
 
    </web-app>

说明:

(1) 配置了DispatcherServlet;配置了【CharacterEncodingFilter过滤】来解决POST请求的中文乱码问题;


applicationContext.xml:

<?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"></context:component-scan>
 
        <mvc:annotation-driven>
            <mvc:message-converters>
                <bean
class="org.springframework.http.converter.StringHttpMessageConverter">
                    <property name="supportedMediaTypes">
                        <list>
                            <value>text/html;charset=utf-8</value>
                        </list>
                    </property>
                </bean>
            </mvc:message-converters>
        </mvc:annotation-driven>
        <mvc:default-servlet-handler/>
 
    </beans>

说明:

(1) context:component-scan:开启组件扫描; mvc:annotation-driven/:启动Spring MVC开发模式; mvc:default-servlet-handler/:将静态资源排除;

(2) 通过【StringHttpMessageConverter】转换器,来解决响应的中文乱码问题;


二:基于【RESTful风格】开发一个案例;

RestfulController类:

package com.imooc.restful.controller;
 
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 
 @Controller
 @RequestMapping("/restful")
 public class RestfulController {
     @GetMapping("/request")
     @ResponseBody
     public String doGetRequest() {
         return "{\"message\":\"客户端返回的查询结果\"}";
     }
 
 }

声明:上面return的写错了,少了{}大括号;特此说明,下面的几张图就不改了;


说明:

(1) 上面就是一个标准的RESTful风格的程序;

(2) 这段程序,我们以前也遇到过啊;为什么说,这就就是RESTful风格的程序了?:

一个老生常淡的注意项:启动Tomcat服务器前,记得设置Artifacts把新引入的jar包引入到发布中去;

启动Tomcat,观察结果:

说明:

(1) 在这个过程中,遇到了【Servlet[springmvc]的Servlet.init()异常】,具体解决策略见【Servlet[springmvc]的Servlet.init()引发异常;】;

(2) 上面后端返回的数据是【纯粹的JSON数据】,没有任何与展现相关的内容。

● 如果我们希望【后端返回的数据】被成功的显示,需要客户端的支持;

● 客户端可以是【一个HTML页面】,也可以是【一个App】,也可以是【微信小程序】;

(3) 而,【HTML页面】、【App】、【微信小程序】这些不同的客户端,都提供了和url进行交互的功能;

● 【HTML页面】在通过url得到后端返回的JSON数据时:Ajax技术就HTML使用的技术;(Ajax以前也接触过很多次,自己也总结过);

下篇博客就通过【RESTful实验室】,来演示客户端页面和RESTful交互的过程;

实现RESTful实验室

说明:

(1) 本篇博客主要内容: 客户端是HTML时,HTML是通过ajax技术,来与服务器端进行交互的;


一:客户端是【HTML】时,案例演示;

1.Get方式,案例演示;

(1.1)client.html:

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>Title</title>
     <script type="text/javascript" src="jquery-3.5.1.js"></script>
     <script>
         $(function () {
             $("#btnGet").click(function () {
                 $.ajax({
                     "url" : "/restful/request",
                     "type" : "get",
                     "dataType" : "json",
                     "success" : function (json) {
                         $("#message").text(json.message);
                     },
                     "error":function () {
                         alert("mmmmmmm");
                     }
                 })
             })
         });
     </script>
 </head>
 <body>
     <input type="button" id="btnGet" value="向服务器发送Get请求">
     <h1 id="message"></h1>
 
 </body>
 </html>

说明:

(1) 由于jQuery对ajax提供了支持;上面的案例,就是通过jQuery来实现的ajax;


(2) 代码结构分析:


(3) 此时,客户端和服务器端之间,传递的不是“text/html”了,而是JSON,所以需要处理对应的中文乱码问题:


(4) 一开始的时候,这个client.html总是访问不成功。经过排除bug后,总结了几点错误原因:

● 一定要确保服务器端返回的是【正确的JSON数据】:后端返回的数据格式写错了,不是JSON格式的;所以,在client.html中获得的数据不是json,其自然就不走success了,然后经过实测,其走了error;

● 引入jQuery文件的时候,最好说明一下类型;

● 最好按照规范的格式,来书写ajax;

● 如果遇到问题,可以灵活使用alert()或者console.log()这些来帮助查找问题;

(1.2)启动Tomcat,观察效果;

(1.3)总结、说明;

(1) 上面的案例是:【HTML客户端】向服务器发送请求的过程;然后HTML客户端使用的是ajax技术;

(2) 本质上,无论客户端是【HTML】、【小程序】还是【App】,都是向服务器发送http请求的;只是【HTML通过ajax发送的http请求】,【小程序】和【App】也有自己的方式来发送http请求;

(3) 对于服务器来说,无论客户端是HTML、小程序或者App,服务器端返回的都是JSON(或xml)数据;服务器端不用关心客户端如何展现这些数据;

2.演示Get、Post、Put、Delete;

(2.1)服务器端:RestfulController:增加Post、Put、Delete的处理方法;

package com.imooc.restful.controller;
 
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 
 @Controller
 @RequestMapping("/restful")
 public class RestfulController {
     @GetMapping("/request")
     @ResponseBody
     public String doGetRequest() {
         return "{\"message\":\"客户端返回的查询结果\"}";
     }
 
     @PostMapping("/request")
     @ResponseBody
     public String doPostRequest() {
         return "{\"message\":\"数据新增成功\"}";
     }
 
     @PutMapping("/request")
     @ResponseBody
     public String doPutRequest() {
         return "{\"message\":\"数据更新成功\"}";
     }
 
     @DeleteMapping("/request")
     @ResponseBody
     public String doDeleteRequest() {
         return "{\"message\":\"数据删除成功\"}";
     }
 
 }

说明:

(1) 虽然,几个方法的url一样,但是其请求方式是不同的;

(2.2)客户端:client.html:增加Post、Put、Delete的请求方法;

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>Title</title>
     <script type="text/javascript" src="jquery-3.5.1.js"></script>
     <script>
         $(function () {
             $("#btnGet").click(function () {
                 $.ajax({
                     "url" : "/restful/request",
                     "type" : "get",
                     "dataType" : "json",
                     "success" : function (json) {
                         $("#message").text(json.message);
                     },
                     "error":function () {
                         alert("mmmmmmm");
                     }
                 })
             })
         });
 
         $(function () {
             $("#btnPost").click(function () {
                 $.ajax({
                     "url" : "/restful/request",
                     "type" : "post",
                     "dataType" : "json",
                     "success" : function (json) {
                         $("#message").text(json.message);
                     },
                     "error":function () {
                         alert("mmmmmmm");
                     }
                 })
             })
         });
 
         $(function () {
             $("#btnPut").click(function () {
                 $.ajax({
                     "url" : "/restful/request",
                     "type" : "put",
                     "dataType" : "json",
                     "success" : function (json) {
                         $("#message").text(json.message);
                     },
                     "error":function () {
                         alert("mmmmmmm");
                     }
                 })
             })
         });
 
         $(function () {
             $("#btnDelete").click(function () {
                 $.ajax({
                     "url" : "/restful/request",
                     "type" : "Delete",
                     "dataType" : "json",
                     "success" : function (json) {
                         $("#message").text(json.message);
                     },
                     "error":function () {
                         alert("mmmmmmm");
                     }
                 })
             })
         });
     </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>
 
 </body>
 </html>

说明:

(1) 为了应对Get、POST、Put、Delete;这个client.xml增加了对应的内容;

(2) 启动Tomcat,结果:

(3) 客户端和服务器端对应:虽然其url是一样的,但是由于其请求方式不同,所以,其不会乱;


说明:(摘自MK网课程老师的回答): Restful是一种编码风格,Get Post Put Delete 对应 查询 新增 修改 删除。但要注意的是,这是纯粹的理论设计,是最理想的情况.。目前以Chrome为代表的浏览器,从设计之初就只支持get/post的两种请求,这是w3c的标准规定的,Chrome只是进行了实现。这也是为什么我们在学习Servlet时候,只去处理doGet/doPost的原因。如果我们的请求是来自于标准浏览器那只用对get/post进行处理。 但是,Ajax就不一样了。 Ajax的核心对象是XmlHttpRequest,这东西并不是W3C规定的标准, 同时XmlHttpRequest对象是允许发送Get/Post/Put/Delete请求的,所以在SpringMVC中我们创建了与之对应的处理办法。通过浏览器发送标准Http请求收到W3C的规定制约,只能发送get/post, 而Ajax则四种都可以 ,看起来都是在网页中提交的,其根本是完全不同的。


二:现存问题;(本篇博客只是实现了基本的Restful,还有很多不完美的地方)

待完善的点:

(1) 需要优化JSON的产生方式;


(2) 如何简化@ResponseBody注解的使用方式;


(3) 如何获取uri中的参数;

RestController注解与路径变量

说明:

(1) 上篇博客演示了RESTful的最基本的使用;只不过只是RESTful的最基本使用还不够,其还存在一些问题。本篇博客就是来解决三个问题的其中两个:

● 【@RestController注解】:简化@ResponseBody注解的使用方式;

● 【路径变量】:使用【{} + @PathVariable注解】的方式,获取uri中的参数;


一:【@RestController注解】;

问题: 【为了满足RESTful开发风格】→【方法需要完成的是返回字符串,而不是页面的跳转】→所以,【方法上需要添加@ResponseBody注解】:这有点麻烦;

解决策略: 为了【为了满足RESTful开发风格】→【同时,不再在每个方法上都使用@ResponseBody注解】→@RestController注解应运而生;


二:路径变量(uri中的变量);

路径变量是什么:

● 在【RESTful开发风格简介】中介绍过,如【 POST/article/1】这样的uri的意思是【创建一个id=1的文章】;

而这种情况,id这个参数,没有按以前熟悉的【 POST /article?id=1】方式;而是把这个参数放在了uri中;

● 像这种放在uri中的变量,就称之为路径变量;即,id=1这个变量不是请求参数,它是uri的一部分;

● 自然【 POST /article/1】在性质上和【 POST /article?id=1】一样;只是【 POST/article/1】是路径变量的形式,【 POST /article?id=1】是请求参数的形式;(起码目前,自己可以这样理解)


● 路径变量说白了就是【在uri中的,可变的一个值】;然后在RESTful中,路径变量一般就是id值;

【 POST /article?id=1】这种请求参数的形式中,我们获取id的值是比较容易的;

但是,【 POST /article/1】这种路径变量的形式,我们如何获取其中的id变量值嘞?:为此,Spring MVC提供了路径变量的支持;

获取路径变量: 比如我们要获取如【/restful/request/100】中的路径变量的值100;那么就可以:首先,在@PostMapping注解的url中,添加{一个变量名}(这个名字可以随便取);然后,在方法的参数中,使用@PathVariable注解,来接收这个变量值;

说明: @RequestParam 和 @PathVariable 都是springMVC的注解,都用于接收请求中的参数,@RequestParam 是从request里面直接拿取值,而 @PathVariable则是从一个URI模板里面来填充,也就是RESTful风格。

启动Tomcat,观察效果:

简单请求与非简单请求

说明:

(1) 本文内容:

● 简单请求和非简单请求是什么;

● 基本逻辑:【Spring MVC原先只能处理GET和POST这种简单请求】→【但后来有了PUT和DELETE这种非简单请求】→【而,Spring MVC一开始是无法处理这种非简单请求的】→【为了能够处理PUT和DELETE这种非简单请求,Spring MVC可以通过FormContentFilter过滤器,来解决这个需求】;


一:【简单请求】与【非简单请求】简介;

(1)简单请求: 在HTML中使用的、标准结构的HTTP请求。包括GET和POST请求;

(2)非简单请求 :包含两种情况;

● 复杂要求的HTTP请求。包括PUT和DELETE请求;

● 在标准的GET和POST请求中,扩展了额外的自定义请求头。这种的GET和POST请求,也可以看成非简单请求;

(3)简单请求和非简单请求比较;

● 简单请求和非简单请求,在数据的结构上,几乎是一致的;

● 二者在数据的内容上存在差异:非简单请求在发送之前,需要额外的发送一次预检请求;

● 简单请求就像廉价快递,其会直接把快递送到你家门口或者附加的快递柜;非简单请求就像顺丰这种高质量快递,其在正式送快递前会先打个电话,问一下收件人是否在家,如果收件人在家,顺丰才会正式投递;

(4)非简单请求的好处: 非简单请求增加了预检请求,其可以让服务器预先处理;预检请求会把不符合要求的请求给排除,这些不符合要求的请求并不会发送实际请求;而这是可以减轻网络传输和服务器的压力;

(5)非简单请求和简单请求的处理方式不同: 在用Spring MVC的处理的时候,二者的处理方式是不同的,我们需要注意。


二:【简单请求】与【非简单请求】案例演示;

1.默认情况下:Spring MVC可以处理简单请求;不能处理非简单请求;

(1.1)服务器端的POST请求和PUT请求,都增加data参数;


(1.2)为了方便服务器端接收客户端传过来的参数,这儿创建一个JavaBean:Person类;


(1.3)在服务器端的Controller类的POST和PUT方法中,尝试接收客户端发来的请求中的参数;


(1.4)启动Tomcat,观察效果:POST这个简单请求是可以的,PUT这个非简单请求不可以;


(1.5)原因分析;

● 最早的Spring MVC是为网页服务的;而默认情况下,网页在进行表单提交的时候,只支持GET和POST请求;所以,一开始的时候,Spring MVC只支持GET和POST请求,Spring MVC是不支持PUT和DELETE请求的;

● 但是,随着时间演进,Spring MVC也必须要考虑PUT请求和DELETE请求了;

● 自然,为了能够处理PUT和DELETE这种非简单请求,Spring MVC就需要新增一些新的、针对PUT和DELETE请求的、处理方式;

● 但是,出于各种原因,Spring MVC又不能把【新的、针对PUT和DELETE请求的、处理方式】,强加到服务器端的代码中;(目测是,出于尽量不要动老代码的目的吧)

● 所以,Spring MVC提出了一种折中方案:对于PUT和DELETE这种非简单请求,Spring MVC提供了一个额外的【表单内容过滤器】(FormContentFilter过滤器);通过这个过滤器来对PUT和DELETE请求进行额外处理;

2.借助FormContentFilter过滤器,Spring MVC也可以处理非简单请求了;

(2.1)首先,需要在web.xml中配置FormContentFilter过滤器;

<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.2)然后,服务器端的代码不用动;


(2.3)启动Tomcat,观察效果:此时Spring MVC也可以处理PUT请求了;


(2.4)分析;

● FormContentFilter过滤器,是对Spring MVC功能的扩展;

● 如果没有这个过滤器的话,Spring MVC是无法正常接收PUT和DELETE请求所传递的参数的;

Json序列化

说明:

(1) 上篇博客演示了RESTful的最基本的使用;只不过只是RESTful的最基本使用还不够,其还存在一些问题。本篇博客就是来解决三个问题的最后一个:

● 【@RestController注解】:需要优化JSON的产生方式;

即,本篇博客主要就是讲解【在Spring MVC中,使用jackson组件】的方法;

(2) 需要注意的点:

● 当我们在Spring MVC中使用jackson时,服务器端Controller中的方法,直接返回实体对象就可以了,jackson会帮我们把对象序列化为JSON;

● 国外的组件,大都存在时区偏移问题。我们需要留意这个问题。

(3)补充:Google也有一款JSON序列化组件:Gson;暂时不管了,如果以后遇到了再了解吧。


一:引入处理JSON的第三方组件:Jackson;

说明: Jackson和Fastjson都是Json库,Jackson在国外比较流行,Fastjson在国内用到比较多。Fastjson是由阿里巴巴开源的Json解析库,主要是速度快,为了提高速度在规范兼容性方面做了一些妥协。而Jackson历史悠久,可扩展性强,所以这里使用了Jackson进行序列化。

然后,有关JSON和Fastjson的内容,如有需要可以快速参考【常用功能与过滤器、监听器、FreeMarker】中的内容;

1.访问Maven中央仓库,了解Jackson组件;

访问【Maven中央仓库的检索网站:https://search.maven.org/

2.在pom.xml中引入Jackson;(重点)

<?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>com.imooc</groupId>
     <artifactId>restful</artifactId>
     <version>1.0-SNAPSHOT</version>
 
     <repositories>
         <repository>
             <id>aliyun</id>
             <name>aliyun</name>
             <url>https://maven.aliyun.com/repository/public</url>
         </repository>
     </repositories>
 
     <dependencies>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-webmvc</artifactId>
             <version>5.1.9.RELEASE</version>
         </dependency>
 
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-core</artifactId>
             <version>2.9.9</version>
         </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
             <version>2.9.9</version>
         </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-annotations</artifactId>
             <version>2.9.9</version>
         </dependency>
     </dependencies>
 
 </project>

说明:

(1) 要想使用jackson,需要引入三个依赖:【jackson-core】,【jackson-databind】,【jackson-annotations】;

(2)需要说明的一点: Spring MVC非常智能;其会检测,当前的类路径中是否存在jackson,一旦发现其有【jackson-core】,【jackson-databind】,【jackson-annotations】这些包,Spring MVC就会自动启用jackson为我们提供JSON序列化的服务,我们不需要任何其他额外的配置;


二:Spring MVC中,使用jackson的案例;

1.案例1:返回结果只包含单个对象:直接返回 E 就行了;(重要)

在RestfulController类中,添加findByPersionId()方法;

package com.imooc.restful.controller;
 
 import com.imooc.restful.entity.Person;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 
 @RestController
 @RequestMapping("/restful")
 public class RestfulController {
 
 
     @GetMapping("/person")
     public Person findByPersonId(Integer id) {
         Person person = new Person();
         if (id == 1) {
             person.setName("zhang");
             person.setAge(20);
         }else if (id == 2){
             person.setName("wang");
             person.setAge(21);
         }
         return person;
     }
 
 }

说明:

(1) Spring MVC中配置是jackson,可以自动把【方法返回的实体对象】序列化为【JSON字符串】;


还是那个点,老生常谈:一旦我们在项目中引入了新依赖,就要记得把这个引用添加到发布中去。


启动Tomcat,观察效果:

2.案例2:返回结果只包含多个对象:返回List<E>集合;(重要)

在RestfulController类中,添加findPersons()方法;

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.List;
 
 @RestController
 @RequestMapping("/restful")
 public class RestfulController {
 
     @GetMapping("/persons")
     public List<Person> findPersons() {
         List list = new ArrayList();
         Person person1 = new Person();
         person1.setName("zhang");
         person1.setAge(20);
         Person person2 = new Person();
         person2.setName("wang");
         person2.setAge(21);
         list.add(person1);
         list.add(person2);
         return list;
     }
 
 }

说明:

(1) Spring MVC中配置是jackson,可以自动把【方法返回的实体对象】序列化为【JSON字符串】;


启动Tomcat,观察效果:

3.案例3:服务器端返回了JSON,客户端可以获取并提取JSON数据:这儿只演示客户端是HTML的情况;


我们知道JSON就是JavaScript Object Notation(JavaScript对象表示法),想表达的意思是JavaScript对JSON有着天然的支持。


服务器端,还是用RestfulController类中的findPersons()方法;

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.List;
 
 @RestController
 @RequestMapping("/restful")
 public class RestfulController {
 
     @GetMapping("/persons")
     public List<Person> findPersons() {
         List list = new ArrayList();
         Person person1 = new Person();
         person1.setName("zhang");
         person1.setAge(20);
         Person person2 = new Person();
         person2.setName("wang");
         person2.setAge(21);
         list.add(person1);
         list.add(person2);
         return list;
     }
 
 }

在客户端的Client.html中新增以下内容:

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>Title</title>
     <script type="text/javascript" src="jquery-3.5.1.js"></script>
     <script>
 
         $(function () {
             $("#btnPersons").click(function () {
                 $.ajax({
                     "url" : "/restful/persons",
                     "type" : "get",
                     "dataType" : "json",
                     "success" : function (json) {
                         for (var i = 0; i < json.length; i++) {
                             var p = json[i];
                             $("#divPersons").append("<h2>" +p.name + ":"
+ p.age +"</h2>")
                            }
                        }
                    })
                })
            });
        </script>
    </head>
    <body>
        <input type="button" id="btnPersons" value="查询所有人员">
        <div id="divPersons"></div>
 
    </body>
    </html>

说明:

(1) 在客户端HTML中,可以提取JSON中的数据;


启动Tomcat,观察效果:


三:Spring MVC中使用jackson处理时间时,存在问题;

1.问题是什么?:jackson处理时间时:得到的是1970年到设置时间的秒数;

启动Tomcat,观察效果;

2.解决策略:jackson通过 @JsonFormat注解,来解决上面的问题;


但是,上面的时间和真实时间不符,差了几个小时:这是因为,jackson在序列化对象时,遇到日期属性,会默认按照格林尼治时间作为起点。咱们是东八区,和格林尼治差了8个小时,所以会出现上面的日期不符的问题。

可以设置【timezone = “GMT+8”】属性,来平移时区;


我们知道,jackson是国外开发的组件,对于其他国外的组件,大都存在时区偏移问题。我们需要留意这个问题。

其实我们在使用国产的阿里做的Fastjson组件,处理时间时,也有类似的问题:

对于,毫秒数的问题,Fastjson可以使用@JSONFiled注解来解决。而,Fastjson由于是国产的,对于我们国内用户来说,不存在时区偏移问题,即Fastjson就是以东八区作为起点的。


如有需要可以参考【对象转换成JSON字符串;JSON字符串转换为对象】:在使用Fastjson时,我们需要借助@JSONFiled注解来处理时间问题。

:JSON序列化输出时,JSON字符串也要考虑到中文乱码问题,所以,响应输出的时候,也要对JSON格式的字符串进行编码转换:

跨域问题:浏览器同源策略

说明:

(1) 本篇博客内容合理性解释:

●【RESTful开发风格中,访问远程网站肯定会经常遇到】→【但是,浏览器存在一个同源策略】→【即,当访问不同域的资源时,会触犯浏览器同源策略,出现无法访问的问题】→【那么,为了能够实现访问不同域中的资源,就需要一种“跨域访问”机制】→【所以,在介绍“跨域访问”之前,本篇博客就先介绍浏览器的同源策略】;

即使如此,但心里要明白,【浏览器同源策略】是一种很好的保护策略;只是,我们在需要跨域访问的地方,我们要解决【浏览器同源策略】带来的“障碍”;


一:【浏览器同源策略】简介;

1.【浏览器同源策略】引入;

(1)【浏览器同源策略】是什么?:

意思是:

● 比如有两个网站【网站A】和【网站B】,这两个网站有不同的域名,在不同的服务器上;

● 如果【网站A的某个页面】向【网站B的某个url】发送了ajax请求;那么,就会因为同源策略被阻止;


(2)浏览器为什么要有【浏览器同源策略】?:

● 浏览器为了保证网站足够安全;

● 设想一下,如果没有【浏览器同源策略】;那么任何网站都可以向其他网站发送请求,这就乱套了;(如果黑客在自己JavaScript中,模拟了十万个人,同时对一个网站发起ajax请求,那么那个网站可能就会因为请求过多而瘫痪)

● 即,【浏览器同源策略】是对资源的保护;网站的页面或者ajax请求,只能获取同网站(同域名)下的资源;


(3)触发【浏览器同源策略】:案例演示;

即,在上面,桌面上的client.html所在的域为【/User/dell/Desktop】,而其中请求的url的域是【localhost】;

然后,启动Tomcat:

2.【浏览器同源策略】中【“域”同源与否】的界定;

(1)“域”同源与否,定义;

即,如上描述,只要协议、域名、端口有任何一个不同,都被认为是不同的域;


(2)“域”同源与否,举例;

(1) 域名不同(一个是imooc.com,一个是xxx.com),端口不同(一个是80,一个是8080),协议也不同(一个是http,一个是https):其自然是不同的域;

(2) 协议也不同(一个是http,一个是https):其自然是不同的域;

(3) 域名不同(一个是imooc.com,一个是abc.imooc.com,即目标url增加了二级域名abc):其自然是不同的域;

(4) 端口不同(一个是80,一个是8080):其自然是不同的域;

(5) 这是一个特例:【localhost】和【127.0.0.1】虽然在系统层面上都指向本机,但是二者不被看做是同一个域;


(3)HTML中,不受【浏览器同源策略】约束的标签;

但是,在HTML中,默认有一些标签是不受【浏览器同源策略】约束的;

(1) <img>图片标签:我们常用<img标签去显示远程的某个网站的图片;默认情况下,<img>标签不受【浏览器同源策略】约束;

(2) <script>标签:加载指定url下的JS脚本;<script>天然支持加载某个远程网站中的JS脚本文件;

(3) <link>标签:加载远程的CSS;

3.控制台打印出【Access-Control-Allow-Origin】:就代表触犯了【浏览器同源策略】;

如上面的案例中,桌面的client.html访问localhost的服务时:


说明:但是,如果在实际开发中,必须要进行跨域访问的话,也是有解决策略的;在下篇博客中会介绍对应的解决策略;

Spring MVC实现“跨域访问”的方法

说明:

(1) 本篇博客的合理性解释:

● 上篇博客的脉络是:【RESTful开发风格中,访问远程网站肯定会经常遇到】→【但是,浏览器存在一个同源策略】→【即,当访问不同域的资源时,会触犯浏览器同源策略,出现无法访问的问题】→【那么,为了能够实现访问不同域中的资源,就需要一种“跨域访问”机制】→【所以,在介绍“跨域访问”之前,上篇博客就先介绍浏览器的同源策略】;

● 既然,上篇博客已经介绍了【浏览器同源策略】;

● 那么【为了能够在访问不同域中的资源时,不触犯浏览器同源策略】,本篇博客就来介绍【Spring MVC实现“跨域访问”的方法】;

(2)强调: 本篇博客的CORS跨域资源访问的内容,仅仅适用于【客户端是HTML浏览器的情况】;如果【客户端是小程序或者App】,就需要考虑其他策略了;


一:CORS跨域访问简介;

1.【CORS跨域访问】,机制简介;

说明:

(0) CORS(Cross-origin resource sharing):跨域资源共享,又称跨域资源访问;

(1) CORS跨域访问是一种机制:

● 通过“跨域访问机制”,能够解决【当访问不同域的资源时,会触犯浏览器同源策略,从而出现无法访问的问题】;

● 这种机制是指:通过【在“HTTP请求”以及“响应头”的部分,附加一些额外的信息】,以通知浏览器可以访问其他域的资源;(自然,这些其他域的资源,在对应域名下也得是允许被访问的)

(2) CORS跨域访问机制中,我们附加了什么信息?:

● CORS如果要进行远程跨域访问,需要在url的响应头中包含【以Access-Control开头的响应头】,以指明当前请求是允许跨域访问的;

● 自然【Access-Control响应头】不是随随便便就能加上的,这需要远程服务器对应的资源进行相应的授权才可以;

2.Spring MVC,实现“CORS跨域访问”的方法;

说明:

(1) 第一种方法:在类中使用【@CrossOrigin注解】,来说明当前Controller所映射的url允许被跨域访问;这个注解只在当前Controller中生效,即这种方法的作用范围是局部的;

如果我们系统中,有大量的Controller都要允许被跨域访问,就要考虑第二种方法了;

(2) 第二种方法:在在applicationContext.xml配置文件中,通过<mvc:cors标签进行一次性的全局配置;


二:项目准备;CORS跨域问题演示;

0.演示前准备:为了模拟“CORS跨域访问”:比照【restful工程】,创建【restful_8080工程】;

如有需要,创建过程可以参考【本专栏中,前面的博客】;这儿就不重复了;

但是需要注意两点:


即,我们创建的【restful_8080工程】的所有资源的域都是【http://localhost:8080】的;而,我们原先的【restful工程】的所有资源的域都是【http://localhost】(或者是【http://localhost:80】,因为我们设置了Tomcat默认端口是80,所以默认端口时80可以省略);

1. CORS跨域问题演示;

启动【restful_8080】工程:

即,在上面的过程中【localhost:8080,下的client.html】访问【localhost:8080,下的/restful/persons】时,没有跨域,其自然是可以访问的;


但是,如果修改下【restful_8080工程】的url:


三:Spring MVC,实现“CORS跨域访问”的两种策略;

1.策略1:在服务提供端的类上使用【@CrossOrigin注解】;

(0)预先说明;

● 因为在上面的案例中,是【restful_8080工程的:localhost:8080,下的client.html】访问【restful工程的:localhost:8080,下的/restful/persons】;即【restful工程】是服务提供端;

● 那么为了能够让【restful_8080工程,作为请求方】能够访问【restful工程,提供的服务】:需要在【restful工程,提供服务的Controller类上使用@CrossOrigin注解】;


(1)在提供服务方的Controller上使用@CrossOrigin注解;

说明:

● 使用【@CrossOrigin注解】来设置跨域的范围;即,说明【哪些其他域名下,送过来的请求】是允许访问本Controller提供的服务;

● 其中的origins配置上,每一条域名要写完整,即协议、域名、端口都要写上;


(2)可以跨域访问的:效果:

上面设置好了之后,重启【restful工程】和【restful_8080工程】:


(3)原因解释,即为什么增加了【@CrossOrigin注解】就能解决跨域访问的问题嘞?

首先, 如果请求发起方,如果其发起的请求是跨域请求,会在请求头中增加【Sec-Fetch-Mode: cors】;

然后, 如果请求接收方(就是服务器端),可以接受请求方的跨域访问,那么会在响应头中增加【Vary:Access-Controll】之类的信息;


反例: 比如,我们没有通过【@CrossOrigin注解】设置跨域访问时:


(4)【@CrossOrigin注解】:设置多个域名;

直接增加就行了;

@CrossOrigin(origins = {"http://localhost:8080","http://www.imooc.com"})


(5)【@CrossOrigin注解】:对所有域名,放开跨域访问;(这种,在实际开发中,几乎不会用到,因为存在严重的安全隐患)

在实际工作中,坚决不能这么干。


(6)【@CrossOrigin注解】:maxAge参数:设置预检请求的缓存时间;

【预检请求】回顾;

在【简单请求(GET和POST);非简单请求(PUT和DELETE)】我们知道:

● 请求包括简单请求和非简单请求;

● PUT和DELETE请求是非简单请求;

● 而非简单请求包括【预检请求】和【正式请求】;

● 先发送【预检请求】,向服务器检查是否允许被访问;如果允许,再发送【正式请求】;如果不允许,当前操作就会被中断;

【预检请求】带来的问题: 但是,上面的流程会产生一个问题;

● PUT和DELETE这些非简单请求在每一次发送时,都会有【预检请求】和【正式请求】这两个请求,而这必然会增加服务器的压力。

● 但是,在如此糟糕的情况下,还是有一个比较利好的情况,即【预检请求】的授权逻辑是不会轻易改变的。所以,maxAge参数,就派上用场了。

maxAge参数;

● 该参数表示,我们可以把【预检请求】的结果进行缓存;

● 比如【maxAge=3600】:设置预检请求的缓存时间为3600秒:表示:在一小时时间内,同样的PUT或DELETE等非简单请求,再次发送的时候,就不再需要进行预检请求处理了,直接发送实际请求。当一小时之后,再发送PUT或DELETE等非简单请求时,重新发送【预检请求】以重新检查服务器状态,如果预检请求通过,那么我们再次将其缓存一小时……

● 即,maxAge可以帮我们降低服务器压力;

● 一定要清楚:maxAge缓存的是【预检请求的处理结果】,而不是【请求的内容】;

【@CrossOrigin注解】虽然好用,但是如果Controller很多时,在很多Controller类上都使用【@CrossOrigin注解】还是比较麻烦的;为此,就引出了Spring MVC实现“跨域访问”的第二种策略: <mvc:cors标签进行一次性的全局配置;

2.策略2:在applicationContext.xml中,通过<mvc:cors标签进行一次性的全局配置;

(0)预先说明;

【@CrossOrigin注解】虽然简单,但是还是存在一些问题;

● 如果一个项目中,有很多Controller需要配置【@CrossOrigin注解】,就有可能出现遗漏的情况;

● 或者,当前系统要统一的设置跨域的域名时,使用【@CrossOrigin注解】就会略显笨拙;

● 我们需要一个可以全局配置的方式,为此就引出了【<mvc:cors标签进行一次性的全局配置】;

(1)在applicationContext.xml中,通过 <mvc:cors标签进行一次性的全局配置;

[mvc:cors](mvc:cors)
<mvc:mapping path="/restful/"
allowed-origins="[http://localhost:8080,http://www.imooc.com](http://localhost:8080,http://www.imooc.com)"

max-age="3600"/>

[/mvc:cors](/mvc:cors)

(2)启动Tomcat,观察效果;


四:补充说明;

(1)上面的CORS跨域访问的内容:仅仅适用于【客户端是浏览器的情况】;

CORS跨域资源访问的上面两种策略:【第一种方法:在类中使用:@CrossOrigin注解】和【第二种方法:在在applicationContext.xml配置文件中,通过<mvc:cors标签进行全局配置】:只是在浏览器中的策略;

如果客户端不是HTML浏览器,而是小程序或者APP的话,上面的策略是不行的;

(2)【@CrossOrigin注解】和【<mvc:cors全局配置】选用哪个?

● 如果,当前应用是一个【专用的webAPI】(即,是一个只对外提供web数据服务的应用),就需要使用【<mvc:cors全局配置】的方式;

● 如果,只是个别的Controller需要对外暴露服务,就推荐使用【@CrossOrigin注解】方式;

● 如果,一个项目既使用了【<mvc:cors全局配置】,又使用了【@CrossOrigin注解】:在实际运行时候,会以【@CrossOrigin注解】为准;