REST风格
资源
资源
是一种看待服务器的方式,服务器可以看作是由很多离散的资源(文件)组成,一个资源可以由一个或多个URI来标识,URI既是资源的名称也是资源在Web上的地址
- 对某个资源感兴趣的客户端应用只需要通过访问资源的URI就能与其进行交互, URI就是每一个资源的独一无二的识别符
资源的表述
: 对于资源在某个特定时刻的表现形式可以用不同的格式描述, 以便资源在客户端(请求)-服务器端(响应)之间转移/交换
- 文本资源可以用txt格式、HTML格式、XML格式、JSON格式,甚至是二进制格式表现,具体采用什么格式可以通过协商机制来确定
资源状态转移
: 资源(数据)在客户端和服务器传输时格式会发生转化, 通过改变资源的格式状态和操作资源的表述(增删改查)来间接实现操作资源的目的
- HTTP协议是一个无状态协议,资源所有的状态都保存在服务器端,如果客户端想要操作服务器的资源,就需要改变对应资源在服务器中的状态/格式
RESTful
REST风格
是一种将URL地址中从前到后的各个单词使用斜杠分开的风格设计,不再将请求参数放到?
后面,而是将请求参数作为URL地址的一部分
- 优点: 请求地址书写简化,隐藏了资源的访问行为,无法通过地址得知该资源是何种操作
- 按照REST风格访问资源称为
RESTful
,面对相同的URL地址可以使用行为动作
区分对资源进行了何种操作,GET(查询),POST(新增),PUT(修改),DELETE(删除)
操作 | 传统方式 | REST风格 | 提交的数据 | 响应的数据 |
---|---|---|---|---|
根据id查询操作 | getUserById?id=1 | user/1+GET请求方式 | 一般以键值对格式提交数据 | 状态码: 200 响应体:单条或多条查询信息 |
新增操作 | saveUser | user+POST请求方式 | 以键值对或JSON格提交数据 | 状态码: 201(或200) 响应体:新增后的数据 |
根据id删除操作 | deleteUser?id=1 | user/1+DELETE请求方式 | 一般以键值对格式提交数据 | 状态码: 204 响应体: 无 |
更新操作 | updateUser | user+PUT请求方式 | 以键值对或JSON格式提交数据 | 状态码: 201(或200) 响应体:修改后的数格 |
静态资源的映射
DefaultServlet
是Tomcat中专门处理静态资源(除jsp和servlet)请求的Servlet, 遇到这些静态资源请求时Tomcat会在服务器下找到这个资源并返回
- 所有web工程的web.xml文件的配置都是继承于Tomcat服务器的web.xml配置,如果我们修改了当前web工程的配置,就会覆盖Tomcat服务器的web.xml配置
若DispatcherServlet的url-pattern=/
表示代替DefaultServlet处理静态资源请求
- DispatcherServlet前端控制器处理请求的方式是看哪个控制器方法的请求映射的路径是这个静态资源名,找到则执行控制器方法找不到则报错
<servlet><servlet-name>default</servlet-name><servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class><init-param><param-name>debug</param-name><param-value>0</param-value></init-param><init-param><param-name>listings</param-name><param-value>false</param-value></init-param><load-on-startup>1</load-on-startup>
</servlet><servlet-mapping><servlet-name>default</servlet-name><!--DefaultServlet可以处理除jsp外的所有请求--><url-pattern>/</url-pattern>
</servlet-mapping>
需求: 当处理访问静态资源(html、js、css、jpg)的请求时先由前端控制器处理,如果找不到请求映射的控制器方法就交由默认的DefaultServlet处理,还找不到则报错
<!--设置所有的请求由默认的DefaultServlet处理,此时前端控制器就不能处理请求了-->
<mvc:default-servlet-handler />
<!--开启mvc注解驱动,当处理访问静态资源的请求时先由前端控制器处理,如果找不到请求映射的控制器方法就交由默认的DefaultServlet处理-->
<mvc:annotation-driven />
请求转换过滤器
注册HiddenHttpMethodFilter
HTTP协议中有GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE几种请求,但是浏览器只支持发送GET和POST两种方式的请求
-
在浏览器地址栏上发起的都是GET请求
-
使用表单的method属性只能设置GET和POST两种请求方式,如果method属性值设置了其他的请求方式或者不设置请求方式那么此时发起的都是GET请求
<form th:action="@{/testPut}" method="put"><!--表单实际发起的是GET请求--><input type="submit" value="测试form表单是否能够发送put或delete请求方式">
</form>
使用SpringMVC提供的HiddenHttpMethodFilter
可以帮助我们将POST请求转换为DELETE,PUT.PATCH方式的请求
,但是需要满足两个条件
- 第一: 由于HiddenHttpMethodFilter只能转换POST请求,所以当前请求的请求方式必须为POST,那就需要创建一个表单
- 第二: POST请求必须携带
_ method
请求参数,value值可以是DELETE,PUT,PATCH
,一般通过隐藏域隐藏_ method请求参数
第一步: 在web.xml中注册HiddenHttpMethodFilter
过滤器,这个过滤器必须要在CharacterEncodingFilter
之后执行
- CharacterEncodingFilter过滤器设置字符集的编码时,要求request对象之前不能有任何获取请求参数的相关操作,否则设置的字符集编码无效无效
- HiddenHttpMethodFilter恰恰有一个获取
_ method
请求参数的值的操作request.getParameter(this.methodParam)
<!--把POST请求转化为其他请求的Filter-->
<filter><filter-name>HiddenHttpMethodFilter</filter-name><filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping><filter-name>HiddenHttpMethodFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>
第二步: 使用form表单发起POST请求,同时携带请求参数_ method,请求参数的值就是我们实际要发送的请求方式
<!--将POST请求转换为PUT请求 -->
<form th:action="@{/user}" method="post"><input type="hidden" name="_method" value="put"><input type="submit" value="修改用户信息">
</form><!--将POST请求转换为DELETE请求 -->
<form th:action="@{/user/1}" method="post"><!--_method请求参数的value值为最终的请求方式 --><input type="hidden" name="_method" value="delete"/><input type="submit" value="删除用户信息">
</form>
HiddenHttpMethodFilter原理
HiddenHttpMethodFilter过滤器
会将请求参数_ method
的值全部大写取替换request中method属性
的值,因此请求参数_method
的值才是最终的请求方式
public class HiddenHttpMethodFilter extends OncePerRequestFilter {// ALLOWED_METHODS是个List集合,集合中的元素是PUT,DELETE,PATCHprivate static final List<String> ALLOWED_METHODS =Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),//HttpMethod是枚举类型HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));public static final String DEFAULT_METHOD_PARAM = "_method";private String methodParam = DEFAULT_METHOD_PARAM;public void setMethodParam(String methodParam) {Assert.hasText(methodParam, "'methodParam' must not be empty");this.methodParam = methodParam;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {// 将原生的request做一个备份,便于后续操作HttpServletRequest requestToUse = request;// 判断当前的请求方式是否是POST请求,并且请求域中有没有异常对象,满足条件则执行过滤规则if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {// this.methodParam的值是"_method",所以就是获取请求参数_method的值即我们指定的请求方式String paramValue = request.getParameter(this.methodParam);// 判断请求参数_method的值是否不为null或空字符串,满足条件则对备份的requestToUse对象重新包装if (StringUtils.hasLength(paramValue)) {// 是个List集合,元素是PUT,DELETE,PATCH// 将我们指定的请求方式转换为大写String method = paramValue.toUpperCase(Locale.ENGLISH);// 判断ALLOWED_METHODS集合中是否包含我们指定的请求方式if (ALLOWED_METHODS.contains(method)) {// 重新包装一个请求对象,重写了getMethod方法即改掉了method属性的值requestToUse = new HttpMethodRequestWrapper(request, method);}}}// 放行执行下一个过滤器或者Servlet,如果当前请求不是POST请求,此时的requestToUse对象等同于原来request对象// 如果当前请求是POST请求,此时的requestToUse对象是我们包装后的request对象,将method属性的值替换成我们指定的请求方式了filterChain.doFilter(requestToUse, response);}// 对原生的request对象进行包装private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {// 请求方式private final String method;public HttpMethodRequestWrapper(HttpServletRequest request, String method) {super(request);// 此时的method属性的值_method属性的值即我们指定的请求方式this.method = method;}@Override// 重写父类的getMethod方法,以后我们调用request对象getMethod方法返回的就是我们指定的请求方式public String getMethod() {return this.method;}}
}
@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){// 根据Id删除对于的员工employeeDao.delete(id);// 删除完员工后需要重定向到查询所有员工功能的请求,因为员工数据有变化需要重新查询并渲染// 重定向的目的就是为了刷新地址栏,防止用户一直刷新添加员工的请求return "redirect:/employee";
}