然后,需要应用<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 属性名称。
请求映射的值可以是一个空字符串,此时该方法被映射到以下网址:
RequestMapping 除了具有 value 属性外,还有其他属性。例如,method 属性用来指示该方法仅处理哪些 HTTP 方法。
例如,仅当在HTTP POST 或PUT 方法时,才访问到下面的 ProcessOrder 方法。...
import org.springframework.stereotype.Controller;
...
@RequestMapping(value="/process-order", method={RequestMethod.POST, RequestMethod.PUT})
public String processOrder() {
// do something here
return "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;
...
@Controller @RequestMapping("/customer") public class CustomerController {
@RequestMapping(value="/delete", method={RequestMethod.POST, RequestMethod.PUT})
public String deleteCustomer() {
// do something here
return ...;
}
由于控制器类的映射使用“/customer”,而 deleteCustomer 方法映射为“/delete”,则如下
URL 会映射到该方法上。
http://domain/context/customer/delete
4.2 编写请求处理方法
每个请求处理方法可以有多个不同类型的参数,以及一个多种类型的返回结果。例如, 如果在请求处理方法中需要访问HttpSession 对象,则可以添加的 HttpSession 作为参数,Spring会将对象正确地传递给方法。
@RequestMapping("/uri")
public String myMethod(HttpSession session) {
...
session.addAttribute(key, value);
...
}
或者,如果需要访问客户端语言环境和 HttpServletRequest 对象,则可以在方法签名上包括这样的参数:
@RequestMapping("/uri")
public String myOtherMethod(HttpServletRequest request, Locale locale) {
...
// access Locale and HttpServletRequest here
...
}
下面是可以在请求处理方法中出现的参数类型:
— javax.servlet.ServletRequest 或 javax.servlet.http.HttpServletRequest 。
— javax.servlet.ServletResponse 或 javax.servlet.http.HttpServletResponse 。
— javax.servlet.http.HttpSession。
— org.springframework.web.context.request.WebRequest 或 org.springframework.web.
context.request.NativeWebRequest。
4.2 编写请求处理方法
— java.util.Locale。
— java.io.InputStream 或 java.io.Reader 。
— java.io.OutputStream 或 java.io.Writer 。
— java.security.Principal。
— HttpEntity<?>paramters
— java.util.Map/org.springframework.ui.Model /。
— org.springframework.ui.ModelMap。
— org.springframework.web.servlet.mvc.support.RedirectAttributes。
— org.springframework.validation.Errors /。
— org.springframework.validation.BindingResult。
— 命令或表单对象。
— org.springframework.web.bind.support.SessionStatus。
— org.springframework.web.util.UriComponentsBuilder。
— 带@PathVariable、@MatrixVariable、@RequestParam、@RequestHeader、@RequestBody 或@RequestPart 注释的对象。
特别重要的是 org.springframework.ui.Model 类型。这不是一个 Servlet API 类型,而是一个包含 Map 的 Spring MVC 类型。每次调用请求处理方法时,Spring MVC 都创建 Model 对象并将其 Map 注入到各种对象。
请求处理方法可以返回如下类型的对象:
— ModelAndView。
— Model。
— 包含模型的属性的 Map。
— View。
— 代表逻辑视图名的 String。
— void。
— 提供对 Servlet 的访问,以响应HTTP 头部和内容 HttpEntity 或 ResponseEntity 对象。
— Callable。
— DeferredResult。
— 其他任意类型,Spring 将其视作输出给 View 的对象模型。
本章后续会展示一个例子,以进一步学习如何开发一个请求处理方法。
4.3 应用基于注解的控制器
本章的示例应用 annotated1 基于第 2 章和第 3 章的例子重写,展示了包含有两个请求处理方法的一个控制器类。
annotated1 和前面的应用程序间的主要区别在于,annotated1 的控制器类增加了注解
@Controller。此外,Spring 配置文件也增加了一些元素,后续小节中会详细介绍。
4.3.1 目录结构
图 4.1 展示了 annotated1 的目录结构。注意,annotated1 中只有一个控制器类,而不是两个,同时新增了一个名为 index.html 的 HTML 文件,以便 Spring MVC Servlet 的 URL 模式设置为“/”时,依然可以访问静态资源。
4.3.2 配置文件
annotated1 有两个配置文件。第一个为部署描述符(web.xml 文件)中注册 Spring MVC
的 Dispatcher Servlet。第二个为 springmvc-config.xml,即 Spring MVC 的配置文件。
清单 4.1 和清单 4.2 分别展示部署描述符和 Spring MVC 的配置文件。
清单 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
模式设置为“/”,当 URL 模式设置为“/”时,意味着所有请求(包括那些用于静态资源)都被映射到Dispatcher Servlet。为了正确处理静态资源,需要在 Spring MVC 配置文件中添加一些<resources/>元素。
清单 4.2 springmvc-config.xml 文件
<?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:mvc="http://www.springframework.org/schema/mvc" 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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/springcontext.xsd">
<context:component-scan base-package="controller"/>
<mvc:annotation-driven/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/*.html" location="/"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.
InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
清单 4.2(Spring MVC 的配置文件)中最主要的是<component-scan/>元素。这是要指示Spring
MVC 扫描目标包中的类,本例是 controller 包。接下去是一个<annotation-driven/>元素和两个
<resources/>元素。<annotation-driven/>元素做了很多的事情,其中包括注册用于控制器注解的bean
对象。<resources/>元素则指示Spring MVC 哪些静态资源需要单独处理(不通过Dispatcher Servlet)。
在清单 4.2 的配置文件中,有两个<resources/>元素。第一个确保在/ CSS 目录下的所有文件可见,第二个允许显示所有的.html 文件。
注意
如果没有<annotation-driven/>,<resources/>元素会阻止任意控制器被调用。若不需要使
用 resources,则不需要<annotation-driven/>元素。
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.3.4 View
annotated1 也有与前面章节示例类似的两个视图:ProductForm.jsp 页面(见清单 4.4)和
ProductDetails.jsp 页面(见清单 4.5)。
清单 4.4 ProductForm.jsp 页面
<!DOCTYPE HTML>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<div id="global">
<form action="save-product" method="post">
<fieldset>
<legend>Add a product</legend>
<p>
<label for="name">Product Name: </label>
<input type="text" id="name" name="name" tabindex="1">
</p>
<p>
<label for="description">Description: </label>
<input type="text" id="description" name="description" tabindex="2">
</p>
<p>
<label for="price">Price: </label>
<input type="text" id="price" name="price" tabindex="3">
</p>
<p id="buttons">
<input id="reset" type="reset" tabindex="4">
<input id="submit" type="submit" tabindex="5" value="Add Product">
</p>
</fieldset>
</form>
</div>
</body>
</html>
清单 4.5 ProductDetails.jsp 页面
<!DOCTYPE HTML>
<html>
<head>
<title>Save Product</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<div id="global">
<h4>The product has been saved.</h4>
<p>
<h5>Details:</h5>
Product Name: ${product.name}<br/> Description: ${product.description}<br/> Price: $${product.price}
</p>
</div>
</body>
</html>
4.3.5 测试应用
在浏览器中输入如下 URL 来测试 annotated1。
http://localhost:8080/annotated1/input-product
浏览器会显示Product 表单,如图 4.2 所示。
单击“Add Product”按钮,会调用 saveProduct 方法。
图 4.2 Product 表 单
4.4 应用@Autowired 和@Service 进行依赖注入
使用 Spring 框架的一个好处是容易进行依赖注入。毕竟,Spring 框架一开始就是一个依赖注入容器。将依赖注入到Spring MVC 控制器的最简单方法是,通过注解@Autowired 到字段或方法。Autowired 注解类型属于 org.springframework.beans.factory.annotation 包。
此外,为了能作为依赖注入,类必须要注明为@Service。该类型是 org.springframework.
stereotype 包的成员。Service 注解类型指示类是一个服务。此外,在配置文件中,还需要添加一个<component-scan/>元素来扫描依赖基本包。
<context:component-scan base-package="dependencyPackage"/>
下面以 annotated2 应用进一步说明 Spring MVC 如何应用依赖注入。在 annotated2 应用程序中,ProductController 类(见清单 4.6)已经不同于 annotated1 中的类。
清单 4.6 annotated2 中的 ProductController 类
package controller;
import java.math.BigDecimal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes; import domain.Product;
import form.ProductForm; import service.ProductService;
@Controller
public class ProductController {
private static final Log logger = LogFactory
.getLog(ProductController.class);
@Autowired
private ProductService productService;
@RequestMapping(value = "/input-product ") public String inputProduct() {
logger.info("inputProduct called"); return "ProductForm";
}
@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:/product-view/" + savedProduct.getId();
}
@RequestMapping(value = "/view-product/{id}")
public String viewProduct(@PathVariable Long id, Model model) { Product product = productService.get(id);
model.addAttribute("product", product); return "ProductView";
}
}
与 annotated1 中相比,annotated2 中的 ProductController 类做了一系列的调整。首先是在如下的私有字段上增加了@Autowired 注解:
@Autowired
private ProductService productService
ProductService 是一个提供各种处理产品的方法的接口。为 productService 字段添加
@Autowired 注解会使ProductService 的一个实例被注入到ProductController 实例中。
清单 4.7 和清单 4.8 分别显示了 ProductService 接口及其实现类 ProductServiceImpl。注意, 为了使类能被 Spring 扫描到,必须为其标注@Service。
清单 4.7 ProductService 接口
package service
import domain.Product;
public interface ProductService { Product add(Product product); Product get(long id);
}
清单 4.8 ProductServiceImpl 类
package service;
import java.math.BigDecimal; import java.util.HashMap; import java.util.Map;
import java.util.concurrent.atomic.AtomicLong; import org.springframework.stereotype.Service; import domain.Product;
@Service
public class ProductServiceImpl implements ProductService {
private Map<Long, Product> products = new HashMap<Long, Product>();
private AtomicLong generator = new AtomicLong();
public ProductServiceImpl() { Product product = new Product();
product.setName("JX1 Power Drill"); product.setDescription(
"Powerful hand drill, made to perfection"); product.setPrice(new BigDecimal(129.99));
add(product);
}
@Override
public Product add(Product product) {
long newId = generator.incrementAndGet(); product.setId(newId);
products.put(newId, product); return product;
}
@Override
public Product get(long id) { return products.get(id);
}
}
如清单 4.9 所示,annotated2 的 Spring MVC 配置文件中有两个<component-scan/>元素: 一个用于扫描控制器类,另一个用于扫描服务类。
清单 4.9 Spring MVC 配置文件
<?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:mvc="http://www.springframework.org/schema/mvc" 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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/springcontext.xsd">
<context:component-scan base-package="controller"/>
<context:component-scan base-package="service"/>
<mvc:annotation-driven/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/*.html" location="/"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
@Controller
public class CustomerController {
// request-handling methods here
annotated1 的目录结构
