SpringMVC-细节一(HelloWorld运行流程,url-pattern,@RequestMapping,Rest风格的URL)

一. HelloWorld运行流程

1)客户端点击链接会发送链接http://localhost:8080/springmvc/hello请求;

2)来到Tomcat服务器;

3)SpringMVC的前端控制器收到所有请求;

4)请求的地址和@RequestMapping标注的哪个匹配,来找到到底使用哪个类的哪个方法来处理;

5)前端控制器找到了目标处理器类和目标方法,直接利用反射执行目标方法;

6)方法执行完成后,会有一个返回值,SpringMVC认为这个返回值就是要去的页面地址;

7)拿到方法返回值以后,用视图解析器进行拼串得到完整的页面地址;

8)拿到页面地址,前端控制器转发页面。

二.url-pattern

在之前的HelloWorld实验中,了解到:

  •  /会拦截所有请求,但不会拦截*.jsp请求,能保证jsp访问正常。
  •  /*范围更大,还会拦截*.jsp这些请求,一旦拦截jsp页面就不能显示了。

  为什么/会拦截.html请求,而不会拦截*.jsp请求呢?首先我们要知道的是处理*jsp是Tomcat的事。所有项目下的小web.xml都是继承Tomcat的大web.xml。

  1)服务器的大web.xml中有一个DefaultServlet的url-pattern=/

  2) 前端控制器的url-pattern=/

DefaultServlet是Tomcat中处理静态资源的一个Servlet。除了JSP和Servlet外剩下的都是静态资源,index.html也是静态资源。现在前端控制器的/禁用了Tomcat的DefaultServlet。 静态资源会来到前端控制器看那个方法的RequestMapping是这个index.html。

JSP页面能运行是我们没有覆盖JspServlet的配置。

使用/不仅仅是因为能保证jsp访问正常,也是为了迎合Rest风格URL。

三.@RequestMapping映射

SpringMVC使用@RequestMapping注解为控制器指定可以处理哪些URL请求。在控制器的类定义以及方法定义域处都可以标注@RequestMapping。

  • 标记在类上:提供初步的映射信息。相当于WEB应用的根目录。
  • 标记在方法上:提供进一步的细分映射信息。相当于标记在类上的URL。

若类上未标注@RequestMapping,则方法标记的URL相当于WEB应用的根目录。

作用:DispatcherServlet截获请求后,就通过控制器上@RequestMapping提供的映射信息确定请求所对应的处理方法。

源码如下:

package org.springframework.web.bind.annotation;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String[] value() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}

标准的HTTP请求报头:

@ RequestMapping 除了可以使用请求 URL 映射请求外,还可以使用请求方法、请求参数及请求头映射请求
@ RequestMapping value method params heads 分别表示请求 URL、请求方法、请求参数及请求头的映射条件,他们之间是与的关系,联合使用多个条件可让请求映射更加精确化。

3.1 属性

3.1.1 method:规定请求方式

定义控制器方法:

@RequestMapping("/haha")
@Controller
public class RequestMappingTestController {
		
	/*
	 * RequestMapping的其他属性:
	 * method: 限定请求方式
	 *    Http协议中的所有请求方式: GET PSOT PUT PATCH DELETE OPTIONS
	 *  
	 *     method=RequestMethod.POST:只接受这种类型的请求,默认是什么都可以
	 *     不是规定的方式报错,4xx都是客户端错误
	 */
	
	@RequestMapping(value="/handle02",method=RequestMethod.POST)
	public String handle02(){
		return "success";
	}

}

以get方式请求:

<a href="haha/handle02">GET方式请求</a>

发生请求错误:

以POST方式请求成功:

<form action="haha/handle02" method="post">

   <input type="submit"/>
</form>

3.1.2 params:规定请求参数

params参数支持简单表达式:

  •  param1: 表示请求必须包含名为 param1 的请求参数。  eg:params={"username"} 发送请求的时候必须带上一个名为username的参数,如果没带404
  • !param1: 表示请求不能包含名为 param1 的请求参数。eg:params={"!username"} 发送请求的时候不携带一个名为username的参数,带了404
  •  param1 != value1: 表示请求包含名为 param1 的请求参数,但其值不能为 value1
  •  {“param1=value1”, “param2”}: 请求必须包含名为 param1 和param2 的两个请求参数,且 param1 参数的值必须为 value1
     
@RequestMapping(value="/handle03",params={"username=123","pwd","!age"})
	public String handle03(){
		return "success";
	}

3.1.3 headers:规定请求头

headers和params一样支持上述的简单表达式,也可以配置浏览器信息。

/*
	 * header:规定请求头,和params一样支持简单的表达式
	 * User-Agent:浏览器信息
	 * 实验 让IE能访问,让谷歌不能访问
	 * 谷歌 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
	 * IE:User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
	 */
	
	@RequestMapping(value="/handle04",headers={"User-Agent=Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"})
	public String handle04(){
		return "success";
	}

3.2 Ant风格的资源路径

Ant 风格资源地址支持 3 种匹配符:

?:能替代任意一个字符
 * *:能替代任意多个字符和一层路径
 * **:能替代多层路径

@RequestMapping支持Ant风格的URL:

package com.test.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/*
 * 模糊匹配功能
 * 
 * 
 * URL地址可以写模糊的通配符
 * 
 * ?:能替代任意一个字符
 * *:能替代任意多个字符和一层路径
 * **:能替代多层路径
 */
@Controller
public class RequestMappingTest {

	@RequestMapping("/antTest01")
	public String antTest01(){
		System.out.println("antTest01");
		return "success";
	}
	
	/*
	 * ?匹配一个字符,0个多个都不行;
	 *    模糊和精确多个匹配的情况下,精确优先
	 */
	@RequestMapping("/antTest0?")
	public String antTest02(){
		System.out.println("antTest02");
		return "success";
	}
	
	/*
	 * 匹配任意多个字符
	 */
	@RequestMapping("/antTest0*")
	public String antTest03(){
		System.out.println("antTest03");
		return "success";
	}
	
	/*
	 * 匹配一层路径
	 */
	@RequestMapping("/*/antTest0*")
	public String antTest04(){
		System.out.println("antTest04");
		return "success";
	}
	
	/*
	 * 匹配多层路径
	 */
	@RequestMapping("/**/antTest0*")
	public String antTest05(){
		System.out.println("antTest05");
		return "success";
	}
}

四.@PathVariable映射URL绑定的占位符

带占位符的URL是Spring3.0新增的功能,该功能在SpringMVC向REST目标挺进发展过程中具有里程碑的意义。

通过@PathVariable可以将URL中占位符参数绑定到控制处理方法的入参中。

/*
	 * 路径上有占位符:占位符 语法就是在任意路径的地方写一个{变量名}
	 * 路径上的占位符只能占一层路径
	 */
	@RequestMapping("/user/{id}")
	public String pathVariableTest(@PathVariable("id")String id){
		System.out.println("路径上的占位符"+id);
		return "success";
	}

五.Rest

5.1 简介

RESTRepresentational State Transfer资源)表现层状态转化。是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用

  • 资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。要获取这个资源,访问它的URI就可以,因此 URI 即为每一个资源的独一无二的识别符。
  • 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层(Representation)。比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式。
  • 状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是 “表现层状态转化”。具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。

Rest:系统希望以非常简洁的URL地址来发请求,怎样表示对一个资源的增删改查用请求方式来区分。简洁的URL提交请求,以请求方式区分对资源的操作。

我们之前的URL一般这样编写:

getOrder?id=1 GET
deleteOrder?id=1 GET
updateOrder POST 
saveOrder POST

Rest推荐URL地址这样起名:/资源名/资源标识符:

/order/1  HTTP GET :得到 id = 1 的 order   
/order/1  HTTP DELETE:删除 id = 1的 order   
/order/1  HTTP PUT:更新id = 1的 order   
/order     HTTP POST:新增 order 

5.2 Rest风格增删改查环境搭建

和之前创建SpringMVC项目一样,创建动态Web项目,配置web.xml和springmvc-servlet.xml。前面创建步骤基本一致。

创建index.jsp页面,在这个页面发出Rest风格的URL请求:

<%@ 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>
</head>
<body>
<!-- 发起图书的增删改查请求,使用Rest风格的URL地址:
               请求URL  请求方式             表示含义
     /book/1 GET:     查询1号图书
     /book/1 DELETE:  删除图书
     /book/1 OUT:     更新1号图书
     /book POST:    添加1号图书

 -->
   <a href="book/1">查询图书</a><br/>
   <form action="book" method="post">
   
   <input type="submit" value="添加一号图书"/>
   </form>
 
   <a href="book/1">删除图书</a><br/>
   <a href="book/1">更新图书</a><br/>
</body>
</html>

处理器:

package com.test.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class BookController {

	@RequestMapping(value="/book",method=RequestMethod.POST)
	public String addBook(){
		System.out.println("添加了新的图书");
		return "success";
	}
	
	/*
	 * 删除图书
	 */
	@RequestMapping(value="/book/{bid}",method=RequestMethod.DELETE)
	public String deleteBook(@PathVariable("bid")Integer id){
		System.out.println("删除了"+id+"号图书");
		return "success";
	}
	
	/*
	 * 图书更新
	 */
	@RequestMapping(value="/book/{bid}",method=RequestMethod.PUT)
	public String updateBook(@PathVariable("bid")Integer id){
		System.out.println("更新了"+id+"号图书");
		return "success";
	}
	
	/*
	 * 处理查询图书
	 */
	@RequestMapping(value="/book/{bid}",method=RequestMethod.GET)
	public String getBook(@PathVariable("bid")Integer id){
		System.out.println("查询到了"+id+"号图书");
		return "success";
	}
}

测试执行后,会发现删除和更新的请求并没有到达相对应的方法,而是去了GET对应的处理方法。这是因为:浏览器 form 表单只支持 GET 与 POST 请求,而DELETE、PUT 等 method 并不支持。

5.3 支持REST风格的过滤器:可以将POST请求转换为PUT或DELETE请求

这个步骤如下:

 1)SpringMVC中有一个Filter,可以把普通的请求转化为规定形式的请求:在web.xml中配置filter(HiddenHttpMethodFilter)

      <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>

2)如何发起其他形式请求
                        (1)创建一个post类型的表单
                        (2)表单项中携带一个_method的参数
                        (3)这个_method的值就是DELETE或PUT

 
 <!-- 发送DELETE请求 -->
   <form action="book/1" method="post">
   <input name="_method" value="delete">
   <input type="submit" value="删除一号图书"/>
   </form><br>
 
 <!-- 发送PUT请求 -->
     <form action="book/1" method="post">
   <input name="_method" value="put">
   <input type="submit" value="删除一号图书"/>
   </form><br>

5.3.1 HiddenHttpMethodFilter源码解析

为什么请求隐含参数名称必须叫做”_method”?form表单的值又是如何转换成隐含参数_method的值的?源码解析如下:

public class HiddenHttpMethodFilter extends OncePerRequestFilter {

	public static final String DEFAULT_METHOD_PARAM = "_method";
	private String methodParam = DEFAULT_METHOD_PARAM;	

@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
        //获取表单_method带来的值
		String paramValue = request.getParameter(this.methodParam);
        //如果表单是一个post,并且_method有值
		if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
            //转为大写PUT,DELETE
			String method = paramValue.toUpperCase(Locale.ENGLISH);
            //重写了request.getMethod(); 
			HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
            //wrapper.getMethod==PUT,DELETE
			filterChain.doFilter(wrapper, response);
		}
		else {
            //直接放行
			filterChain.doFilter(request, response);
		}
	}
...
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值