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:
说明:
(1) 设置了国内阿里仓库;引入了【spring-webmvc】模块;
说明:
(1) 配置了DispatcherServlet;配置了【CharacterEncodingFilter过滤】来解决POST请求的中文乱码问题;
applicationContext.xml:
说明:
(1) context:component-scan:开启组件扫描; mvc:annotation-driven/:启动Spring MVC开发模式; mvc:default-servlet-handler/:将静态资源排除;
(2) 通过【StringHttpMessageConverter】转换器,来解决响应的中文乱码问题;
二:基于【RESTful风格】开发一个案例;
RestfulController类:
声明:上面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:
说明:
(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的处理方法;
说明:
(1) 虽然,几个方法的url一样,但是其请求方式是不同的;
(2.2)客户端:client.html:增加Post、Put、Delete的请求方法;
说明:
(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过滤器;
(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;(重点)
说明:
(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()方法;
说明:
(1) Spring MVC中配置是jackson,可以自动把【方法返回的实体对象】序列化为【JSON字符串】;
还是那个点,老生常谈:一旦我们在项目中引入了新依赖,就要记得把这个引用添加到发布中去。
启动Tomcat,观察效果:
2.案例2:返回结果只包含多个对象:返回List<E>集合;(重要)
在RestfulController类中,添加findPersons()方法;
说明:
(1) Spring MVC中配置是jackson,可以自动把【方法返回的实体对象】序列化为【JSON字符串】;
启动Tomcat,观察效果:
3.案例3:服务器端返回了JSON,客户端可以获取并提取JSON数据:这儿只演示客户端是HTML的情况;
我们知道JSON就是JavaScript Object Notation(JavaScript对象表示法),想表达的意思是JavaScript对JSON有着天然的支持。
服务器端,还是用RestfulController类中的findPersons()方法;
在客户端的Client.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注解】:设置多个域名;
直接增加就行了;
(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注解】为准;