RestController注解
这两者究竟是什么呢?下面通过程序一演示就明白了。
首先回到之前的项目中,在上面我们在controller中写入查询、新增、修改和删除这四种操作。但是发现一件特别麻烦的事情,每一次在书写方法以后,都需要在每一个方法上写@ResponseBody,这样返回的字符串才能被正常地输出到响应。所以在Spring4以后。体提供了一个新的spring注解,叫做@RestController ,把它替换原来的@Controller注解写在类名上即可,这个注解的作用就是,只要一写上以后,默认当前方法返回的都是rest形式的数据。使用了这个注解以后,我们最大的好处就是不需要在每个方法上添加@ResponseBody了,它默认就会将这个字符串向请求中进行输出,帮助我们简化开发。
路径变量
我们见过 POST/article/1 这样的一种uri的书写形式,表示创建一个ID值为1的文章。像这种url,id这个位置其实是灵活的,是变化的,这里可能是1,之后就是3等了。这种放在uri中的变量,就成为路径变量。 在restful风格下,这种路径变量的使用是很普遍的。就拿当前的这个例子来说,比如我要创建一个全新的请求,这个请求的ID值假设是100的话,可能我们会书写成POST/restful/request/100 ,那在我们服务器端这么接收到这个100呢?它并不是我们请求的参数,而是我们uri中的一部分啊。好在Spring MVC为我们提供了路径变量,我们只需要在这个@xxxMapping("/request")里面的url后面添加一个{},并给一个路径变量名字就可以了。比如@xxxMapping("/request/{rid}") ,那在我们程序中如何接收呢?其实也很简单,在方法中创建一个参数,比如Integer requestId ,然后在参数前添加@PathVariable注解,注解里面传入路径变量的名字rid。
示例如下:
@GetMapping("/request/{rid}") public String doGetRequest(@PathVariable("rid") Integer requestId){ return "{\"message\":\"返回查询结果"+requestId+"\"}"; }
更改html的ajax请求:
<script> $(function (){ $("#btnGet").click(function (){ $.ajax({ url:"http://localhost:8888/restful/request/100", type:"get", dataType:"json", success:function (data){ $("#message").text(data.message+data.id) } }) }) }) </script>
运行如下:
简单请求与非简单请求
简单请求是指标准结构的HTTP请求,对应GET/POST请求。是我们以前在html中最常使用的标准结构的http请求。
非简单请求是指复杂要求的HTTP请求,指PUT/DELETE请求、扩展标准请求。
简单请求与非简单请求在数据的结构上,几乎是一致的,只是在数据的内容上会略有不同。PUT和DELETE是特殊的请求发送方式,而扩展标准请求则自定义了额外的请求头。两者最大的区别是非简单请求发送前,需要发送预检请求。
这是一个非简单请求的发送过程,在原始的情况下,如果是简单请求,直接把请求发过来,由服务器处理就完事了。但是如果是非简单请求的话,它首先要发送一个预检请求,预检请求的作用是让服务器返回当前这个请求能不能够被正常地处理,如果服务器返回能进行处理,之后再由浏览器发送实际的请求给服务器进行处理。
下面我们通过程序来演示一下这个案例,回到上面的项目,重新编写controller里面的代码:
package com.haiexijun.restful.controller; import com.haiexijun.restful.entity.Person; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/restful") public class RestfulController { @GetMapping("/request") public String doGetRequest(Person person){ System.out.println(person.getName()+":"+person.getAge()); //注意这里模拟返回一个json字符串 return "{\"message\":\"返回查询结果\"}"; } @PostMapping("/request") public String doPostRequest(Person person){ System.out.println(person.getName()+":"+person.getAge()); return "{\"message\":\"数据新建成功\"}"; } @PutMapping("/request") public String doPutRequest(Person person){ System.out.println(person.getName()+":"+person.getAge()); return "{\"message\":\"数据更新成功\"}"; } @DeleteMapping("/request") public String doDeleteRequest(){ return "{\"message\":\"数据删除成功\"}"; } }
我们用Person对象来接收数据,故要定义一个Person的javabean:
package com.haiexijun.restful.entity; public class Person { 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; } }
然后重新编写html,写几个按钮,分别来对比post请求和put请求:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>RESTful</title> <script src="jquery.js"></script> <script> $(function (){ $("#btnGet").click(function (){ $.ajax({ url:"/restful/request?name=lily&age=23", type:"get", dataType:"json", success:function (data){ $("#message").text(data.message) } }) }) $("#btnPost").click(function (){ $.ajax({ url:"/restful/request", data:"name=lily&age=23", type:"post", dataType:"json", success:function (data){ $("#message").text(data.message) } }) }) $("#btnPut").click(function (){ $.ajax({ url:"/restful/request?name=lily&age=23", type:"put", dataType:"json", success:function (data){ $("#message").text(data.message) } }) }) $("#btnDelete").click(function (){ $.ajax({ url:"/restful/request", type:"delete", dataType:"json", success:function (data){ $("#message").text(data.message) } }) }) }) </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>
我们运行后,点击第一个按钮get请求和第二个按钮post请求后发送简单请求,发现可以完成请求:
但是,当我们点击Put请求的时候,就不能完成请求了。如下,put并没有接收到实际的数据,控制台打印null:
那这又是为什么呢?这里就涉及到一个历史问题了。作为最早的springMVC是为我们网页服务的。默认网页在表单提交的时候只支持GET和
POST这两种请求,对于PUT和DELETE是不支持的。但是随着技术的演进,put和delete作为springmvc必须要考虑的。但又不能把put和delete请求的处理方式强塞进原有的代码中,所以springmvc做了一个折中的方案,作为PUT和DELETE这两种非简单请求,springmvc提供了一个额外的表单内容过滤器来对put和delete进行处理。
那它的具体写法是:
打开web.xml文件,在这个xml文件中增加一个filter。代码如下:
<!--表单内容过滤器,用于对非简单请求进行额外处理--> <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>
然后重启运行一下,所有请求都能正常处理了。
JSON序列化
在学习JSON序列化时,先要导入一个maven依赖Jackson,在中央仓库搜索jackson-core和jackson-databind和jackson-annotations,jackson是目前世界上使用范围最广,效率最高的JSON序列化组件。但是我们最好还是选择2.9.4版本以后的版本,因为之前的版本有严重的安全隐患。
<!--jackson的核心包--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.13.2</version> </dependency> <!--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如何在springmvc里面来使用呢?我们还是通过案例来书写一下。这里打开之前写好的控制器RestfulController,在这控制器中我们增加一个方法:
@GetMapping("/person") public Person findByPersonId(Integer id){ Person p = new Person(); if (id==1){ p.setAge(23); p.setName("lily"); }else if (id==2){ p.setAge(22); p.setName("smith"); } return p; }
在这里返回的是Person对象而不是string或者ModelAndView呢?因为刚才我们配置了jackson,所以jackson会自动帮我们进行序列化输出。这解决了我们手动拼接字符串时的麻烦。
我们运行项目,结果如下:
下面再来补充一个在实际应用中的特殊场景,比如我们现在查询的不是单个对象,而是多个对象,该如何返回呢?下面我们示意性的写一下:
@GetMapping("/persons") public List<Person> findPersons(){ List<Person> list=new ArrayList<Person>(); Person p1=new Person(); p1.setAge(22); p1.setName("smith"); Person p2=new Person(); p2.setAge(23); p2.setName("lily"); list.add(p1); list.add(p2); return list; }
下面是浏览器访问的运行结果:
下面再补充一个小知识点,如果返回的对象中有日期信息呢,那返回的json对象其实对于时间日期信息返回的是一个到1970年的时间戳。比如我们在Person类里面添加一个birthday。如果要让Jackson对时间进行格式化,就要在该属性前添加一个**@JsonFormat**注解,里面用pattern设置格式化的格式,timezone设置时区为东八区。
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date birthday;
更改一下方法:
@GetMapping("/persons") public List<Person> findPersons(){ List<Person> list=new ArrayList<Person>(); Person p1=new Person(); p1.setAge(22); p1.setName("smith"); p1.setBirthday(new Date()); Person p2=new Person(); p2.setAge(23); p2.setName("lily"); p2.setBirthday(new Date()); list.add(p1); list.add(p2); return list; }
浏览器访问的结果:
三.跨域问题
浏览器的同源策略
本节来聊一个在restful中必须要考虑的问题,就是跨域访问。那什么是跨域访问呢?为什么要强调浏览器的跨域访问呢?这其实,跨域访问的根源是来自于浏览器的同源策略。
浏览器的同源策略是指阻止一个域加载的脚本去获取另外一个域上的资源。听起来有一点晦涩,说白了,我们有两个网站,一个是网站A,一个是网站B。他们有不同的域名在不同的服务器上。如果A的某一个页面向B的某个URL发送了AJAX请求的话,就会因为同源策略被阻止。原因很简单,就是浏览器为了保证我们的网站足够的安全。如果没有同源策略的保护,那任何一个网站都可以向其他网站发起请求。只要协议,域名,端口有任何一个不同,都被当做是不同的域。 浏览器的console看到Access-Control-Allow-Origin就代表跨域了,请求得到的结果并不会被浏览器处理。
下面我们看几个例子:
在html里面的有一些标签是不受同源策略约束的,HTML中允许跨域的标签:
CrossOrigin注解解决跨域访问
首先我们要了解一个名词,如果要涉及到跨域访问,肯定会经常看到一个名词叫CORS ,翻译过来就是跨域资源共享的意思。
说起CORS就要说到它的底层原理了,CORS是一种机制,使用额外的HTTP头通知浏览器可以访问其他域。 URL响应头包含Access-Control-*指明请求允许跨域。 但这个响应头并不是我们自己随随便便就可以加上的,这是要远程服务器对应的资源进行相应的授权,才允许访问。
那Springmvc里如何做到跨域访问呢?方法主要有两种:
第一种是在我么的类中,使用@CrossOrigin(origins={“允许跨域的全域名”,“允许跨域的全域名”} ,maxAge=秒数) 这个注解来说明当前controller所映射的URL允许被跨域访问。这是局部的处理,当然了,这个注解,只是在当前的controller里面生效。如果我们系统中有大量的controller要考虑跨域的问题。如果要允许所有的跨域请求都允许访问的话,写成:@CrossOrigin(origins={" * "}) ,但是并不推荐这样去使用。
@RestController @RequestMapping("/restful") @CrossOrigin(origins={"http://localhost:8080"},maxAge=3600) public class RestfulController { ········省略····· ·······省略····· }
上的设置了maxAge为3600又是什么意思呢?前面我们知道,PUT和DELETE都是非简单请求,那非简单请求在发送的时候,首先要发送一个预检请求,来向服务起检查当前的请求是否允许被访问呢?如果允许,我们就发送实际的请求,如果不允许,当前的操作就会被中断。但是,这又会产生一个新的问题。作为PUT和DELETE这种非监督请求,在每一次发送的时候,都其实有两个请求。这必然会增加服务器的压力。而且作为服务器端,预检请求授权的逻辑是不会轻易地改变的。所以刚才的maxAge就起到作用了。maxAge将预检请求的结果进行缓存,设置了3600秒,也就是一小时。在一小时的时间内,同样的PUT请求再次发送的时候就不需要再发起预检请求处理了。直接发送实际请求。
这时可以使用第二种方式,在配置文件中,使用<mvc:cors>这个标签一次性的全局配置。 这样可以一劳永逸。
<mvc:cors> <mvc:mapping path="/restful/**" allowed-origins="http://localhost:8080,http://localhost:9999" max-age="3600"/> </mvc:cors>
path指定要跨域的资源的路径,上面的/restful/**就是restful下所有的controller。 allowed-origins指定允许跨域的域名,多个域名用逗号隔开。maxAge同上。
注意:如果我们既配置了全局的,又配置了注解的,springmvc会以注解的配置为准。