Spring MVC 的请求映射与参数

2022-11-15 13:42:23 浏览数 (1)

1 控制器方法的参数

(1)请求参数的获取

 编码处理 HTTP 请求的首要问题是要获取用户所提交的参数。原生 HTTP 参数都是字符串类型的,在传统 Servlet 中,我们需要通过 request 对象获取。

String 变量 = request.getParameter("参数名");

 如果获取的是 ID 值,或者是日期,还要进行非空判断和类型转换,倘若一次获取很多表单(form)字段,代码将会冗长。几乎所有的 MVC 框架都一定会优化这种简单冗余的代码。在 Spring MVC 中,我们可以在控制器方法中直接获取用户提交的请求参数,只要方法参数的名字和请求参数的名字相同即可,Sprig MVC 还会自动对参数作相应的类型转换。  比如我们要写登录验证的控制器方法就可以直接接受表单提交过来的用户名

(username)和密码(password)。

       表单代码:

代码语言:javascript复制
<form action="check-login" method="post">  	 	
    <input type="text" name="username" />  	 	
    <input type="password" name="password" />  	 	
    <button>登录</button>  	
</form> 

控制器方法代码:

代码语言:javascript复制
@RequestMapping(value="/check-login")  	
public String login(String username, String password){ 
 	 	… 
} 

(2) 推送给 View 的 Model

 除了获取请求参数,请求处理中另一个最常见的操作就是要把 Controller 中的数据 Model 推送到 View 中显示。前面提到,我们可以把要推送给 View 的数据 put 到

Map<String,Object>中,然后在转发后的 View 中就可以获取并显示了。

       控制器方法:

代码语言:javascript复制
@RequestMapping(value="/check-login") 
 public String login(String username, String password, Map<String,Object> model){   
    User loginUser = userBiz.checkLogin(username, password);   
    if(loginUser!=null){    
        return "redirect:/index"; //重定向 
    }else{    
        model.put("message", "用户名或密码有误。");    
        return "login";   //转发 
 	 	} 
 	} 

这里需要注意的还有控制器方法的返回值。

 如果 return 后面跟着”redirect: ”开头的字符串,表示的是重定向行为(相当于 response.sendRedirect(…) ); 如 果 不 是 , 则 表 示 是 转 发 行 为 ( 相 当 于 request.getRequestDispacther(…).forward(…)).

除了使用 Map 之外,Spring MVC 还可以使用 Model 类型对象来装载 Model 数据。

代码语言:javascript复制
@RequestMapping(value="/check-login")  	
public String login(String username, String password, Model model){ 
 	 … model.addAttribute("message", "用户名或密码有误。"); … 
} 

(3)  使用 Servlet API

 MVC 框架优化了参数的获取或模型的推送,这些操作都不需要 request、response 对象,也就是所谓的 Servlet API。传统 Servlet 编程的繁琐就在这里,如果代码都几乎不出现

Servlet API,Web 编程就会变得直观简明。

 当然,我们不可能完全抛开 Servlet API,这是服务器编程啊,比如我们要用到 Session 怎么办?于是 Spring MVC 中的控制器方法还有第三个功能,就是为我们传入所需要的 Servlet API,无论是 request、response、session、application 等等,只要你想要,就直接在控制器方法中声明一个参数就可以了。

下面是完整的登录处理方法,登录成功后,把用户信息存放在 session 中以备将来取用

代码语言:javascript复制
@RequestMapping(value="/check-login") 
 public String login(HttpSession session, String username, String password, Model model){ 
     User loginUser = userBiz.checkLogin(username, password); 
     if(loginUser!=null){ 
         session.setAttribute("loginUser", loginUser); //session 中保存登录状态 
         return "redirect:/index"; 
     }else{ 
         model.addAttribute("message", "用户名或密码有误。"); 
     return "login"; 
     } 
 } 

2 RequestMapping 注解和 REST 请求风格

@RequestMapping 注解除了能简单的实现从 URL 到 Controller 方法的映射之外,还支持更先进 HTTP 请求理念,就是所谓的 REST 风格。

REST(Representational State Transfer)翻译为中文是“表述性状态转移”。简单的说,就是把 HTTP 协议的多种特性都用上去区别对待一个请求,这包括 URL、请求的 Method、请求头信息等等;而不是只靠 URL 去区分请求。严格按照 REST 风格写出来的服务器,一个 URL 相当于一个业务对象,请求 method 中的 get、post、put、delete 分别对应对象的查询、新增、修改、删除,服务器还可以根据请求头的需要返回 html、json 或者是 xml,这样的服务器就不仅仅是一个供人浏览的网站了,还是一个可以被手机端、桌面软件、另一个服务器等不同系统获取信息的 SOA 系统,称为 REST Web Service。

    为了支持 REST 风格,@RequestMapping 提供了以下属性供我们设置。

属性

功能

value

指定请求的实际地址,是数组,可以指定多个

method

指定请求的 method 类型, GET、POST、PUT、DELETE 等

consumes

指定请求的提交内容类型(Content-Type),例如 application/json, text/html;

produces

指定返回的内容类型,仅当 request 请求头中的(Accept)类型中包含该指定类型才返回

params

指定 request 中必须包含某些参数值是,才让该方法处理

headers

指定 request 中必须包含某些指定的 header(请求头)值,才能让该方法处理请求

在简单的情况下,我们至少应该设置@RequestMapping 的 value 和 method。例如,我们希望同一个请求地址“/login”,在 get 请求下返回一个登录表单供用户填写,在 post 请求下处理登录验证,则我们可以写成。

代码语言:javascript复制
@RequestMapping(value="/login",method=RequestMethod.GET) 
 public String login(){ 
     return "login"; 
 } 
 
 @RequestMapping(value="/login", method=RequestMethod.POST) 
 public String login(HttpSession session, String username, String password, Model model){ 
 … 
 }

3 值类型参数的获取

下面的示例是一个多条件分页的控制器方法,实现电影信息的分页查询功能。

代码语言:javascript复制
@RequestMapping("/movie-list") 
 public String movieList( int cid, String title, int page, Model model ){ 
     final int pageSize = 4; 
     List<Movie> movies = movieBiz.getMoviesPagings(cid, title, page, pageSize); 
     int rows = movieBiz.fetchMovieRows(cid, title); 
     int totalPages = rows%pageSize==0?rows/pageSize:rows/pageSize 1; 
     model.addAttribute("totalPages", totalPages); 
     model.addAttribute("page", page); 
     model.addAttribute("movies", movies); 
     model.addAttribute("categories", categoryBiz.getAll()); 
     return "admin/movie-list"; 
} 

当请求提供 cid 参数和 page 参数时,运行效果良好.

           但如果请求中不提供 cid 参数和 page 参数时,就无法运行了.

这是由 int 类型的 cid 参数和 page 参数引起的。int 是原生类型参数,原生类型不能放入 null 值,因此当请求参数为空时,Spring MVC 无法为我们设置 int 类型的方法参数!而

String 类型参数 title 却无所谓,因为 String 参数不存在时,可以被设置为 null。

       要解决这个问题,可以选择以下两种方案其中一种。

  1. 使用包装类(Integer)代替原生类型(int)声明方法参数

方法一是:但凡原生值类型的参数,如果不是必须的,都换成包装类。

代码语言:javascript复制
@RequestMapping("/movie-list") 
 public String movieList( Integer cid, String title, Integer page, Model model){ 
     cid = cid==null? 0 : cid; 
     page = page==null? 1 : page; 
 … 
} 

但这种方式在使用时一定要注意 null 值处理,例如上述代码中的“page = page==null? 1 : page;”,当页码为 null 时,把页码设置为第 1 页,否则在底层查询方法执行时,很可能会发生 NullPointerException!

  1. 使用@RequestParam 注解标记参数    第二中方法是:使用@RequestParam 修饰 Controller 方法的参数。 @RequestParam 该注解可以:
    1. 通过 name 属性来指定请求参数的参数名,这样方法参数和请求参数名称就可以不相同;
    2. 通过 required 属性指定该请求参数是必须提供的(true),还是可选的(false)
    3. 通过 defaultValue 属性指定当该请求参数不提供时的默认值。
代码语言:javascript复制
@RequestMapping("/movie-list") 
 public String movieList( 
     @RequestParam(name="cid",required=false,defaultValue="0") int cid, 
     String title, 
     @RequestParam(name="page",required=false,defaultValue="1") int page, 
     Model model) { 
 … 

这种方式更为标准,只是方法参数列表看起来变复杂了。

4 查询字符串参数和路径参数

(1)查询字符串参数       继续为电影信息实现一个修改功能。我们希望点击查询列表中的“编辑”连接,转向一个单一信息的编辑页面,并把要修改的记录的 ID 专递过去。

通常我们可以使用“edit?id=2”这样的超链接,即 get 请求参数,这种在 URL 的“?”后传递参数的方式常常被称为“查询字符串”。

相对应的控制器方法应该如下:

代码语言:javascript复制
@RequestMapping("/edit") 
 public String edit ( Integer id, Model model ) { 
     id = id==null?0:id; 
     if(id>0){ 
         Movie m = movieBiz.fetchById(id); 
         model.addAttribute("movie", m); 
     }     
     model.addAttribute("categories", categoryBiz.getAll()); 
     return "admin/movie-edit"; 
 } 

(2)路径参数——@PathVariable

       为了更好的实现 REST 风格和优化路径,Spring MVC 还支持使用@PathVariable 的路径参数传递。使用路径参数时,我们可以在 URL 路径上,而不是?后传递参数值。上述编辑功能的控制方法可以改写为:

代码语言:javascript复制
  @RequestMapping( "/edit/{id}" )
  public String edit ( @PathVariable("id") int id, Model model ) {
    …
  }

在@RequestMapping 的路径属性中通过 “{参数名}” 的方式声明路径参数的位置,在方法参数中使用@PathVariable("参数名") 去接收。这时,请求地址如下所示:

这种参数不一定要位于 URL 的最后,可以在中间,但通常是必传递的参数,不是可选参数。(初学时不是非常建议使用“路径参数”方式传参,因为可能会引起相对路径的混乱)

  1. 对象型参数

 当我们完成了一个表单的编辑,要提交数据时,表单中往往存在许多元素,这些元素对应着一个对象的许多属性。如果我们的控制器要接收一个个零散参数将会很麻烦,正如下面的例子所示。

代码语言:javascript复制
@RequestMapping(value="/movie-save", method=RequestMethod.POST) 
public String save ( int id, String title, String movieCode, int categoryId, … ) { 
     //… 执行 movieBiz.add() 或者 movieBiz.update() 
}

 Spring MVC 允许我们用对象去一口气接收表单提交上来的多个参数,默认只要“对象的属性名”与“表单元素的 name 名称”一致就行。因此保存电影修改的控制器方法只要用一个参数就可以获取整个表单提交的值。

代码语言:javascript复制
@RequestMapping(value="/movie-save", method=RequestMethod.POST) 
 public String save(Movie movie){ 
     if(movie.getId()>0){ 
         movieBiz.update(movie); 
     }else{ 
         movieBiz.add(movie); 
     } 
     return "redirect:/admin/movie-list"; 
 } 

这里值得注意的是,最后的重定向“redirect:/admin/movie-list”,如果希望重定向后能还能查询到所需的数据,不妨在重定向后加上一些查询参数,比如下面的写法。

  1. POST 请求的乱码的解决

       上述修改保存后,大家可能都会看到除了了中文乱码的情况。

  我们知道,在 JSP 技术中,GET 请求乱码可以通过设置服务器的 server.xml 配置来解决,而 POST 请求乱码则应该在获取请求数据前使用代码“request.setCharactorEncoding=”UTF8” ”。在 Spring MVC 中我们不应该经常调用 Servlet API,因此框架给我们提供了编码过滤器,通过设置过滤器,就能指定请求的编码设置。

代码语言:javascript复制
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"  	
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  	
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   	
 http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> 
<!-- 编码过滤器,解决 Spring MVC 请求参数中文乱码 --> 
 	<filter> 
 	 	<filter-name>encodingFilter</filter-name> 
 	 	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 
 	 	<async-supported>true</async-supported> 
 	 	<init-param> 
 	 	 	<param-name>encoding</param-name> 
 	 	 	<param-value>UTF-8</param-value> 
 	 	</init-param> 
 	</filter> 
 	<filter-mapping> 
 	 	<filter-name>encodingFilter</filter-name> 
 	 	<url-pattern>/*</url-pattern> 
 	</filter-mapping> 
     <!-- Spring 容器的启动监听器 --> 
     <listener> 
         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
     </listener> 
     <context-param> 
         <param-name>contextConfigLocation</param-name> 
         <param-value>classpath:spring-beans.xml</param-value> 
     </context-param> 
     <!-- 配置 MVC 框架的核心控制器 --> 
     <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:spring-mvc.xml</param-value> 
         </init-param> 
         <load-on-startup>1</load-on-startup> 
     </servlet> 
     <servlet-mapping> 
     <servlet-name>springmvc</servlet-name> 
     <url-pattern>/</url-pattern> 
 </servlet-mapping> 
</web-app>

0 人点赞