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 版本中满足了其中一些请求。

注册 Formatter

我们将从 ConverterFormatter 的注册开始,这可以通过提供您自己的 ConversionService 实例来实现,如下所示:


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

对于自定义 Formatter,您将继承 FormattingConversionServiceFactoryBean 并在代码中注册 Formatter。从 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>

您仍然可以选择在代码中注册 ConverterFormatter。这通过此版本中引入的 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 而不是 formatter setter 呢?当您需要在一个地方为特定格式化类别注册多个相关的转换器和格式器时,FormatterRegistrar 会很有用。另一种情况是注册一个索引在字段类型(而不是其自身的泛型类型 <T>)下的 Formatter,或者可能从 Printer/Parser 对注册一个 Formatter。Spring 框架中的 JodaTimeFormatterRegistrar 是一个实际的 FormatterRegistrar 实现的良好示例,您可以查看一下。

FormattingConversionServiceFactoryBean 中最后一个选项是通过 registerDefaultFormatters 标志完全关闭默认 Formatter 注册。

注册 HttpMessageConverter

从 3.1 M1 开始,您可以通过 mvc:annotation-driven 的子元素注册 HttpMessageConverter。例如:


<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

如果您以前从未见过 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

[callout title=更新]本节中的信息已过时。方法在里程碑 2 中已更改。请改阅 这篇 Spring MVC 3.1 M2 文章。[/callout]

在本帖的这一部分,我将使用一个现有的示例应用程序:mvc-showcase,许多用户可能从 Keith Donald 之前的帖子中熟悉它。我将用基于 Java 的配置替换其 XML 配置。这样做可以比较配置前后的代码和配置。

最终的示例应用程序可在 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 配置一起使用——它提供了一个简单的构造函数,而不是 Spring 在使用 XML 时调用的 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 的声明性,并且您使用的 IDE 在 Spring XML 配置的类和方法名称上具有代码补全功能,那么使用 XML 命名空间也是可以的。

正如最近在网络研讨会 Introducing 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 社区所有即将举行的活动。

查看所有