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

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

Spring 2.5 引入了一种编写注解式 Web MVC 控制器的方法,我们对此尚未进行太多博客介绍... 我将借此机会向大家概述当下 Spring MVC 的真正意义所在。

Spring MVC 本质上是一个请求分发器框架,具有 Servlet API 变体和 Portlet API 变体。它在其宿主环境(Servlet 或 Portlet)中紧密运行。可以将 Spring MVC 视为在 Servlet/Portlet 容器之上提供基础设施和便利:例如,灵活的请求映射、控制器处理和视图渲染阶段的分离、数据绑定、补充 JSTL 的基本 JSP 标签库等。这些是复杂 HTTP 请求处理的基石。

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

Spring 2.5 的注解式控制器

那么 Spring 2.5 基于注解的控制器方法是如何融入这个图景的呢?非常简单:它本质上是 DispatcherServlet / DispatcherPortlet 支持的一种替代控制器类型,它不实现特定的接口,而是使用注解来表达特定处理器方法的请求映射。它主要是实现多动作控制器的下一代风格,取代了 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 的核心功能,即注解驱动的依赖注入:将调用此构造函数,并传入从 Spring ApplicationContext 按类型获取的 ImageDatabase 类型的 Spring bean。在我们的例子中,这是来自应用程序服务层的核心 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 对象的 List,并将其以属性名 "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 基础的发展方向一致——同时也通过我们新的 Spring Faces 模块特别关注 JSF。请密切关注!

获取 Spring 资讯

订阅 Spring 资讯,保持连接

订阅

领先一步

VMware 提供培训和认证,助力您的进步。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

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

查看全部