领先一步
VMware 提供培训和认证,助您加速进步。
了解更多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 发行版附带的“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";
}
}
这个控制器类究竟做了什么——它的设计目的是什么?让我们一步一步地来看……
构造函数被标记为 @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。
“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 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 模块。请继续关注!