领先一步
VMware 提供培训和认证,助力您的进步。
了解更多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 发行版附带的 "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 的核心功能,即注解驱动的依赖注入:将调用此构造函数,并传入从 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。
"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 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。请密切关注!