Spring 3.1 M2: Spring MVC 增强

工程 | Rossen Stoyanchev | 2011年6月13日 | ...

本文重点介绍 Spring 3.1 M2 中 Spring MVC 的新特性。以下是讨论的主题:

  • 基于代码的 MVC 命名空间等效配置。
  • 可定制的 @MVC 处理。
  • 编程模型改进。

在此简单提醒,本文讨论的特性已在 Greenhouse 项目中实际应用。

基于代码的 Spring MVC 配置

正如 Chris 在上周五的他的博客文章中指出的那样,XML 命名空间极大地减少了配置,但同时也降低了透明度和灵活性。这对于 MVC 命名空间同样适用,它支持多种定制,但并非所有可用功能都支持。这意味着你或者使用它,或者完全放弃它。我们相信基于代码的配置为此提供了一个解决方案,并提供了一条从简单到高级的路径。

让我们从这个简单、熟悉的片段开始


<mvc:annotation-driven />

虽然使用注解控制器并非必须,但 <mvc:annotation-driven> 做了许多有用的事情——它检测是否存在 JSR-303 (Bean Validation) 实现并配置数据绑定,如果 Jackson JSON 库可用则添加 JSON 消息转换器,以及其他一些可以节省大量配置的功能。

现在让我们用基于代码的配置来实现同样的功能


@Configuration
@EnableWebMvc
public class WebConfig {
}

这里的 @EnableWebMvc 导入了一个 @Configuration 类,它与 <mvc:annotation-driven> 的功能相匹配。就这么简单。

下一步是在 <mvc:annotation-driven> 中使用属性,也许提供一个 FormattingConversionService,或者添加子元素来配置消息转换器,或者使用其他 MVC 命名空间元素,如 <mvc:interceptors><mvc:resources> 等。

让我们看看如何在基于代码的配置中完成所有这些


@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // register converters and formatters...
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // add message converters...
    }

    @Override
    public void configureInterceptors(InterceptorConfigurer configurer) {
        configurer.addInterceptor(new AccountExposingHandlerInterceptor());
    }

    @Override
    public void configureResourceHandling(ResourceConfigurer configurer) {
        configurer.addPathMapping("/resources/**").addResourceLocation("/resources/");
    }

    // more @Override methods ... 

}

注意这次我们还继承了 WebMvcConfigurerAdapter,这是一个方便的基类,它只包含 WebMvcConfigurer 接口的 方法实现。WebMvcConfigurer 接口定义了用于定制 Spring MVC 配置的回调。实现了这个接口的 @Configuration 类会被检测到并有机会应用定制。基本上就是这样。

上述方式在透明性方面可以说与 MVC 命名空间相当。你可以使用熟悉的 Java IDE 快捷键来探索 WebMvcConfigurer 接口和导入的 @Configuration 类的 @Bean 方法。

更高级的定制怎么办?对于 MVC 命名空间来说,我们只能做到这里。对于基于代码的配置,在下一个版本 (RC1) 中,你将能够 按原样 使用上面的示例,删除导入的配置(即移除 @EnableWebMvc),然后切换到一个包含你可以覆盖的 @Bean 方法的基类。这意味着你可以使用 Spring MVC 基于代码的配置,并且知道任何级别的定制都是可能的——无论是通过简单的回调机制还是直接扩展提供实际配置的类。

以下是 Greenhouse 项目中的web 配置

可定制的 @MVC 处理

@MVC 编程模型非常成功,使得控制器方法签名更加灵活。然而,许多人希望底层的 @MVC 支持类能够更具定制性。为此,我们推出了一套新的 @MVC 支持类,它们赋予你更多能力,并为我们构建更好的基础。

在使用注解之前,Controller 是处理端点。使用注解后,单个控制器方法成为端点,并带有自己的请求映射。按照这个逻辑,HandlerMapping 不应该局限于选择控制器,而应该选择一个 控制器方法。因此,我们添加了 HandlerMethod 抽象和 AbstractHandlerMethodMapping 用于选择 HandlerMethod 的处理映射,这是有意义的。

以下是围绕 HandlerMethod 抽象构建的新 @MVC 支持类

  • RequestMappingHandlerMapping
  • RequestMappingHandlerAdapter
  • ExceptionHandlerExceptionResolver

因此,我们现在在处理映射中拥有 一个单一的选择点,处理适配器以及其他组件都精确地知道选择了哪个处理方法。例如,许多人曾要求在 处理方法调用周围进行拦截,现在这个空白已被填补。另一个不太明显的优势是可以自由地在 不同控制器中映射相同的 URL,只要映射在其他方面有所不同(例如 HTTP 方法)。

除了请求映射,控制器方法的执行还需要解析方法参数(如 @RequestParameter, @ModelAttribute 等)和处理返回值(如 @ResponseBody, @ModelAttribute 等)。新的 @MVC 支持类公开了一种 可插拔 机制,通过它可以插入 HandlerMethodArgumentResolverHandlerMethodReturnValueHandler 的实现,以解析 每个 方法参数并处理 每个 返回值。你对此拥有 完全 控制权——你可以设计自己的参数类型和返回值类型,或者定制内置类型的处理方式。更多细节将在后续文章中介绍。

要尝试新的 @MVC 支持类,只需升级到 Spring 3.1 M2。MVC 命名空间和 @EnableWebMvc 都会配置它们。或者如果你有自己的配置,只需替换这些类:

  • DefaultAnnotationHandlerMapping -> RequestMappingHandlerMapping
  • AnnotationMethodHandlerAdapter -> RequestMappingHandlerAdapter
  • AnnotationMethodExceptionResolver -> ExceptionHandlerExceptionResolver

兼容性注意事项:现有的支持类将继续可用。但是,我们建议你今后切换到新的类。例如,下一节中的所有编程模型改进都只通过这种方式提供。新类在大多数情况下应该可以直接替换现有类,但有两个值得注意的区别。第一,你不能将任何现有的 AbstractUrlHandlerMapping 类型(例如 SimpleUrlHandlerMapping)与新的处理适配器结合使用,因为新的适配器期望的是 HandlerMethod 而不是一个 handler。第二,当两个 @RequestMapping 方法同样匹配一个请求时,你不能依赖方法名来区分。

编程模型改进

本节列出了在新 @MVC 支持类中引入的编程模型改进。

1. 声明的 @PathVariable 参数现在会自动添加到模型中。例如,这


@RequestMapping("/develop/apps/edit/{slug}")
public String editForm(@PathVariable String slug, Model model) {
	model.addAttribute("slug", slug);
    // ...
}

被替换为


@RequestMapping("/develop/apps/edit/{slug}")
public String editForm(@PathVariable String slug, Model model) {
    // model contains "slug" variable
}

2. 重定向字符串支持使用模型中的变量(包括声明的 @PathVariables)扩展 URI 模板。例如,这


@RequestMapping(
    value="/groups/{group}/events/{year}/{month}/{slug}/rooms", 
    method=RequestMethod.POST) 
public String createRoom(
    @PathVariable String group, @PathVariable Integer year, 
    @PathVariable Integer month, @PathVariable String slug) {
    // ...	
    return "redirect:/groups/" + group + "/events/" + year + "/" + month + "/" + slug;
}

被替换为


@RequestMapping(
    value="/groups/{group}/events/{year}/{month}/{slug}/rooms", 
    method=RequestMethod.POST) 
public String createRoom(
    @PathVariable String group, @PathVariable Integer year, 
    @PathVariable Integer month, @PathVariable String slug) {
    // ...	
    return "redirect:/groups/{group}/events/{year}/{month}/{slug}";
}

3. 数据绑定中支持 URI 模板变量。例如,这


@RequestMapping("/people/{firstName}/{lastName}/SSN")
public String find(Person person, 
                   @PathVariable String firstName, 
                   @PathVariable String lastName) {
    person.setFirstName(firstName);
    person.setLastName(lastName);
    // ...
}

被替换为


@RequestMapping("/people/{firstName}/{lastName}/SSN")
public String search(Person person) {
    // person.getFirstName() and person.getLastName() are populated
    // ...
}

4. 可消费和可生成媒体类型可以通过 @RequestMapping 指定。例如,这


@RequestMapping(value="/pets", headers="Content-Type=application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // ...
}

被替换为


@RequestMapping(value="/pets", consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // ...
}

除了更简洁之外,如果 URL 匹配但输入的媒体类型不匹配,上述代码会返回 NOT_ACCEPTABLE (406)。

5. 对于可生成媒体类型,这


@Controller
@RequestMapping(value = "/pets/{petId}", headers="Accept=application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {    
    // ...
}

被替换为


@Controller
@RequestMapping(value = "/pets/{petId}", produces="application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {    
    // ...
}

如果 URL 匹配但可接受的媒体类型不匹配,上述代码会返回 NOT_SUPPORTED_MEDIA_TYPE (415)。

总结

这个里程碑版本中有很多新内容。我鼓励大家尝试这些变化,并在 RC1 和 GA 版本发布前提供反馈。

我还想提请大家关注另一个正在进行的工作,即为 Spring MVC 应用提供集成测试支持。服务器端测试支持请参阅 Github 上的 spring-test-mvc 项目。客户端支持请查看 Spring Social 项目或关注以下 JIRA 票证 SPR-7951

订阅 Spring 邮件列表

通过 Spring 邮件列表保持联系

订阅

领先一步

VMware 提供培训和认证,助你飞速进步。

了解更多

获取支持

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

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部