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 社群所有即將舉行的活動。

檢視所有