第四章 基于注解的控制器
内容关键词:
Spring MVC 注解类型
Controller注解类型
Controller 类
重定向和 Flash 属性
请求数和路径变量
应用基于注解的控制器
授课老师:720科技 张森鹏
一、知识笔记
4. .1 1 Spring MVC 注解类型
使用基于注解的控制器的几个优点。其一,一个控制器类可以处理多个动作(而实现了 Controller 接口的一个控制器只能处理一个动作)。这就允许将相关的操作写在同一个控制器 类中,从而减少应用程序中类的数量。 其二,基于注解的控制器的请求映射不需要存储在配置文件中。使用 RequestMapping 注 释类型,可以对一个方法进行请求处理。 Controller 和 RequestMapping 注释类型是 Spring MVC API 最重要的两个注解类型。本章 重点介绍这两个,并简要介绍了一些其他不太流行的注解类型。
4.1.1 Controller 注解类型 org.springframework.stereotype.Controller 注解类型用于指示 Spring 类的实例是一个控制 器。下面是一个带注解@Controller 的例子。 package com.example.controller;
import org.springframework.stereotype; ...
@Controller public class CustomerController {
4.1 Spring MVC 注解类型
// request-handling methods here }
Spring 使用扫描机制来找到应用程序中所有基于注解的控制器类。为了保证 Spring 能找 到你的控制器,需要完成两件事情。首先,需要在 Spring MVC 的配置文件中声明 spring- context,如下所示:
<beans ... xmlns:context="http://www.springframework.org/schema/context" ... >
然后,需要应用<component-scan/>元素,如下所示:
<context:component-scan base-package="basePackage"/>
请在<component-scan/>元素中指定控制器类的基本包。例如,若所有的控制器类都在 com.example.controller 及其子包下,则需要写一个如下所示的<component-scan/>元素:
<context:component-scan base-package="com.example.controller"/>
现在,整个配置文件看上去如下所示:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring- context.xsd">
<context:component-scan base-package="com.example.controller"/><!-- ... --> </beans>
请确保所有控制器类都在基本包下,并且不要指定一个太广泛的基本包(如指定 com.example, 而非 com.example.controller,前者就更广泛),因为这会使得 Spring MVC 扫描了无关的包。
4.1.2 RequestMapping 注解类型 现在,我们需要在控制类的内部为每一个动作开发相应的处理方法。要让 Spring 知道用
哪一种方法来处理它的动作,需要使用 org.springframework.web.bind.annotation.Request Mapping 注解类型映射的 URI 与方法。 RequestMapping 注解类型的作用同其名字所暗示的:映射一个请求和一种方法。可以使 用@RequestMapping 注解一种方法或类。 一个采用@RequestMapping 注解的方法将成为一个请求处理方法,并由调度程序在接收 到对应 URL 请求时调用。 下面是一个 RequestMapping 注解方法的控制器类。
package com.example.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; ...
@Controller public class CustomerController {@RequestMapping(value = "/input-customer ") public String inputCustomer() {
// do something here
return "CustomerForm"; } }
使用 RequestMapping 注解的 value 属性将 URI 映射到方法。在上面的例子中,我们将 input-customer 映射到 inputCustomer 方法。这样,可以使用如下 URL 访问 inputCustomer 方法。
http://domain/context/input-customer
由于 value 属性是 RequestMapping 注释的默认属性,因此,若只有唯一的属性,则可以 省略属性名称。换句话说,如下两个标注含义相同。 @RequestMapping(value = "/input-customer ") @RequestMapping("/input-customer ")
但如果有多个属性时,就必须写入 value 属性名称。 请求映射的值可以是一个空字符串,此时该方法被映射到以下网址: http://domain/context
RequestMapping 除了具有 value 属性外,还有其他属性。例如,method 属性用来指示该 方法仅处理哪些 HTTP 方法。
4.1 Spring MVC 注解类型
例如,仅当在 HTTP POST 或 PUT 方法时,才访问到下面的 ProcessOrder 方法
。import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; ... @RequestMapping(value="/process-order", method={RequestMethod.POST, RequestMethod.PUT}) public String processOrder() {
// do something herereturn "OrderForm"; }
若 method 属性只有一个 HTTP 方法值,则无需花括号。例如, @RequestMapping(value="/process-order", method=RequestMethod.POST)
如果没有指定 method 属性值,则请求处理方法可以处理任意 HTTP 方法。 此外,RequestMapping 注解类型也可以用来注解一个控制器类,如下所示: import org.springframework.stereotype.Controller; ...
@Controller @RequestMapping(value="/customer") public class CustomerController {
在这种情况下,所有的方法都将映射为相对于类级别的请求。例如,下面的 deleteCustomer 方法。
... import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; ... @Controller @RequestMapping("/customer") public class CustomerController {
@RequestMapping(value="/delete", method={RequestMethod.POST, RequestMethod.PUT}) public String deleteCustomer() {// do something here
第 4 章 基于注解的控制器
return ...; }
由于控制器类的映射使用“/customer”,而 deleteCustomer 方法映射为“/delete”,则如下 URL会映射到该方法上。 http://domain/context/customer/delete
二、重要记录
4. .3 3 应用基于注解控制器
本章的示例应用 annotated1 基于第 2 章和第 3 章的例子重写,展示了包含有两个请求处 理方法的一个控制器类。 annotated1 和前面的应用程序间的主要区别在于,annotated1 的控制器类增加了注解 @Controller。此外,Spring 配置文件也增加了一些元素,后续小节中会详细介绍。
4.3.1 目录结构 图 4.1 展示了 annotated1 的目录结构。注意,annotated1 中只有一个控制器类,而不是两 个,同时新增了一个名为 index.html 的 HTML 文件,以便 Spring MVC Servlet 的 URL 模式设 置为“/”时,依然可以访问静态资源。
4.1 annotated1(web.xml)的部署描述符 <?xml version="1.0" encoding="UTF-8"?> <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<servlet>
<servlet-name>springmvc</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/config/springmvc-config.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>
图 4.1 为 annotated1 的目录结构。
图 4.1 annotated1 的目录结构
另外,在部署描述符中的<servlet-mapping/>元素,Spring MVC 的 dispatcher-servlet 的 URL
4.3.3 Controller 类
如前所述,使用 Controller 注释类型的一个优点在于:一个控制器类可以包含多个请 求处理方法。如清单 4.3 所示,ProductController 类中有 inputProduct 和 saveProduct 两个 方法。
清单
4.3 ProductController 类
package controller; import java.match.Bigoecimal import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import domain.Product; import form.ProductForm;
@Controller public class ProductController {private static final Log logger = LogFactory.getLog(ProductController.class);
@RequestMapping(value="/input-product") public String inputProduct() { logger.info("inputProduct called"); return "ProductForm"; }
@RequestMapping(value="/save-product") public String saveProduct(ProductForm productForm, Model model){ logger.info("saveProduct called"); // no need to create and instantiate a ProductForm // create Product Product product = new Product(); product.setName(productForm.getName()); product.setDescription(productForm.getDescription()); try { product.setPrice(new BigDecimal(productForm.getPrice())); } catch (NumberFormatException e) {
}
// add product model.addAttribute("product", product); return "ProductDetails"; } }
其中,ProductController 的 saveProduct 方法的第二个参数是 org.springframework.ui.Model 类型。无论是否会使用,Spring MVC 都会在每一个请求处理方法被调用时创建一个 Model 实例,用于增加需要显示在视图中的属性。例如,通过调用 model.addAttribute 来添加 Product 实例: model.addAttribute("product", product);
Product 实例就可以像被添加到 HttpServletRequest 中那样访问了
4. .5 5 重 重定 定向 向和 和 F Fl la as sh h 属 属性 性
作为一名经验丰富的 servlet/ JSP 程序员,必须知道转发和重定向的区别。转发比重定向 快,因为重定向经过客户端,而转发没有。但是,有时采用重定向更好,若需要重定向到一 个外部网站,则无法使用转发。
使用重定向的另一个场景是避免在用户重新加载页面时再次调用同样的动作。例如,在 annotated1 中,当提交产品表单时,saveProduct 方法将被调用,并执行相应的动作。在一个 真实的应用程序中,这可能包括将所述产品加入到数据库中。但是,如果在提交表单后重新 加载页面,saveProduct 就会被再次调用,同样的产品将可能被再次添加。为了避免这种情况, 提交表单后,你可能更愿意将用户重定向到一个不同的页面。这个网页任意重新加载都没有 副作用。例如,在 annotated1 中,可以在提交表单后,将用户重定向到一个 ViewProduct 页面。
在 annotated2 中,ProductController 类中的 saveProduct 方法以如下所示的行结束: return "redirect:/view-product/" + savedProduct.getId();
这里,使用重定向而不是转发来防止当用户重新加载页面时 saveProduct 被二次调用。
使用重定向的一个不便的地方是:无法轻松地传值给目标页面。而采用转发,则可以简 单地将属性添加到 Model,使得目标视图可以轻松访问。由于重定向经过客户端,所以 Model 中的一切都在重定向时丢失。好在,Spring 3.1 版本以及更高版本通过 Flash 属性提供了一种 供重定向传值的方法。 要使用 Flash 属性,必须在 Spring MVC 配置文件中有一个<annotation-driven/>元素。然 后,还必须在方法上添加一个新的参数类型 org.springframework.web.servlet.mvc.support. RedirectAttributes。清单
4.10 展示了更新后的 saveProduct 方法。
4.10 使用 Flash 属性 @RequestMapping(value = "save-product", method = RequestMethod.POST) public String saveProduct(ProductForm productForm, RedirectAttributes redirectAttributes) { logger.info("saveProduct called");
// no need to create and instantiate a ProductForm // create Product Product product = new Product(); product.setName(productForm.getName()); product.setDescription(productForm.getDescription()); try { product.setPrice(new BigDecimal(productForm.getPrice())); } catch (NumberFormatException e) { }// add product Product savedProduct = productService.add(product);
redirectAttributes.addFlashAttribute("message", "The product was successfully added.");
return "redirect:/ view_product /" + savedProduct.getId();
三、学习参考
Spring MVC学习指南(第二版)