前言
不同的编程语言可能有着不同的设计模式,比方说我们最熟悉的Java有二十三种设计模式(单例、工厂、适配器、桥接模式等等),我们今天要讲的开发模式RestFul并不属于这二十三种设计模式,就和AOP以及IoC一样,更多的是一种设计理念,或者说是一种架构思想,本节我们就来聊聊关于Web的开发模式RestFul
什么是RestFul
看到"rest",我的第一反应就是"rest"的意思,restful是"宁静的"意思,不知道为什么看到rest,我就想睡觉zzzz
但是仔细看,有三个字母是小写,那么他注定就是一个缩写英文单词啦~
rest即Representational State Transfer,表现层状态转化 ,互联网上资源(是服务)细化理解为一个url,如果访问某个资源通过http url访问。 我们把”资源”具体呈现出来的形式,叫做它的”表现层”(Representation)。
表现层其实是我们经常接触到的层面,它对用户展示的形式有:html、json、xml、pdf、图片。
由于http协议的限制,服务器和客户端不能实现主动通信,只能有客户端发起请求服务端响应请求,也就是将请求方法和参数都包含url,其实不管是不是使用到了RestFul设计模式,规则是永远不会变的,只不过RestFul制定了对url更加友好的规划,或者说是一种规范,这个规则将方法和资源分开看起来做的很简单,但是能够让我们更好的理解对于网络资源的操作。
那说了半天,到底什么是表现层状态转化 呢?
其实说白了,我们在网上看到的任何一个资源,都是一个独一无二的url,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生”状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是”表现层状态转化”。
RESTful开发中URL的规范
URL规范
用户登录:http请求方法设置为get
我们来看看对比下原始url和规范后的url,发现有几个地方都被缺省了,一个是.action
,再者就是我们的属性名
原始url:http://www……/login.action?username=marco&password=123456
规范后的url:http://www…/item/marco/123456
如果这个还不明显,我们再看看几个例子
商品删除:http请求方法设置为delete
规范后的url:url:http://www…/item/100/86表示对100分类 下的86号商品进行删除
我们发现是不是通过这种方式,我们请求数据的url、删除数据的url、亦或者是修改数据的url都变得一致了!
rest向客户端发送数据
在请求时指定服务端给客户端响应的内容类型是什么?
实现:在请求时设置http头中Accept
对商品查询,以json格式展示:
rest设置:
url为:http://www…/item/101/1
客户端请求此url并且设置Accept为”applicatoin/json”
服务端处理方法:
接收请求,解析Accept,根据指定类型返回不同的内容格式。
如果解析到Accept为”applicatoin/json”,服务端就将内容转成json输出
如果解析到Accept为”applicatoin/xml”,服务端就将内容转成xml输出
使用SpringMVC+Json构建基于Restful风格的应用
理论总归是枯燥的,那我们还是拿案例来演示下加深理解吧~
第一步:导包
因为这次需要使用到Json,因此我们需要导入SpringMVC的解析Json的jar包(三个jackson的jar)
第二步:创建实体类Person
package com.marco.domain;
public class Person {
private Integer id;
private String name;
private String address;
public Person() {
// TODO Auto-generated constructor stub
}
public Person(Integer id, String name, String address) {
super();
this.id = id;
this.name = name;
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", address=" + address + "]";
}
}
第三步:配置web.xml
前面两步就不需要我再说了吧~ 大家注意看web.xml的变化
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>01_springmvc_hello</display-name>
<!-- 编码过滤器 开始-->
<filter>
<filter-name>EncodeingFilter</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>EncodeingFilter</filter-name>
<!-- <url-pattern>*.action</url-pattern> -->
<servlet-name>springmvc</servlet-name>
</filter-mapping>
<!-- 编码过滤器 结束-->
<!-- 配置前端控制器 开始 -->
<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:springmvc.xml</param-value>
</init-param>
<!-- 启动时就要创建DispatcherServlet对象 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置前端控制器 结束 -->
</web-app>
这个区别不太好找,眼尖的朋友应该发现了这个文件和之间的有什么不同。
这个差别就是<url-pattern>/</url-pattern>
,我们之前在里面配置的是.action
,这里的/
表示拦截所有,但是不包括.jsp这样的静态文件,关于为什么不能使用/*
的原因呢,之前在 Marco’s Java【SpringMVC入门(二) 之 SpringMVC的入门配置及项目测试】中有讲解过,不懂得朋友可以去回顾下咯。
第四步:创建PersonController控制器
那在这里我将基本的请求类型都列举出来了,我们的请求的种类一般有四种: POST、GET、DELETE、PUT
大家也许对后面两种比较陌生,他们分别是删除和修改的意思。那肯定有人会有疑问了,我一个POST可以搞定所有的事情,怎么还来个DELETE、PUT凑热闹呢?
那么我们接着往下面看,看完以后兴许会有结论了~
package com.marco.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.marco.domain.Person;
@RestController
public class PersonController {
/*创造10条数据,模拟后台JDBC获取数据的操作*/
private static List<Person> persons=new ArrayList<>();
static {
for (int i = 1; i <=10; i++) {
persons.add(new Person(i, "marco"+i, "武汉"+i));
}
}
/**
* 测试say,produces="text/html;charset=utf-8"主要是为了保证请求过来的参数不会乱码(针对请求中有中文)
*/
@RequestMapping(value="say/{name}", method= {RequestMethod.POST}, produces="text/html;charset=utf-8")
public String say(@PathVariable("name")String name) {
return "你好:"+name;
}
/**
* 根据id查询用户
*/
@RequestMapping(value= "person/{id}", method= {RequestMethod.GET})
public Person getPerson(@PathVariable(value="id")Integer id) {
Person p=null;
for (Person person : persons) {
if(id==person.getId()){
p=person;
break;
}
}
return p;
}
/**
* 使用post添加用户
*/
@RequestMapping(value= {"person"}, method= {RequestMethod.POST})
public String addPerson(Person person) {
persons.add(person);
println();
return "success";
}
/**
* 根据id删除用户
*/
@RequestMapping(value= {"person/{id}"}, method= {RequestMethod.DELETE})
public String deletePerson(@PathVariable(value="id")Integer id) {
Person p=null;
for (Person person : persons) {
if(id==person.getId()){
persons.remove(person);
break;
}
}
println();
return "success";
}
/**
* 使用PUT请求更新用户信息
*/
@RequestMapping(value= {"person"}, method= {RequestMethod.PUT})
public String updatePerson(Person person) {
for (Person p : persons) {
if(p.getId() == person.getId()) {
BeanUtils.copyProperties(person, p);
}
}
println();
return "success";
}
/**
* 批量查询
*/
@RequestMapping(value= {"person"}, method= {RequestMethod.PATCH})
public List<Person> loadPerson(Person person) {
return persons;
}
public void println() {
for (Person person : persons) {
System.out.println(person);
}
}
}
仔细看完上面的代码,比较之后,我们发现几个特点
特点一: RequestMapping请求映射中的value值,都是person或者person/{xxx}的形式
特点二: RequestMapping中多添加了一个属性method,并且使用RequestMethod枚举类型可以获取到不同的请求类型作为method的值
特点三: 当方法中的参数中包含基本数据包装类型的时候,都加上了注解@PathVariable
那我们一一的来分析讲解
先从第一个特点开始吧,还记得上面我们提供的一个原始url和规范后之后url对比的栗子吗?
原始url:http://www……/login.action?username=marco&password=123456
规范后的url:http://www…/item/marco/123456
再结合我们的注解中的这个值,我们是不是发现使用person/{userName}的形式,就相当于约定了页面上按url上的/
分割的参数顺序的属性名称,我们在url上只用展现它的值,这样做是不是变相的让数据传输更加的安全了呢?并且,url也更加简洁了
那么特点二,使用RequestMethod枚举的中枚举参数对不同操作类型的方法加以区分,我们之前不是还遗留一个问题没有解决吗?
POST可以搞定所有的事情,为什么还需要使用DELETE、PUT?
其实大家应该看出来了,我们一个方法就对应了一个操作,而加上了DELETE、PUT等标签后,就约束了我们的方法只能够接收请求方式为delete、put的请求。
因此,可以我们可以使用不同的请求方式,来区分Controller处理器中的操作,而且看到上面的枚举,也能够轻易的区别每一个方法的作用是什么
那最后一个特点三,使用@PathVariable的原因是在Spring 3.0后,没有办法获取方法上的参数的名称,怎么说呢,大家使用最新版的sts也发现了,当我们使用一个类实现了接口,自动重写的方法中的参数的名称是arg0、arg1、arg2或者是param1、param2、param3等等,因此,我们可以使用@PathVariable的方式,约定方法中的参数的名称,这个就类似与我们之前在Mybatis中讲到的@Param的注解的使用方式,相信大家应该都能懂啦~
第五步:新建jsp页面测试RestFul应用
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>
<input type="button" id="btn1" value="say">
<input type="button" id="btn2" value="得到id=2的Person 使用get方法">
<input type="button" id="btn3" value="添加Person 使用post方法">
<input type="button" id="btn4" value="删除id=4的Person 使用delete">
<input type="button" id="btn5" value="更新Person PUT">
<input type="button" id="btn6" value="全查询使用 PATCH">
<script type="text/javascript">
$("#btn1").click(function(){
$.ajax({
url:'say/marco',
type:'post',
success:function(obj){
alert(obj);
},
error:function(){
alert("请求失败");
}
})
});
$("#btn2").click(function(){
$.ajax({
url:'person/2',
type:'get',
success:function(obj){
alert(obj.name);
},
error:function(){
alert("请求失败");
}
})
});
$("#btn3").click(function(){
$.ajax({
url:'person',
type:'post',
data:{
id:11,
name:'川普',
address:'美国'
},
success:function(obj){
alert(obj);
},
error:function(){
alert("请求失败");
}
})
});
$("#btn4").click(function(){
$.ajax({
url:'person/4',
type:'delete',
success:function(obj){
alert(obj);
},
error:function(){
alert("请求失败");
}
})
});
$("#btn5").click(function(){
$.ajax({
url:'person',
type:'put',
data:{
id:1,
name:'普京',
address:'俄罗斯'
},
success:function(obj){
alert(obj);
},
error:function(){
alert("请求失败");
}
})
});
$("#btn6").click(function(){
$.ajax({
url:'person',
type:'patch',
success:function(obj){
alert(obj);
},
error:function(){
alert("请求失败");
}
})
});
</script>
</body>
</html>
我们来随便找几个测试下,比方说第二个,成功获取到了id=2的person的名字
再比方说第三个,是不是成功的添加了川普到我的队伍中?
还有一个问题差点忘了提了,就是关于PUT请求的问题,因为这个请求有bug,无法成功的执行该请求方式的request,因此Spring官方团队给到了一个补救的方案,就是在springmvc.xml中加上这个过滤器的配置
<filter>
<filter-name>HttpPutFormContentFilter</filter-name>
<filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HttpPutFormContentFilter</filter-name>
<servlet-name>springmvc</servlet-name>
</filter-mapping>
那配置完成后,我们再来测试下
当然也是no problem的啦,不成功的代码我会放上来吗?哈哈哈,那这一节就讲到这里~