Spring 3.1 M1:MVC 命名空间增强和 @Configuration

工程 | Rossen Stoyanchev | 2011 年 2 月 21 日 | ...

在这篇描述 Spring 3.1 M1 特性系列文章的第五篇中,我将重点关注 Web 应用。前半部分将讨论 MVC XML 命名空间的增强。然后,我将展示如何通过全 Java 配置创建与 MVC 命名空间等效的功能。最后,我将提及在 3.1 M2 中你可以期待的一些与 Servlet 3.0 相关的配置变化。

MVC 命名空间改进

Spring MVC 3.0 提供了一个自定义的 MVC 命名空间。这个命名空间的核心——<mvc:annotation-driven> 元素,配置了处理带注解的控制器方法所需的一切。更重要的是,它设置了一系列默认值,用于处理类型转换、格式化、验证、请求和响应体的读取和写入等等。

随着时间的推移,你们中的一些人曾要求对上述默认配置的各个方面拥有更多控制权,我们在 3.1 M1 版本中解决了其中的许多请求。

注册 Formatters

我们先从 ConvertersFormatters 的注册开始,这可以通过提供你自己的 ConversionService 实例来完成,如下所示


<mvc:annotation-driven conversion-service="..." />

对于自定义的 Formatters,你会子类化 FormattingConversionServiceFactoryBean 并在代码中注册 Formatters。从 3.1 M1 开始,你可以使用 setter 声明式地注册 FormatterAnnontationFormatterFactory 类型


<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>

你仍然可以在代码中注册 ConvertersFormatters。这通过本版本中引入的 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 注册的能力。

注册 HttpMessageConverters

从 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 属性。

注册自定义 WebArgumentResolvers

如果你之前从未见过 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

列表中的最后一项是提供自定义 MessageCodesResolver 的能力


<mvc:annotation-driven message-codes-resolver="messageCodesResolver" />

<bean id="messageCodesResolver" class="org.example.CustomMessageCodesResolver" />

MVC 命名空间还有许多其他可以完成的事情。上面的列表应该有助于涵盖最常见的用例以增加灵活性,但如果您认为我们遗漏了其他重要的用例,请告知我们。

从 XML 到 @Configuration

[提示 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 包中创建 MvcFeaturesMvcBeansMvcFeatures 贡献 @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。前者提供了与后者相同的默认 ConverterFormatter 注册,但更适合与 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() 方法中使用了排除过滤器,以防止 MvcFeaturesMvcBeans 被注册两次——一次由 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 和环境抽象相结合。

希望你喜欢这些功能并觉得它们有用。请告诉我们你的想法。

获取 Spring 新闻邮件

订阅 Spring 新闻邮件保持联系

订阅

领先一步

VMware 提供培训和认证,助力你加速前进。

了解更多

获取支持

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

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看全部