领先一步
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 调用程序/Hessian/Burlap 服务导出器或用于 Web 服务的 XFire 导出器时,他们会利用这一点。DispatcherServlet 甚至可以托管任意第三方 Servlet,允许在 Spring 环境中配置和管理这些 Servlet。
让我们看一个例子,取自 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“)。
**返回值是字符串**,仅表示要呈现的视图的名称。本质上,您可以编写相同的方法,不带任何参数和 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** 模块。敬请关注!