前言
其實我接觸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;
}
其它幾個元件,有興趣可以查閱相關資料,或者翻下源碼了解下。
純粹是自已瞎研究,有問題歡迎及時指正。