Sping MVC 框架学习二:Spring MVC 中 @RequestMapping、@PathVariable、@RequestParam 等常用注解用法

 

@RequestMapping 注解用法

 

在POJO类定义处标注 @Controller,再通过 <context:component-scan/> 扫描相应的类包,即可使POJO成为一个能处理 HTTP 请求的控制器。

你可以创建数量不限的控制器,分别处理不同的业务逻辑,如 LoginController、UserController、ForumController等。每个控制器可拥有多个处理请求的方法,每个方法负责不同的请求操作。如何将请求映射到对应的控制器方法中是 Spring MVC 框架最重要的任务之一,这项任务就是由 @RequestMapping 来承担的。DispatchServlet 截获请求后,就通过控制器上 @RequestMapping 提供的映射信息确定请求所对应的处理方法。

 

在控制器的 类定义处 及 方法定义处 都可标注 @RequestMapping,它们分别代表:

1. 类定义处:提供初步的请求映射信息。相对于 WEB 应用的根目录。

2. 方法处:提供进一步的细分映射信息。相对于类定义处的 URL。若类定义处未标注 @RequestMapping,则方法标记处的 URL 相对于 WEB 应用的根目录。

 

 1 @Controller
 2 public class HelloWorld 
 3 {
 4     private static final String SUCCESS = "success";
 5     
 6   @RequestMapping("/helloworld")
 7     public String hello()
 8     {
 9             ... ...
10    }
1 1     <!-- 类定义处未添加映射信息时,只需匹配方法处的映射信息 -->
2 2     <a href="helloworld">Hello World</a>

 

 1 @RequestMapping("/springmvc")
 2 @Controller
 3 public class HelloWorld 
 4 {
 5     private static final String SUCCESS = "success";
 6 
 7     @RequestMapping("/helloworld")
 8     public String hello()
 9     {
10             ... ...
1     <!-- 类定义处添加了映射信息时,需把类定义处与方法定义处的映射信息都写上 -->
2     <a href="springmvc/helloworld">Hello World</a>

 

 

@RequestMapping 除了可以使用请求 URL 映射之外,还可以使用请求方法、请求参数及请求头映射请求。

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

 

1. method属性

 1 @RequestMapping("/springmvc")
 2 @Controller
 3 public class SpringTest
 4 {
 5     private static final String SUCCESS = "success";
 6     
 7     //使用 method 属性来指定请求方式
 8     @RequestMapping(value="/testMethod", method=RequestMethod.POST)
 9     public String testMethod()
10     {
11         System.out.println("Test Method");
12         return SUCCESS;
13     }
14 }
<!-- 编写一个index.jsp请求页面,以post方式提交一个表单 -->
<form action="springmvc/testMethod" method="post"> <input type="submit" value="submit"/> </form>

编写一个 success.jsp 跳转结果页面

 1 <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
 2     pageEncoding="ISO-8859-1"%>
 3 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 4 <html>
 5 <head>
 6 <title>Insert title here</title>
 7 </head>
 8 <body>
 9     <h4>Success Page</h4>
10 </body>
11 </html>

点击 “submit” 按钮可以看到 成功跳转至 success.jsp 页面,同时控制台打印出 Test Method。

注:若此时请求为 GET 请求,则程序会报错。

 

2. params 和 headers 属性

它们支持简单的表达式

1. param1:表示请求必须包含名为 param1 的请求参数

2. !param1:表示请求不能包含名为 param1 的请求参数

3. param1 != value1:表示请求包含名为 param1 的请求参数,但其值不能为 value1

4. {"param1=value1", "param2"}:请求必须包含名为 param1 和 param2 的两个请求参数,且param1 参数的值必须为 value1

 

 1 @RequestMapping("/springmvc")
 2 @Controller
 3 public class SpringTest
 4 {
 5     private static final String SUCCESS = "success";
 6     //测试params属性
 7     @RequestMapping(value="/testParamAndHeader", params={"username", "age!=10"})
 8     public String testParamAndHeader()
 9     {
10         System.out.println("Test ParamAndHeader");
11         return SUCCESS;
12     }
13 }
    <!-- 当age值不为10时,程序能正常执行,若将age值改为10,则程序将不能正常执行 -->
    <a href="springmvc/testParamAndHeader?username=a&age=1">Test ParamAndHeader</a>

 

3. heads属性

1     //此时的 headers 属性能使程序正常执行,若将属性值改为 Accept-Language:en-US,zh;q=0.8 ,程序将报错。请求jsp使用上面的jsp请求即可
2     @RequestMapping(value="/testParamAndHeader", params={"username", "age!=10"}, headers={"Accept-Language:zh-CN,zh;q=0.8"})
3     public String testParamAndHeader()
4     {
5         System.out.println("Test ParamAndHeader");
6         return SUCCESS;
7     }

 

 

@RequestMapping 不但支持标准的URL,还支持 Ant 风格的 URL

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

1. ?:匹配文件名中的一个字符

2. *:匹配文件名中的任意字符

3. **:匹配多层路径

@RequestMapping 支持 Ant 风格的URL:

1. /user/*/createUser:匹配如 /user/aaa/createUser、/user/bbb/createUser 等 URL

2. /user/**/createUser:匹配如 /user/createUser、/user/aaa/bbb/createUser 等 URL

3. /user/createUser??:匹配如 /user/createUseraa、/user/createUserbb 等 URL

 

在前面的SpringTest类中添加如下方法,在index.jsp页面增加 Test AntPath 超链接

1     @RequestMapping("/testAntPath/*/abc")
2     public String testAntPath()
3     {
4         System.out.println("Test AntPath");
5         return SUCCESS;
6     }
    <a href="springmvc/testAntPath/xyz/abc">Test AntPath</a>

启动服务器,程序可以正常运行,将超链接中xyz改为其他任何字符,程序也是可以运行的

 

 

@PathVariable 注解用法

@RequestMapping 还支持带占位符的 URL,该功能在 Spring MVC 向 REST 目标挺进的发展过程中具有里程碑的意义。

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

 

在SpringTest中新增一个方法,相应index.jsp页面增加一个超链接

1     //可以映射URL中的占位符到目标方法的参数中
2     @RequestMapping("/testPathVariable/{id}")
3     public String testPathVariable(@PathVariable("id") Integer id)
4     {
5         System.out.println("testPathVariable:" + id);
6         return SUCCESS;
7     }
<a href="springmvc/testPathVariable/1">Test PathVariable</a>

 

类定义处的 @RequestMapping 的 URL 如果使用占位符的参数,也可以绑定到处理方法的入参中

 1 @RequestMapping("/springmvc/{Msg}")
 2 @Controller
 3 public class SpringTest
 4 {
 5     private static final String SUCCESS = "success";
 6 
 7     @RequestMapping("/testPathVariable/{id}")
 8     public String testPathVariable(@PathVariable String Msg, @PathVariable Integer id)
 9     {
10         System.out.println("testPathVariable:" + Msg + ", " + id);
11         return SUCCESS;
12     }
    <a href="springmvc/Msg/testPathVariable/1">Test PathVariable</a>

由结果可以看 URL中的占位符已经绑定到控制器处理方法的入参中了

注:为避免传递参数时可能出现的空指针异常,@PathVariable 参数接收类型最好使用对象类型(原声类型使用其包装类型)

 

 

@RequestParam 注解用法

Spring MVC 通过分析处理方法的签名,将HTTP请求信息绑定到处理方法的相应的入参中,并根据方法的返回值类型做出相应的后续出理。

前面我们讲过的 @PathVariable 是一个 REST 风格的注解,它通过占位符放入方式携带一个参数,但这个参数不是正真意义上的请求参数。

在处理方法入参处使用 @RequestParam 可以把请求参数传递给请求方法,它有三个参数

1. value:参数名

2. required:是否必须,默认为true,表示请求中必须包含对应的参数名,如果不存在将抛出异常

3. defaultValue:默认参数,表示如果请求中没有同名参数时的默认值。设置该参数时,自动将 required 设置为 false。不推荐使用

 

SpringTest类新增如下方法,此时请求参数的键值必须与@RequestParam中value参数相同

1     @RequestMapping(value="/testRequestParam")
2     public String testRequestParam(@RequestParam(value="username")String name,
3             @RequestParam("age")Integer age)
4     {
5         System.out.println("testRequestParam:" + name + ", " + age);
6         return SUCCESS;
7     }

index.jsp新增超链接

    <a href="springmvc/testRequestParam?username=tom&age=10">Test RequestParam</a>

启动服务器,程序正常运行。此时如果我们将超链接改为

    <a href="springmvc/testRequestParam?username=tom">Test RequestParam</a>

程序执行时报错,解决方法在 @RequestParam 添加属性 required=false,或者还可以使用 defaultValue 为参数赋默认值

    @RequestMapping(value="/testRequestParam")
    public String testRequestParam(@RequestParam(value="username")String name,
            @RequestParam(value="age", required=false, defaultValue="0")Integer age)
    {
        System.out.println("testRequestParam:" + name + ", " + age);
        return SUCCESS;
    }

 

需要注意

@RequestParam 参数类型最好都写为对象类型,不然可能会发生空指针异常。 如将上面代码中的 Integer 改为 int 类型,当传入的 age 参数为空时,在方法处的 age 值会被赋值为 null,但是 int 类型的变量不能被赋值为 null,会发生空指针异常,所以最好写为对象类型或包装类型。

如果实在没办法必须写为原生数据类型,那么就需要使用 required 和 defaultValue 来避免异常产生,比如 设置 required=false;设置一个默认值 defaultValue=默认值。

 

 

@RequestHeader 

在请求头中包含了若干属性,服务器可根据此获取和客户端的信息,通过 @RequestHeader 即可将请求头中的属性值绑定到处理方法的入参中

    @RequestMapping("/testRequestHeader")
    public String testRequestHeader(@RequestHeader(value="Accept-language")String lag)
    {
        System.out.println("testRequestHeader: " + lag);
        return SUCCESS;
    }
    <a href="springmvc/testRequestHeader">Test RequestHeader</a>

 

 

@CookieValue

Spring MVC 还提供了绑定 Cookie 值的注解 @CookieValue ,它可以让方法入参绑定某个 Cookie 的值,它和 @RequestParam 拥有3个一样的参数

    @RequestMapping("/testCookieValue")
    public String testCookieValue(@CookieValue(value="JSESSIONID")String sessionId)
    {
        System.out.println("testCookieValue: " + sessionId);
        return SUCCESS;
    }

 

 

使用 POJO 对象绑定请求参数

当我们要映射一个表单中的多个项目时,可以使用将表单中的内容放入一个 POJO 对象中,当绑定表单到处理方法入参时就不必一个一个的去写实现,而只需将 POJO 对象绑定到入参,可以提高效率。Spring MVC 会按照请求参数名和 POJO 属性名进行自动装配,自动为该对象填充属性值,并且支持级联属性。

新增两个 POJO 对象

package com.bupt.springmvc.entity;

public class User
{
    private String username;
    private Address address;
   
  //生成 get 和 setter 方法,重写 toString 方法
}
package com.bupt.springmvc.entity;

public class Address
{
    private String city;

  //生成 get 和 setter方法,重写 toString 方法
}

SpringTest类新增处理方法

    @RequestMapping("/testPojo")
    public String testPojo(User user)
    {
        System.out.println("testPojo: " + user);
        return SUCCESS;
    }

index.jsp新增表单

    <form action="springmvc/testPojo" method="post">
        username: <input type="text" name="username"/><br>
        city: <input type="text" name="address.city"/><br>
        <input type="submit" value="submit"/>
    </form>

 

 

使用 Servlet API 作为入参

在Spring MVC 中,控制器类可以不依赖任何 Servlet API 对象,但是 Spring MC 并不阻止我们使用 Servlet API 的类作为处理方法的入参。

Spring MVC 的 Handler 方法接受的 Servlet API 包括:HttpServletRequest、HttpServletResponse、HttpSession、java.security.Principal、Locale、InputStream、OutputStream、Reader、Writer。

 

继续添加处理方法和超链接

1     @RequestMapping("/testServletAPI")
2     public void testServletAPI(HttpServletRequest request, HttpServletResponse response, Writer out) throws IOException
3     {
4         System.out.println("testServlet: " + request + ", " + response);
5         out.write("Hello");
6     }

 

 

REST 风格的请求

REST :即 Representational State Transfer,一般翻译为(资源)表现层状态转化。

表现层:把资源具体呈现出来的形式,叫做它的表现层。比如,文本可以使用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式。

状态转化:没法送一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,就是一个无状态协议,即所有的状态都保存在在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”。而这种转化是建立在表现层之上的,所以就是“表现层状态转化”。具体说,就是 HTTP 协议里,四个表示操作的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。

浏览器表单只支持 GET 与 POST 请求,而 DELETE、PUT 等并不支持。所以Spring 3.0 提供了一个 HiddenHttpMethodFilter,允许通过 “_method” 的表单参数指定这些特殊的HTTP方法(实际上还是通过POST提交表单)。服务器端配置了 HiddenHttpMethodFilter 后,Spring 会根据 _method 参数指定的值模拟出相应的 HTTP 方法,这样就可以使用这些HTTP 方法对处理方法进行映射了。

示例:

- /order/1 HTTP GET:得到 id = 1 的 order

- /order/1 HTTP DELETE:删除 id = 1 的 order

- /order/1 HTTP PUT:更新 id = 1 的 order

- /order HTTP POST:新增 id = 1 的 order

 

 

在SpringTest里新增如下方法

 1     /*
 2      * 如何发出 PUT 请求和 DELETE 请求呢
 3      * 1. 配置 HiddenHttpMethodFilter
 4      * 2. 发出POST请求
 5      * 3. 发出POST请求时携带一个name="_method"的隐藏域,值为 DELETE 或 PUT
 6      */
 7     @RequestMapping(value="/testRest/{id}", method=RequestMethod.GET)
 8     public String testRestGet(@PathVariable Integer id)
 9     {
10         System.out.println("testRest GET:" + id);
11         return SUCCESS;
12     }
13     
14     @RequestMapping(value="/testRest", method=RequestMethod.POST)
15     public String testRestPost()
16     {
17         System.out.println("testRest POST");
18         return SUCCESS;
19     }
20     
21     @RequestMapping(value="/testRest/{id}", method=RequestMethod.DELETE)
22     public String testRestDelete(@PathVariable Integer id)
23     {
24         System.out.println("testRest DELETE:" + id);
25         return SUCCESS;
26     }
27     
28     @RequestMapping(value="/testRest/{id}", method=RequestMethod.PUT)
29     public String testRestPut(@PathVariable Integer id)
30     {
31         System.out.println("testRest PUT:" + id);
32         return SUCCESS;
33     }

 

index.jsp中新增如下超链接

 1     <form action="springmvc/testRest/1" method="post">
 2         <input type="hidden" name="_method" value="PUT">
 3         <input type="submit" value="Test REST PUT">
 4     </form>
 5     <br><br>
 6     <form action="springmvc/testRest/1" method="post">
 7         <input type="hidden" name="_method" value="DELETE">
 8         <input type="submit" value="Test REST DELETE">
 9     </form>
10     <br><br>
11     <form action="springmvc/testRest" method="post">
12         <input type="submit" value="Test REST POST">
13     </form>
14     <br><br>
15     <a href="springmvc/testRest/1">Test REST GET</a>

 

启动服务器,运行程序,如果你运行环境是在 tomcat 8 下,当运行到 DELETE 和 PUT 请求时,程序报错

出现的原因,根据 stackoverflow 上的解释说是因为,JSP 2.3 要求只能响应 GET、HEAD 和 POST,对于其他的HTTP方法不支持。Tomcat选择不支持PUT、DELETE方法来防止出现篡改HTTP方法的攻击。

直接从网上找了三种解决方法:

1. 将Tomcat 8 改为 Tomcat 7,在 Tomcat 7 环境下运行是正常的

2. 将请求转发(forward) 改为请求重定向 (redirect)

3. 自己手动写一个 Filter 来包装 HttpRequest 中的 getMethod()方法

下面介绍第三种做法,也就是自己写一个 Filter 来包装从服务器发送过来的 HttpRequest 请求

大致流程:

1. 客户端向服务器端发送请求,若发送的是 POST 请求且带有以 _method 为名的参数会被 Spring 的 HanddenHttpMethodFilter给拦截

2. HiddenHttpMethodFilter 内有一个静态内部类通过继承 HttpServletRequestWrapper 类重写 getMethod()方法,将该方法返回值设为 _method 隐藏域的值。

3. HiddenHttpMethodFilter 在包装好 Request 后,将请求发往服务器中对应的方法处理器,这时请求变成了3中的 WrapperRequest by SpringFilter

4. 服务器处理完请求后,产生一个 forward 请求,产生相应的请求信息发往客户端,注意这时的 request 的 getMethod() 方法仍然是 HiddenHttpMethodFilter 包装过的

5. 我们需要在请求到达客户端前拦截它,通过自定义的滤波器 MyHttpMethodFilter进一步包装请求,将getMethod() 方法返回值改成 POST 或  GET 即可。

6. 在 web.xml 中配置该filter 注意 dispatcher节点值必须为FORWARD

<filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

<dispatcher>元素的作用:

这个元素有四个可能值:REQUEST、FORWARD、INCLUDE 和 ERROR,可以在一个 <filter-mapping> 元素中加入任意数目的 <dispatcher>,使得filter将会作用于直接从客户端过来的request,通过forward过来的request,通过include过来的request和通过 <error-page> 过来的request。若没有指定任何 <dispatcher>元素,默认值是REQUEST。

 

posted on 2016-07-03 15:28  Traveling_Light_CC  阅读(808)  评论(0编辑  收藏  举报

导航