领先一步
VMware 提供培训和认证,助力你加速前进。
了解更多在这篇描述 Spring 3.1 M1 特性 的系列文章的第五篇中,我将重点关注 Web 应用。前半部分将讨论 MVC XML 命名空间的增强。然后,我将展示如何通过全 Java 配置创建与 MVC 命名空间等效的功能。最后,我将提及在 3.1 M2 中你可以期待的一些与 Servlet 3.0 相关的配置变化。
Spring MVC 3.0 提供了一个自定义的 MVC 命名空间。这个命名空间的核心——<mvc:annotation-driven>
元素,配置了处理带注解的控制器方法所需的一切。更重要的是,它设置了一系列默认值,用于处理类型转换、格式化、验证、请求和响应体的读取和写入等等。
随着时间的推移,你们中的一些人曾要求对上述默认配置的各个方面拥有更多控制权,我们在 3.1 M1 版本中解决了其中的许多请求。
我们先从 Converters
和 Formatters
的注册开始,这可以通过提供你自己的 ConversionService
实例来完成,如下所示
<mvc:annotation-driven conversion-service="..." />
对于自定义的 Formatters,你会子类化 FormattingConversionServiceFactoryBean
并在代码中注册 Formatters。从 3.1 M1 开始,你可以使用 setter 声明式地注册 Formatter
和 AnnontationFormatterFactory
类型
<mvc:annotation-driven conversion-service="conversionService" />
<bean class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<list>
<bean class="org.example.EmailFormatter"/>
<bean class="org.example.PhoneAnnotationFormatterFactory"/>
</list>
</property>
</bean>
你仍然可以在代码中注册 Converters
和 Formatters
。这通过本版本中引入的 FormatterRegistrar
接口完成。下面是一个示例
public class FinancialInstrumentFormatterRegistry implements FormatterRegistrar {
public void registerFormatters(FormatterRegistry registry) {
// Register custom Converters and Formatters here...
}
}
你的 FormatterRegistrary
可以这样接入
<bean class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatterRegistrars">
<list>
<bean class="org.example.FinancialInstrumentFormatterRegistrar"/>
</list>
</property>
</bean>
那么,什么时候应该使用 FormatterRegistrar
而不是 formatters setter 呢?当需要从一个地方为特定格式类别注册多个相关的 converters 和 formatters 时,FormatterRegistrar
很有用。另一种情况是将 Formatter
注册到与其自身泛型类型 <T> 不同的字段类型下,或者可能从 Printer
/Parser
对注册 Formatter
。Spring Framework 中的 JodaTimeFormatterRegistrar
是一个实际的 FormatterRegistrar
实现的很好例子,所以可以看看它。
FormattingConversionServiceFactoryBean
中的最后一个选项是通过 registerDefaultFormatters
标志完全关闭默认的 Formatter
注册的能力。
从 3.1 M1 开始,你可以通过 mvc:annotation-driven
的子元素注册 HttpMessageConverters
。例如
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.google.protobuf.spring.http.ProtobufHttpMessageConverter"/>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="prefixJson" value="true"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
以这种方式提供的消息转换器列表优先于 MVC 命名空间默认注册的消息转换器。例如,上面我们添加了一个自定义转换器 ProtobufHttpMessageConverter
,并且还提供了一个根据应用程序需求定制的 Spring MVC 的 MappingJacksonHttpMessageConvert
实例。
如果你不想默认注册任何消息转换器,可以在 <mvc:message-converters>
元素上使用 register-defaults
属性。
如果你之前从未见过 WebArgumentResolver
,它用于解析 @RequestMapping
方法中的自定义参数。Spring Mobile 项目有一个 SitePreferenceWebArgumentResolver
。它解析 SitePreference
方法参数类型,该类型指示用户是想要页面的移动版本还是完整版本。从 Spring 3.1 M1 开始,你可以通过 MVC 命名空间注册自定义参数解析器
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.mobile.device.site.SitePreferenceWebArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
列表中的最后一项是提供自定义 MessageCodesResolver
的能力
<mvc:annotation-driven message-codes-resolver="messageCodesResolver" />
<bean id="messageCodesResolver" class="org.example.CustomMessageCodesResolver" />
MVC 命名空间还有许多其他可以完成的事情。上面的列表应该有助于涵盖最常见的用例以增加灵活性,但如果您认为我们遗漏了其他重要的用例,请告知我们。
[提示 title=更新]此部分信息已过时。该方法在里程碑 2 中已更改。请阅读这篇 Spring MVC 3.1 M2 文章代替。[/提示]
在这篇文章的这一部分,我将使用一个现有的示例应用程序:许多人可能从 Keith Donald 之前的文章中熟悉的 mvc-showcase,并将其 XML 配置替换为基于 Java 的配置。这样做可以比较配置前后的代码和配置。
生成的示例应用程序可在 spring-3.1-mvc-java-config 处检出。你可以直接在 GitHub 上浏览源代码,或按照 README 说明获取代码到本地。
我们的第一步是修改 web.xml 以指向我们的基于 Java 的配置,并指定用于处理该配置的 ApplicationContext
类型。以下是相关的 web.xml 片段
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
org.springframework.samples.mvc.config.MvcFeatures
org.springframework.samples.mvc.config.MvcBeans
</param-value>
</init-param>
</servlet>
接下来,我们将在 ~.config 包中创建 MvcFeatures
和 MvcBeans
。MvcFeatures
贡献 @Feature
方法,是我们的主要关注点
@FeatureConfiguration
class MvcFeatures {
@Feature
public MvcAnnotationDriven annotationDriven(ConversionService conversionService) {
return new MvcAnnotationDriven().conversionService(conversionService)
.argumentResolvers(new CustomArgumentResolver());
}
// ...
}
以上代码片段等同于以下 XML 命名空间配置
<mvc:annotation-driven conversion-service="conversionService">
<mvc:argument-resolvers>
<bean class="org.springframework.samples.mvc.data.custom.CustomArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
正如你所见,MvcAnnotationDriven
使用方便的链式方法 API 提供了与 XML 元素相同的选项。另外请注意,我们声明了一个 ConversionService
方法参数。此参数通过类型自动装配并注入。其声明可以在 MvcBeans
中找到
@Configuration
public class MvcBeans {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService bean = new DefaultFormattingConversionService();
bean.addFormatterForFieldAnnotation(new MaskFormatAnnotationFormatterFactory());
return bean;
}
// ...
}
注意使用了 DefaultFormattingConversionService
而不是通常在 XML 配置中熟悉的 FormattingConversionServiceFactoryBean
。前者提供了与后者相同的默认 Converter
和 Formatter
注册,但更适合与 Java 配置一起使用——它提供了一个简单的构造函数,而不是在使用 XML 时由 Spring 调用的 FactoryBean
生命周期初始化方法。
MvcFeatures
的剩余部分声明了与 <mvc:resources>
、<mvc:view-controller>
和 <context:component-scan>
元素等效的内容
@FeatureConfiguration
class MvcFeatures {
// ...
@Feature
public MvcResources resources() {
return new MvcResources("/resources/**", "/resources/");
}
@Feature
public MvcViewControllers viewController() {
return new MvcViewControllers("/", "home");
}
@Feature
public ComponentScanSpec componentScan() {
return new ComponentScanSpec("org.springframework.samples").excludeFilters(
new AnnotationTypeFilter(Configuration.class),
new AnnotationTypeFilter(FeatureConfiguration.class));
}
}
有两点值得注意。一是只需要一个 MvcViewControllers
实例就可以使用链式方法调用定义任意数量的视图控制器。二是 componentScan()
方法中使用了排除过滤器,以防止 MvcFeatures
和 MvcBeans
被注册两次——一次由 AnnotationConfigWebApplicationContext
注册,另一次由组件扫描注册。
为了完整性,这是 MvcBeans
的剩余部分
@Configuration
public class MvcBeans {
// ...
@Bean
public InternalResourceViewResolver jspViewResolver() {
InternalResourceViewResolver bean = new InternalResourceViewResolver();
bean.setPrefix("/WEB-INF/views/");
bean.setSuffix(".jsp");
return bean;
}
@Bean
public MultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
}
最后一步是移除位于 /WEB-INF/spring
下的 Spring XML 配置。
至此,我们已经通过全 Java 式的 Spring 配置引导了一个 Web 应用。既然已经引入了 @FeatureConfiguration
和 @Feature
,你可以期待看到越来越多的 FeatureSpecification
实现作为自定义 XML 命名空间的替代方案。我个人很喜欢 Java 配置的最终结果,但这当然不意味着我现有的应用需要切换。关键在于拥有选择。如果你偏好 XML 的声明性,并且使用带有 Spring XML 配置中类名和方法名代码补全功能的 IDE,那么使用 XML 命名空间也很好。
正如最近在网络研讨会 Spring Framework 3.1 介绍 中听到的,在 Spring 3.1 的里程碑 2 中,你可以期待看到 Servlet 3.0 支持,包括无 XML 的 Web 应用设置(即没有 web.xml),这与本文和本博客系列之前文章中演示的 AnnotationConfigWebApplicationContext 和环境抽象相结合。
希望你喜欢这些功能并觉得它们有用。请告诉我们你的想法。