Spring 2.5 中的注解式 Web MVC 控制器

工程 | Juergen Hoeller | 2007 年 11 月 14 日 | ...

Spring 2.5 引入了一种编写注解式 Web MVC 控制器的方法,我们对此的博客介绍还不多……我将借此机会概述一下 Spring MVC 目前的真正意义。

Spring MVC 本质上是一个请求分发器框架,它有 Servlet API 变体和 Portlet API 变体。它在其托管环境(Servlets 或 Portlets)中运行得非常紧密。将 Spring MVC 视为在 Servlet/Portlet 容器之上提供了基础功能和便利性:例如,灵活的请求映射、控制器处理和视图渲染阶段的分离、数据绑定、补充 JSTL 的基本 JSP 标签库等。这是复杂 HTTP 请求处理的构建块。

Spring MVC 是一个非常灵活的框架:它的核心 DispatcherServlet 不仅可以托管其原生控制器,还可以适应任何类型的动作。它可以托管处理基于 HTTP 的远程协议的普通 HttpRequestHandlers:这就是 Spring 用户在定义 HTTP invoker / Hessian / Burlap 服务导出器,或 Web 服务的 XFire 导出器时所利用的。DispatcherServlet 甚至可以托管任意第三方 Servlet,允许这些 Servlet 由 Spring 环境配置和管理。

Spring 2.5 的注解控制器

那么,Spring 2.5 的基于注解的控制器方法是如何融入这个图景的呢?很简单:它本质上是 DispatcherServlet / DispatcherPortlet 支持的一种替代控制器类型,不实现特定的接口,而是使用注解来表达特定处理器方法的请求映射。它主要是实现多动作控制器(multi-action controllers)的下一代风格,取代了 Spring 经典的 MultiActionController 类。

让我们来看一个例子,摘自 Spring 发行版附带的“imagedb”示例应用程序。(注意:这是“imagedb”的 Spring 2.5 最终版本,与 RC 版本略有不同。)

@Controller
public class ImageController {

private ImageDatabase imageDatabase;

@Autowired
public ImageController(ImageDatabase imageDatabase) {
this.imageDatabase = imageDatabase;
}

@RequestMapping("/imageList")
public String showImageList(ModelMap model) {
model.addAttribute("images", this.imageDatabase.getImages());
return "imageList";
}

@RequestMapping("/imageContent")
public void streamImageContent(@RequestParam("name") String name, OutputStream outputStream)
throws IOException {

this.imageDatabase.streamImage(name, outputStream);
}

@RequestMapping("/imageUpload")
public String processImageUpload(
@RequestParam("name") String name, @RequestParam("description") String description,
@RequestParam("image") MultipartFile image) throws IOException {

this.imageDatabase.storeImage(name, image.getInputStream(), (int) image.getSize(), description);
return "redirect:imageList";
}

@RequestMapping("/clearDatabase")
public String clearDatabase() {
this.imageDatabase.clearDatabase();
return "redirect:imageList";
}
}

这个控制器类究竟做了什么——它的设计目的是什么?让我们一步一步地来看……

@Controller 和 @RequestMapping

首先,该类被注解为 @Controller 样板。这表明其方法应该被扫描以进行请求映射。它还允许通过 Spring 2.5 的组件扫描(<context:component-scan>)进行自动检测,就像其他样板 @Component、@Repository 和 @Service 一样。在“imagedb”示例中,ImageController 仍然通过 <bean> 标签显式定义——这仅仅是因为自动检测在控制器数量较多的情况下才真正有价值。

构造函数被标记为 @Autowired,并接受一个 ImageDatabase 类型的参数。这是核心 Spring 2.5 功能,即注解驱动的依赖注入:这个构造函数将被调用,传入一个 ImageDatabase 类型的 Spring bean,该 bean 是通过类型从 Spring ApplicationContext 获取的。在我们的例子中,这是应用程序服务层中的核心 ImageDatabase 服务。

实际的请求映射是通过方法级别的 @RequestMapping 注解表达的。每个映射都绑定到包含的 DispatcherServlet 中的特定 HTTP 路径。映射路径也可以从处理器方法名推断出来,并且在类型级别上有一个常见的映射模式(例如,"*.image")——重用了我们熟悉的经典 MultiActionController 中的 InternalPathMethodNameResolver!

因此,当在类型级别使用 @RequestMapping 时,方法级别的注解将“缩小”特定处理器方法的映射。@RequestMapping 允许指定 HTTP 请求方法(例如,method = RequestMapping.GET)或特定的请求参数(例如,params = "action=save"),所有这些都可以缩小特定方法的类型级别映射。或者,类型级别的 @RequestMapping 也可以与经典的 Controller 接口实现相结合——例如 SimpleFormController 或 MultiActionController。

灵活的处理器方法签名

映射可以说是“显而易见”的部分,因为它很清楚发生了什么。现在,不那么显而易见的部分是处理器方法签名。这是一个非常灵活的事务,不被限制于像经典的 Controller 或 MultiActionController 那样非常特定的签名。当然,你可以使用标准的 HttpServletRequest / HttpServletResponse / ModelAndView 签名,但真正的威力在于使用更具体的参数。

“imagedb”示例展示了几个基本变体

@RequestMapping("/imageList")
public String showImageList(ModelMap model) {
model.addAttribute("images", this.imageDatabase.getImages());
return "imageList";
}

对于这个处理器方法,唯一需要解析的参数是 ModelMap。ModelMap 是 Spring 2.0 重新设计的 ModelAndView 对象的一部分,它封装了一组将暴露给视图的键值对。上面的代码只是调用 ImageDatabase 服务来加载 ImageDescriptor 对象列表,并将它们作为“images”属性名暴露出来。或者,你可以调用不带属性名的 addAttribute 变体,在这种情况下,属性名将根据给定的值类型推断出来(在本例中是:“imageDescriptorList”)。

返回值是一个 String,简单地表示要渲染的视图的名称。实际上,你可以编写同一个方法,不带参数,返回 ModelAndView——但上面的方法通常更容易阅读,并且避免了对 ModelAndView 对象的依赖。(请注意,ModelMap 是“ui”包中的一个通用类,而 ModelAndView 是“web.servlet”包中一个相当具体的类。)

@RequestMapping("/imageContent")
public void streamImageContent(@RequestParam("name") String name, OutputStream outputStream)
throws IOException {

this.imageDatabase.streamImage(name, outputStream);
}

这个处理器方法展示了一个完全不同的用例。它的目的是将图像内容从数据库流式传输到 HTTP 响应。它直接写入响应,而不是转发到视图;因此它的返回类型是 void。它使用 Spring 2.5 的新 @RequestParam 注解,将 HTTP 请求参数作为方法参数接收,以及一个 OutputStream 类型的参数,用于访问响应流的句柄。图像内容的实际加载再次委托给 ImageDatabase 服务。

或者,你可以使用更传统的 HttpServletRequest / HttpServletResponse 签名来实现相同的处理器方法,从而获得对精确 HTTP 处理的更多控制。然而,这会引入与 Servlet API 的更强耦合,并且需要更多的单元测试工作。

@RequestMapping("/imageContent")
public void streamImageContent(HttpServletRequest request, HttpServletResponse response)
throws IOException {

this.imageDatabase.streamImage(request.getParameter("name"), response.getOutputStream());
}

这类处理器方法的目的应该已经很清楚了:它们是 HTTP 请求世界和服务层世界之间非常简单的“桥梁”,用于适应请求参数和响应内容。让我们以图片上传处理程序为例,看看一个高级示例。

@RequestMapping("/imageUpload")
public String processImageUpload(
@RequestParam("name") String name, @RequestParam("description") String description,
@RequestParam("image") MultipartFile image) throws IOException {

this.imageDatabase.storeImage(name, image.getInputStream(), (int) image.getSize(), description);
return "redirect:imageList";
}

基本目的仍然是接受几个特定的 HTTP 请求参数,进行一些处理,然后返回一个视图的名称——在这种情况下,指示重定向到“imageList”路径。然而,这个特定的方法处理的是多部分文件上传,这就是为什么“image”参数被声明为 MultipartFile 类型。Spring 的 @RequestParam 处理会自动将其解析为多部分元素,以便处理器方法能够获取文件大小并作为 InputStream 访问上传的文件内容。

有关注解处理器方法支持的参数类型的完整列表,请参阅 @RequestMapping javadoc

超越无状态的多动作控制器

以上就是使用 Spring 2.5 的 Web 注解来实现多动作控制器。同样的控制器风格也可以支持基本表单处理,取代了经典的 SimpleFormController。这可以在 Spring 2.5 版本的 PetClinic 中看到,它的所有表单控制器现在都以注解风格实现,展示了表单对象和基于 JavaBean 的数据绑定的使用。关于这些表单处理功能的讨论将在后续博文中进行。

我在这里总结一下 Spring MVC 的目的:正好是无状态控制器、基本表单处理和灵活的视图渲染。MVC 本质上是 Spring 核心 Web 支持中以调度为中心的模块,服务于许多不同类型的用例的运行时——并且在其之上构建了更高级的功能。在这方面,它类似于 Java EE 5 的 JSF 运行时,后者也主要作为一个基本的 Web 平台来构建更高级的功能。

这就是 Spring Web Flow 的用武之地:SWF 是我们更高级的、面向会话的控制器引擎,对 MVC 视图JSF 视图 都有强大的支持。我强烈建议尝试 SWF 来构建 Web 用户界面,特别是在面对非平凡的导航和状态管理需求时。Spring Web Flow 2.0 里程碑中有激动人心的进展,与 Spring 2.5 MVC 基础的发展方向一致——但同时也特别侧重于 JSF,通过我们新的 Spring Faces 模块。请继续关注!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有