@RequestMapping 详解
在 Spring-mvc 中,@RequestMapping 是用来映射请求的,我们知道 Servlet 原始的开发方式中,可以通过 @WebServlet 注解指定该 Servlet 的映射访问地址。而 @RequestMapping 类似也是这样,并且提供了更方便的方式,使得我们在编写 Servlet 更关注业务代码。
源码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
通过观察源码,我们发现 @RequestMapping 注解可以用在方法、类上(ElementType.METHOD, ElementType.TYPE),header (请求头信息)、params(参数信息)、method(指定提交方式)
详细使用
指定映射地址
-
在方法上指定,可以通过指定 value 的值,来表示请求的路径,如果请求了对应的路径,就会执行这个被注解的方法(test)
//表示一个控制器,把 TestController 的实体类交给 Spring 管理。 @Controller public class TestController { //指定映射地址 @RequestMapping("/test") public String test(){ System.out.println("我是测试方法 1 "); return "success.jsp"; } }
如果我们把注解定义在方法上,这个方法就是目标地址,那么这个地址的 URL 怎么写?(这里的 helloworld 是项目路径)
http://localhost:8080/helloworld/test
-
在类上定义
在上面的例子是,定义在方法上,直接给方法指定请求地址。
而在类上定义,提供了初步的映射请求URL,表示此请求处理器中所有的目标方法都在此URL下访问
//表示一个控制器,把 TestController 的实体类交给 Spring 管理。 @Controller @RequestMapping("/demo") public class TestController { //指定映射地址 @RequestMapping("/test") public String test(){ System.out.println("我是测试方法 1 "); return "success.jsp";//指向另一个界面给客户端 } }
那么这个地址的 URL 怎么写?(这里的 helloworld 是项目路径)
http://localhost:8080/helloworld/demo/test
这里的请求的路径就变成了 demo 下的 test 了
-
总结:
- 目标方法真实的映射URL信息 = 类定义处映射的URL+方法定义出映射的URL。
-
细节:
- 不管是类定义出映射的URL还是方法定义出映射的URL都可以省略第一个
\
符号。
- 不管是类定义出映射的URL还是方法定义出映射的URL都可以省略第一个
设置请求方式
在通过源码,我们看的注解中,还有一个属性 method,这个是用来设置请求方式的。指定了请求方式的目标方法只能处理指定的请求方式。
如果我们需要指定为 post 请求,可以这样写 @RequestMapping(value="url",method=RequestMethod.POST)
//表示一个控制器,把 TestController 的实体类交给 Spring 管理。
@Controller
@RequestMapping("/demo")
public class TestController {
//指定映射地址
@RequestMapping(value="/test",method=RequestMethod.POST)
public String test(){
System.out.println("我是测试方法 1 ");
return "success.jsp";//指向另一个界面给客户端
}
}
- method 还有其他参数
RequestMethod.POST
,表示此处理方法只能够处理 POST 请求RequestMethod.GET
,表示此处理方法只能够处理 GET 请求RequestMethod.DELETE
,表示此处理方法只能够处理DELETE请求RequestMethod.PUT
,表示此处理方法只能够处理PUT请求
请求参数
获取请求参数
在 Servlet 中会通过 request.getParameter (" 参数名 ") 的方式在 Servlet 接收请求参数。那么 Spring MVC 还需要这样?
Spring MVC 对参数的获取进行了封装。使得获取请求的参数非常的方便。
假设这里有一个表单
<form action="${pageContext.request.contextPath}/helloworld/test1">
名称:<input type="text" name="name"><br>
密码:<input type="text" name="password"><br>
<input type="submit" value="提交"><br>
</form>
那么怎么获取 name 和 password 的值?Spring MVC 有一个约定
-
目标方法的参数名称和请求参数的名称一致,就会自动匹配
所以我们的目标方法可以这样写
//表示一个控制器,把 TestController 的实体类交给 Spring 管理。 @Controller @RequestMapping("/demo") public class TestController { //指定映射地址 @RequestMapping(value="/test",method=RequestMethod.POST) public String test(String name,String password){ System.out.println("名称:"+name+" 密码:"+password); return "success.jsp";//指向另一个界面给客户端 } }
如果参数名称就是不同?怎么办?
-
**没事,我们还可以绑定对应的参数名称,具体就是使用
@RequestParam
注解。指定匹配的请求参数名称。**上面的 html 代码修改一下
<form action="${pageContext.request.contextPath}/helloworld/test1">
名称:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
<input type="submit" value="提交"><br>
</form>
这里就修改了两个控件的 name 属性,在 目标方法中,只需这样
//表示一个控制器,把 TestController 的实体类交给 Spring 管理。
@Controller
@RequestMapping("/demo")
public class TestController {
//指定映射地址
@RequestMapping(value="/test",method=RequestMethod.POST)
public String test(@RequestParam(value="username",required=false) String name,String password){
System.out.println("名称:"+name+" 密码:"+password);
return "success.jsp";//指向另一个界面给客户端
}
}
这样参数不同也可以传递了。解释一下 @RequestParam
注解
-
value 属性:用来指定表单或者 url 参数中的参数名。
-
required 属性:
-
值为 true 或 false
-
值为 true 时候,指定客户端请求时,如果没有带该参数(上面的是 username),那么将会报 400 错误
-
值为 false 时候,那么当没有该参数时候,会赋值为 null (这里也就建议当参数类型为基本类型时,最好换成包装类)
-
-
defaultValue 属性:
- 表示设置默认值,如果设置了默认值,即使 required 为 true,也不会报错。
param 属性
上面的 @RequestParam 可以用来对参数绑定和要求带有哪些参数,对于要求带有哪些参数,可以通过 @RequestMapping 中的 param 属性设置。
//表示一个控制器,把 TestController 的实体类交给 Spring 管理。
@Controller
@RequestMapping("/demo")
public class TestController {
//保存
@RequestMapping(value = "/test1",params = {"name","money"})
@ResponseBody
public String test1(@RequestParam("name") String n, String money){
System.out.println(n);
System.out.println(money);
return "成功";
}
}
这里的 param 有两个请求参数,分别是 name 和 money,如果客户端请求没有这两个,那么回报 400 错误,和上面的一样。下面还有几种情况
- params = {“name=小王”,“money”}
- 表示必须带有这两个请求参数,并且 name 的值为 小王
- params = {“name=!小王”,“money”}
- 表示必须带有这两个请求参数,并且 name 的值不能为 小王
- params = {"!name",“money”}
- 表示带有的请求参数必须有 money,并且不能用 name 的请求参数。
请求头
headers 属性
通过在 @RequestMapping 的 headers 属性指定请求头。因为请求头有多个,可以使用一个数组表示。
例如我这里指定请求头中 Connection 的值必须为 keep-alive ,那么可以如下所示
//表示一个控制器,把 TestController 的实体类交给 Spring 管理。
@Controller
@RequestMapping("/demo")
public class TestController {
//保存
@RequestMapping(value = "/test2",headers = {"Connection=keep-alive"})
@ResponseBody
public String test2(String name, String money){
System.out.println(name);
System.out.println(money);
return "成功";
}
}
如果请求头的 Connection 不是 keep-alive 则会报 404 错误。
@RequestHeader
和上面的不同意思,因 header 参数可以用来设置和现代,而这个注解可以注解在参数上,进行获取头信息。如下:
//表示一个控制器,把 TestController 的实体类交给 Spring 管理。
@Controller
@RequestMapping("/demo")
public class TestController {
@RequestMapping(value = "/test3",headers = {"Connection=keep-alive"})
@ResponseBody
public String test3(@RequestParam("name") String n, String money,@RequestHeader("Connection") String header){
System.out.println(n);
System.out.println(money);
System.out.println(header);
return "成功";
}
}
上面代码中,使用 @RequestHeader 注解 header ,指定 value 值来获取 Connection 的值。
Ant 风格
什么是 Ant 风格?其实也就在映射路径中,可以使用通配符
?
表示匹配一个字符*
表示匹配任意个字符**
表示匹配多次路径的任意字符
例如:
//表示一个控制器,把 TestController 的实体类交给 Spring 管理。
@Controller
@RequestMapping("/demo")
public class TestController {
@RequestMapping(value = "/test4/*/one"})
@ResponseBody
public String test4(@RequestParam("name") String n, String money){
System.out.println(n);
System.out.println(money);
return "成功";
}
}
如上的 @RequestMapping ,我们的请求地址如果是 /demo/test4/aaa/one
或者 /demo/test4/d/one
都能够匹配,也就是只要请求地址是 /demo/test4/任意字符/one
都能够匹配。
同理,如果是 ** ,那么请求地址 /demo/test4/a/b/c/one
也能够匹配,也就是 /demo/test4/任意字符多级路径/one
都能够匹配。
Restful 风格
Restful (Representational state transfer )资源表现层状态转换,是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
我们都知道 http 是无状态协议。所有的状态都存在于服务器。因此客户端想操作服务队,必须通过某种手段,让服务器端发生状态转换,而这种转换建立在表现层,所以叫做资源表现层的状态转换。具体的就是,在 http 协议中
- GET: 用于获取资源
- POST: 用于新建资源
- PUT: 用于更新资源
- DELETE:用于删除资源
http 协议存在 get 和 post 请求,如果发送 put 和 delete 请求,Spring MVC 可以将 post 请求转换为 put 或 delete 请求。
例子:
- get 请求查询数据:
/user/1
表示查询 id=1(假设这里的 1 指的是 id)的 user 资源 - post 请求新增数据:
/user
表示新增user - put 请求修改数据:
/user/1
表示修改 id=1(假设这里的 1 指的是 id)的 user 资源 - delete 请求删除数据:
/user/1
表示删除 id=1(假设这里的 1 指的是 id)的 user 资源
那么如何使用?如何把 post 转为为 put 请求和 delete 请求?
先介绍一个注解
@PathVariable
该注解是用来映射 URL 中的参数占位符。Spring 3.0 添加的功能,对于Spring 在 Restful 风格的使用占据了里程碑式的意义。Spring 通过 @PathVariable 对 URL 中的占位符参数绑定到目标方法的形参中。
简单应用:例如我的 url 如下
http://localhost:8080/SSM01_war/demo/user/1
我的 1 是参数,那么对于目标方法怎么写?
@Controller
@RequestMapping("/demo")
public class DemoController {
@RequestMapping(value="/user/{id}",method = RequestMethod.GET)
public String selectId(@PathVariable("id") String id){
System.out.println("查询数据: "+id);
return "/user/success";
}
}
只要把映射地址中,需要指定的参数,换成以 /
方式分割参数,对于要绑定的参数写成 {参数名}
即可。然后在目标方法的入参中,用注解 @PathVariable 对形参进行绑定就完成了。
请求地址 http://localhost:8080/SSM01_war/demo/user/1
在请求方式是 get 的情况下,会自动匹配到 /SSM01_war/demo/user/{id}
,然后执行对应的目标方法。
那么对于如何 post 转 put 和 delete 如何操作呢?
使用
步骤
- 配置 HiddenHttpMethodFilter 过滤器
- 书写界面
- 书写目标方法
配置 HiddenMethodFilter 过滤器
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframeword.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
书写界面
-
get 请求
<a href="${pageContext.request.contextPath}/demo/user/1"> get 请求 </a>
-
post 请求
<form action="${pageContext.request.contextPath}/demo/user" method="post"> <input type="submit" value="post 请求"><br> </form>
-
put 请求
<form action="${pageContext.request.contextPath}/demo/user/1" method="post"> <input type="hidden" value="put" name="_method"> <input type="submit" value="put 请求"><br> </form>
-
delete 请求
<form action="${pageContext.request.contextPath}/demo/user/1" method="post"> <input type="hidden" value="delete" name="_method"> <input type="submit" value="delete 请求"><br> </form>
通过上面的代码,你能发现一点不一样的?没错,就是 put 和 delete 的请求中,都添加了一个 隐藏控件,并且 name 属性都为 _method。
可以说,过滤器过滤了带有 _method 属性名的空间,如果有属性的 name 为 _method 的话,需要进行判断是否为 put 和 delete 请求。
查看 HiddenHttpMethodFilter 的源码。(这里就贴部分的,也就是 doFilter 方法,因为是过滤器)
public static final String DEFAULT_METHOD_PARAM = "_method"; private String methodParam = "_method"; ...... protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest requestToUse = request; // 如果是 post 请求 if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) { // 获取请求参数名为 _method 的 value 值。 String paramValue = request.getParameter(this.methodParam); if (StringUtils.hasLength(paramValue)) { requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, paramValue); } } filterChain.doFilter((ServletRequest)requestToUse, response); } private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper { private final String method; public HttpMethodRequestWrapper(HttpServletRequest request, String method) { super(request); // 将请求的值都转为大写 this.method = method.toUpperCase(Locale.ENGLISH); } public String getMethod() { return this.method; } }
可以得出三个结论
- 控件必须带有 _method
- 参数值 put 和 delete 有大小写限制吗?没有,通过上面源观察,我们发现它都是会通过 toUpperCase() 方法来转为大写
- 一定要使用隐藏控件?
- 不一定,你可以发现,上面的代码只是通过 getParamete() 方法来获取值,所以可以不用。但是,如果你是显示了控件的话,比如是 text,那么 value 的值就会被用户修改,达不到想要的效果。
-
书写目标方法
@Controller @RequestMapping("/demo") public class DemoController { @RequestMapping(value="/user/{id}",method = RequestMethod.GET) public String selectId(@PathVariable("id") String id){ System.out.println("查询数据: "+id); return "/user/success"; } @RequestMapping(value = "/user",method = RequestMethod.POST) public String insert(){ System.out.println("新增数据"); return "/user/success"; } @RequestMapping(value = "/user/{id}",method = RequestMethod.PUT) public String update(@PathVariable("id") String id){ System.out.println("更新数据: "+id); return "/user/success"; } @RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE) public String delete(@PathVariable("id") String id){ System.out.println("删除数据: "+id); return "/user/success"; } }
这里的映射地址都是一样的。唯一不同的就是 method 是不一样的。指定请求方式不同,在对于 put 的时候,Spring MVC 会自动转换。
请求地址
http://localhost:8080/SSM01_war/demo/user/1
如果请求方式是 get 的话,会自动匹配到/SSM01_war/demo/user/{id}
,然后根据对于的请求方式去找对应的目标方法。
@CookValue
使用 @CookieValue 可以获取 Cookie 信息。
@RequestMapping("testCookie")
public String testCookie(@CookieValue("JSESSIONID") Object value) {
System.out.println(value.toString());
return "success.jsp";
}
属性
- value:cook 的属性名称
将ServletAPI作为入参
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request,HttpServletResponse response,HttpSession session) {
System.out.println(session.getId());
return SUCCESS;
}
获取 POJO 类型参数
Controller 中的业务方法的 POJO 属性名称要与请求参数的name一致, 参数值会自动映射匹配。
http://localhost:8080/user/quick9?username=zhangsan$age=12
@RequestMapping(value = "/quick12")
@ResponseBody
private void save12(User user) throws IOException {
System.out.println(user);
}