前言
其实我接触Java web开发比较晚,这句话的意思就是,我做开发的时候就使用的是比较新的技术了,比如spring boot,从来没用过ssh那一套,虽然用了spring mvc,但也是基于spring boot封装好的。
当然了,这有好处,也有坏处,好处是跟上了时代的潮流,坏处是对于被封装的那一套了解不够深刻。
今天在翻某些框架封装的源码时候,看到一些WEB项目的配置类继承了WebMvcConfigurerAdapter类,然后自定义了一些配置。另外在这个类还有这个注解:@EnableWebMvc。
然后产生了两个疑问:
1.想要自定义spring mvc的配置为什么继承WebMvcConfigurerAdapter类
2.@EnableWebMvc注解起什么用
带着这两个疑问,准备一探究竟
为什么要继承WebMvcConfigurerAdapter
首先说明下,spring mvc有几个核心组件,这些组件的配置都是可扩展的。\ 这几个核心组件不是本文的重点,也不做强调,需要了解的可以查阅相关资料。
我以前看过《看透Spring MVC源代码分析与实践》作者:韩路彪,这本书中讲解了spring mvc的九大组件,感兴趣的可以看下。
那么问题来了,如果想自定义这些配置,为什么要继承WebMvcConfigurerAdapter类。查看了其中一些类的注释,并不是一定要继承这个类,但是如果想自定义一些高级配置,建议继承它,什么算高级?嘿嘿。
先介绍下这个类,这个类实现了WebMvcConfigurer接口的所有方法(都是空实现),这里提一下WebMvcConfigurer接口,类的注释上是这样说明的:
定义回调方法,以通过{@code @EnableWebMvc}自定义启用Spring MVC的基于Java的配置。\ {@code @EnableWebMvc}已注释的配置类可以实现此接口的回调,并有机会自定义默认配置。 考虑扩展{@link WebMvcConfigurerAdapter},它提供所有接口方法的存根实现。
这几句注释在我看来有下面几个意思:
使用@EnableWebMvc注解启用spring mvc的基于java config的配置
实现WebMvcConfigurer接口的方法可以自定义spring mvc的配置
对于第2个意思,建议采用继承WebMvcConfigurerAdapter类来实现
如果想要让继承WebMvcConfigurerAdapter的自定义配置的子类起作用,那这个类应该是配置类(比如加上注解@Configuration,毕竟这个类应该托管到spring 容器内,spring mvc才会知道这个子类,要不这些自定义配置怎么起作用)
也就是说,想要启用spring mvc的时候,应用使用注解@EnableWebMvc启用spring mvc的配置,另外,如果想自定义这些配置,就使用一个可以托管到spring容器的配置类,继承WebMvcConfigurerAdapter类并重写需要自定义配置的那些方法。
到这里, 我又产生了几个疑问:
Q1. 我继承了WebMvcConfigurerAdapter类并重写需要自定义配置的那些方法,但是spring mvc是怎么知道的
Q2. spring mvc怎么知道我要自定义哪些配置,我自定义的配置会不会导致默认配置不可用
Q3. 这个自定义配置的子类是怎么和spring mvc关联的
这些问题的答案,应该就在@EnableWebMvc注解这里。所以,看下文
@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@interfaceEnableWebMvc{}
重点在于:@Import(DelegatingWebMvcConfiguration.class)这里,这是基于java config格式的配置类的导入,然后看下DelegatingWebMvcConfiguration的源码:
@ConfigurationpublicclassDelegatingWebMvcConfigurationextendsWebMvcConfigurationSupport
那就明白了: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类的部分源码,作个解释:
@ConfigurationpublicclassDelegatingWebMvcConfigurationextendsWebMvcConfigurationSupport{privatefinalWebMvcConfigurerCompositeconfigurers=newWebMvcConfigurerComposite();//注意看这里,这里把spring容器的所有实现了WebMvcConfigurer接口的类的bean作为一个集合//变量注入到了这里。//这就意味着,我们需要自定义springmvc配置的那些配置类,都会被注入到这里//这样就可以把所有配置(包括我们自定义的配置添加进去)@Autowired(required=false)publicvoidsetConfigurers(List<WebMvcConfigurer>configurers){if(!CollectionUtils.isEmpty(configurers)){this.configurers.addWebMvcConfigurers(configurers);}}@OverrideprotectedvoidconfigurePathMatch(PathMatchConfigurerconfigurer){this.configurers.configurePathMatch(configurer);}@OverrideprotectedvoidconfigureContentNegotiation(ContentNegotiationConfigurerconfigurer){this.configurers.configureContentNegotiation(configurer);}@OverrideprotectedvoidconfigureAsyncSupport(AsyncSupportConfigurerconfigurer){this.configurers.configureAsyncSupport(configurer);}@OverrideprotectedvoidconfigureDefaultServletHandling(DefaultServletHandlerConfigurerconfigurer){this.configurers.configureDefaultServletHandling(configurer);}//...}
不用太多,上面几行就明白了。
关键在于DelegatingWebMvcConfiguration类的这个属性上:
privatefinalWebMvcConfigurerCompositeconfigurers=newWebMvcConfigurerComposite();//看下WebMvcConfigurerComposite类的部分源码://可以看到这个类有个属性delegates,是个WebMvcConfigurer接口的实现类的集合classWebMvcConfigurerCompositeimplementsWebMvcConfigurer{privatefinalList<WebMvcConfigurer>delegates=newArrayList<WebMvcConfigurer>();publicvoidaddWebMvcConfigurers(List<WebMvcConfigurer>configurers){if(!CollectionUtils.isEmpty(configurers)){this.delegates.addAll(configurers);}}@OverridepublicvoidconfigurePathMatch(PathMatchConfigurerconfigurer){for(WebMvcConfigurerdelegate:this.delegates){delegate.configurePathMatch(configurer);}}@OverridepublicvoidconfigureContentNegotiation(ContentNegotiationConfigurerconfigurer){for(WebMvcConfigurerdelegate: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就会加载自定义的和默认的,看下源码就明白了:
/***Providesaccesstotheshared{@linkHttpMessageConverter}susedbythe*{@linkRequestMappingHandlerAdapter}andthe*{@linkExceptionHandlerExceptionResolver}.*Thismethodcannotbeoverridden.*Use{@link#configureMessageConverters(List)}instead.*Alsosee{@link#addDefaultHttpMessageConverters(List)}thatcanbe*usedtoadddefaultmessageconverters.*/protectedfinalList<HttpMessageConverter<?>>getMessageConverters(){if(this.messageConverters==null){this.messageConverters=newArrayList<HttpMessageConverter<?>>();configureMessageConverters(this.messageConverters);if(this.messageConverters.isEmpty()){//这里如果非空的就不加载默认配置了,注释上也有解释addDefaultHttpMessageConverters(this.messageConverters);}extendMessageConverters(this.messageConverters);}returnthis.messageConverters;}
其它几个组件,有兴趣可以查阅相关资料,或者翻下源码了解下。