天天看點

spring mvc之注解@EnableWebMvc前言1. 為什麼要繼承WebMvcConfigurerAdapter2. @EnableWebMvc注解起什麼用

前言

其實我接觸Java web開發比較晚,這句話的意思就是,我做開發的時候就使用的是比較新的技術了,比如spring boot,從來沒用過ssh那一套,雖然用了spring mvc,但也是基于spring boot封裝好的。

當然了,這有好處,也有壞處,好處是跟上了時代的潮流,壞處是對于被封裝的那一套了解不夠深刻。

今天在翻某些架構封裝的源碼時候,看到一些WEB項目的配置類繼承了WebMvcConfigurerAdapter類,然後自定義了一些配置。另外在這個類還有這個注解:@EnableWebMvc。

然後産生了兩個疑問:

1. 想要自定義spring mvc的配置為什麼繼承WebMvcConfigurerAdapter類

2. @EnableWebMvc注解起什麼用

帶着這兩個疑問,準備一探究竟

1. 為什麼要繼承WebMvcConfigurerAdapter

首先說明下,spring mvc有幾個核心元件,這些元件的配置都是可擴充的。這幾個核心元件不是本文的重點,也不做強調,需要了解的可以查閱相關資料。我以前看過《看透Spring MVC源代碼分析與實踐》作者:韓路彪,這本書中講解了spring mvc的九大元件,感興趣的可以看下。

那麼問題來了,如果想自定義這些配置,為什麼要繼承WebMvcConfigurerAdapter類。檢視了其中一些類的注釋,并不是一定要繼承這個類,但是如果想自定義一些進階配置,建議繼承它,什麼算進階?嘿嘿。

先介紹下這個類,這個類實作了WebMvcConfigurer接口的所有方法(都是空實作),這裡提一下WebMvcConfigurer接口,類的注釋上是這樣說明的:

定義回調方法,以通過{@code @EnableWebMvc}自定義啟用Spring MVC的基于Java的配置。

{@code @EnableWebMvc}已注釋的配置類可以實作此接口的回調,并有機會自定義預設配置。 考慮擴充{@link WebMvcConfigurerAdapter},它提供所有接口方法的存根實作。

這幾句注釋在我看來有下面幾個意思:

1. 使用@EnableWebMvc注解啟用spring mvc的基于java config的配置 

2. 實作WebMvcConfigurer接口的方法可以自定義spring mvc的配置

3. 對于第2個意思,建議采用繼承WebMvcConfigurerAdapter類來實作

4. 如果想要讓繼承WebMvcConfigurerAdapter的自定義配置的子類起作用,那這個類應該是配置類(比如加上注解@Configuration,畢竟這個類應該托管到spring 容器内,spring mvc才會知道這個子類,要不這些自定義配置怎麼起作用)

也就是說,想要啟用spring mvc的時候,應用使用注解@EnableWebMvc啟用spring mvc的配置,另外,如果想自定義這些配置,就使用一個可以托管到spring容器的配置類,繼承WebMvcConfigurerAdapter類并重寫需要自定義配置的那些方法。

到這裡, 我又産生了幾個疑問:

Q1. 我繼承了WebMvcConfigurerAdapter類并重寫需要自定義配置的那些方法,但是spring mvc是怎麼知道的

Q2. spring mvc怎麼知道我要自定義哪些配置,我自定義的配置會不會導緻預設配置不可用

Q3. 這個自定義配置的子類是怎麼和spring mvc關聯的

這些問題的答案,應該就在@EnableWebMvc注解這裡。是以,看下文

2. @EnableWebMvc注解起什麼用

@EnableWebMvc注解起什麼用?先看下源碼中第一行的注解說明:

将此注解添加到{@code @Configuration}類可從{@link WebMvcConfigurationSupport}導入Spring MVC配置

也就是說,這個注解應當加到有@Configuration注解的類上(意思是這個類應當是托管到spring容器的配置類),然後就可以從從{@link WebMvcConfigurationSupport}導入Spring MVC配置,問題在這個WebMvcConfigurationSupport類上。

再看下@EnableWebMvc注解類的源碼:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
           

重點在于:@Import(DelegatingWebMvcConfiguration.class)這裡,這是基于java config格式的配置類的導入,然後看下DelegatingWebMvcConfiguration的源碼:

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport 
           

那就明白了:DelegatingWebMvcConfiguration是繼承了WebMvcConfigurationSupport的配置類。

現在先來看下WebMvcConfigurationSupport是什麼,注釋是這樣解釋的:

這是提供MVC Java配置背後的配置的主類。 通常通過将{@link EnableWebMvc @EnableWebMvc}添加到應用程式{@link Configuration @Configuration}類來導入它。 另一種選擇

  進階選項是直接從此類擴充并根據需要覆寫方法,記住将{@link Configuration @Configuration}添加到子類和{@link Bean @Bean}以覆寫{@link Bean @Bean}方法。 有關更多詳細資訊,請參閱{@link EnableWebMvc @EnableWebMvc}的Javadoc。

這個WebMvcConfigurationSupport類的作用呢,其實提供了上文提到的spring mvc的幾個核心元件的能力。如果想要從此類擴充,隻需要繼承并重寫它的一些方法(有興趣的可以看下這個類的源碼)。

現在就是,DelegatingWebMvcConfiguration類繼承了WebMvcConfigurationSupport類并重寫了它的一些方法,并且DelegatingWebMvcConfiguration類是一個配置類被托管了spring容器,重點來了:

這裡說明了@EnableWebMvc注解的一個作用:

1. 啟用spring mvc的這幾個核心元件提供的能力(就是啟用了spring mvc)

如果還不明白,這裡解釋下:上文說了,@EnableWebMvc注解需要用在一個可以注冊到spring容器的配置類上,然後@EnableWebMvc注解導入了DelegatingWebMvcConfiguration配置類,這個類繼承了WebMvcConfigurationSupport類提供的spring mvc各個元件的能力并且這個類也被注冊到了spring容器。

那麼,@EnableWebMvc注解和自定義配置的關系在哪,我認為這算是@EnableWebMvc注解提供的第二個作用:

2. 支援自定義spring mvc配置的能力

而它的這個能力,關鍵在于DelegatingWebMvcConfiguration配置類,上文說了DelegatingWebMvcConfiguration類繼承自WebMvcConfigurationSupport類并重寫了它的一些方法,就是它重寫的這些方法,允許了我們增加自定義配置。

看一下DelegatingWebMvcConfiguration類的部分源碼,作個解釋:

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


    // 注意看這裡,這裡把spring容器的所有實作了WebMvcConfigurer接口的類的bean作為一個集合
    // 變量注入到了這裡。
    // 這就意味着,我們需要自定義spring mvc配置的那些配置類,都會被注入到這裡
    // 這樣就可以把所有配置(包括我們自定義的配置添加進去)
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}


	@Override
	protected void configurePathMatch(PathMatchConfigurer configurer) {
		this.configurers.configurePathMatch(configurer);
	}

	@Override
	protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		this.configurers.configureContentNegotiation(configurer);
	}

	@Override
	protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
		this.configurers.configureAsyncSupport(configurer);
	}

	@Override
	protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
		this.configurers.configureDefaultServletHandling(configurer);
	}
 //...
}
           

不用太多,上面幾行就明白了。

關鍵在于DelegatingWebMvcConfiguration類的這個屬性上:

private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

// 看下WebMvcConfigurerComposite類的部分源碼:
//可以看到這個類有個屬性delegates,是個WebMvcConfigurer接口的實作類的集合
class WebMvcConfigurerComposite implements WebMvcConfigurer {

	private final List<WebMvcConfigurer> delegates = new ArrayList<WebMvcConfigurer>();


	public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.delegates.addAll(configurers);
		}
	}


	@Override
	public void configurePathMatch(PathMatchConfigurer configurer) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.configurePathMatch(configurer);
		}
	}

	@Override
	public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.configureContentNegotiation(configurer);
		}
	}
//...
}
           

注意看下,我上面貼的兩段代碼中加的一些中文注釋。結合起來一看,這樣就明白了,DelegatingWebMvcConfiguration類會把所有實作了接口WebMvcConfigurer的類(子類也是,這是java文法,就不說了)包括我們那些托管到spring容器的自定義的配置類(因為也實作了它)都會把這些配置加上。

這也就是解釋了第1節中提到的Q1、Q3的問題:我的配置類注冊到了spring容器中,spring通過自動注入的方式把所有WebMvcConfigurer接口的實作類注入到了DelegatingWebMvcConfiguration的configurers屬性中,在WebMvcConfigurerComposite類把這些配置都給配置上。然後回調那些實作了WebMvcConfigurer接口的實作類,最終将我們自定義的配置都給加上。

現在,就剩Q2這個問題了,spring mvc怎麼知道我自定義哪些配置了,在WebMvcConfigurerComposite類回調我們重寫方法的接口時,如果我們重寫了需要自定義配置的方法,自然就加上了,現在的問題是第二個,如果自定義了配置,是否會加載預設配置?這個就看自定義誰的配置了,比如HttpMessageConverter,如果在重寫了方法configMessageConverters自定義了配置,就不會加載預設配置,如果重寫的方法是extendMessageContertes就會加載自定義的和預設的,看下源碼就明白了:

/**
	 * Provides access to the shared {@link HttpMessageConverter}s used by the
	 * {@link RequestMappingHandlerAdapter} and the
	 * {@link ExceptionHandlerExceptionResolver}.
	 * This method cannot be overridden.
	 * Use {@link #configureMessageConverters(List)} instead.
	 * Also see {@link #addDefaultHttpMessageConverters(List)} that can be
	 * used to add default message converters.
	 */
	protected final List<HttpMessageConverter<?>> getMessageConverters() {
		if (this.messageConverters == null) {
			this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
			configureMessageConverters(this.messageConverters);
			if (this.messageConverters.isEmpty()) {
                // 這裡如果非空的就不加載預設配置了,注釋上也有解釋
				addDefaultHttpMessageConverters(this.messageConverters);
			}
			extendMessageConverters(this.messageConverters);
		}
		return this.messageConverters;
	}
           

其它幾個元件,有興趣可以查閱相關資料,或者翻下源碼了解下。

純粹是自已瞎研究,有問題歡迎及時指正。

繼續閱讀