问题引出
实现跨域配置时,导致YML的自动加载注解失效(WebMvcAutoConfiguration此注解失效)
问题一
@Configuration
//此注解有坑
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowCredentials(true)
.maxAge(3600);
}
}
问题二
@Configuration
public class WebConfiguration extends DelegatingWebMvcConfiguration{
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowCredentials(true)
.maxAge(3600);
}
}
问题三
@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport{
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowCredentials(true)
.maxAge(3600);
}
}
前置知识
@Import:即在含有该注解的类中导入代码中相应的类型的bean(DelegatingWebMvcConfiguration)并在Springboot启动时进行加载其类中实例,总结:实例化并加载导入的类(导入子类也会实例化父类)
@Import(DelegatingWebMvcConfiguration.class)
@ConditionalOnMissingBean:当WebMvcConfigurationSupport类型的bean不存在时加载含有该注解的类
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@EnableWebMvc
EnableWebMvc是什么?
直接看源码,@EnableWebMvc实际上引入一个DelegatingWebMvcConfiguration。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
...
}
所以@EnableWebMvc=继承DelegatingWebMvcConfiguration=继承WebMvcConfigurationSupport
@WebMvcAutoConfiguration
由@EnableAutoConfiguration注解引出@WebMvcAutoConfiguration
首先我们要搞清楚@EnableAutoConfiguration的设置到底代表的是啥,从自动装配的原理入手,我们翻spring.factories,和mvc有关的自动装配类,是如下几个:
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
注意最后一行,凭感觉,其中WebMvcAutoConfiguration一定和Spring MVC在Spring boot中自动装配最有关,找下去,这是class的声明。
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
...
}
这是因为在 springboot的web自动配置类 WebMvcAutoConfiguration 上有条件注解
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
这个注解的意思是在项目类路径中缺少WebMvcConfigurationSupport类型的bean时改自动配置类才会生效,所以继承 WebMvcConfigurationSupport 后需要自己再重写相应的方法。
1.可以看到,当WebMvcConfigurationSupport
类不存在的时候,该自动装配类才会创建出来,也就是说,如果我们使用@EnableWebMvc
,@EnableWebMvc
就相当于导入了WebMvcConfigurationSupport
类(前面介绍@EnableWebMvc有提到),这个时候,spring boot的自动装配就不会发生了,这个时候,我们能用的,只有WebMvcConfigurationSupport
提供的若干个配置
2.至此,可以说明,无论是使用第一种方法@EnableWebMvc
还是第二种方法扩展DelegatingWebMvcConfiguration
类及第三种方法扩展WebMvcConfigurationSupport
类,spring都会创建一个WebMvcConfigurationSupport
的类,进而屏蔽掉自动装配类WebMvcAutoConfiguration
。
那么为何扩展WebMvcConfigurationAdapter
(Java8 有接口默认方法后,该类被废弃,转为使用WebMvcConfigurer
接口,下文我们统一用WebMvcConfigurer
)不会屏蔽掉spring boot的自动装配呢?
这要再说WebMvcConfigurer
的工作方式了,实际上,使用@EnableWebMvc引入的类不是WebMvcConfigurationSupport
,而是DelegatingWebMvcConfiguration
。
DelegatingWebMvcConfiguration
继承了WebMvcConfigurationSupport
,并且DelegatingWebMvcConfiguration
通过依赖注入,持有Spring 容器所有WebMvcConfigurer
实现类的一个List,所有对spring mvc的自定义的WebMvcConfigurer
,都会挂到DelegatingWebMvcConfiguration
上去。
而WebMvcAutoConfiguration
内部,创建了一个继承了DelegatingWebMvcConfiguration
的内部类EnableWebMvcConfiguration
,这个类,一方面,提供了缺失的WebMvcConfigurationSupport
的功能,另一方面,就起到了收集所有WebMvcConfigurer
,调用它们的方法的作用。
最后我们对前面2点总结
- 无论是使用
@EnableWebMvc
还是WebMvcConfigurationSupport
,都会禁止Springboot的自动装配,只有使用WebMvcConfigurer
才不会。 - 虽然禁止了Springboot的自动装配,但是
WebMvcConfigurationSupport
本身,还是会注册一系列的MVC相关的bean的,从附加的api可以看到。 - 后面第2点可以得出
WebMvcAutoConfiguration
自动装备,其实会创建一个WebMvcConfigurationSupport
的子类,叫EnableWebMvcConfiguration
。
总结
implements WebMvcConfigurer
:不会覆盖@EnableAutoConfiguration关于WebMvcAutoConfiguration的配置@EnableWebMvc + implements WebMvcConfigurer
:会覆盖@EnableAutoConfiguration关于WebMvcAutoConfiguration的配置(问题引出第一种方法)extends DelegatingWebMvcConfiguration
:会覆盖@EnableAutoConfiguration关于WebMvcAutoConfiguration的配置(问题引出第二种方法)extends WebMvcConfigurationSupport
:会覆盖@EnableAutoConfiguration关于WebMvcAutoConfiguration的配置(问题引出第三种方法)
如果有配置文件继承了DelegatingWebMvcConfiguration,或者WebMvcConfigurationSupport,或者配置文件有@EnableWebMvc,那么 @EnableAutoConfiguration 中的WebMvcAutoConfiguration 将不会被自动配置,而是使用WebMvcConfigurationSupport及自己定义的配置。
额外的一些
细心的开发者会发现,WebMvcConfigurationSupport中那些子类可以重写的空方法
在WebMvcConfigurer都有,这说明WebMvcConfigurer只是WebMvcConfigurationSupport的一个扩展类,它并没有扩展新功能,只是为让用户更方便安全的添加自定义配置,为什么说是安全呢?因为如果直接继承
WebMvcConfigurationSupport,那么用户可以重写默认的配置,如果对原理不是很清楚地开发者不小心
重写了默认的配置,springmvc可能相关功能就无法生效,是一种不安全
的行为。
1.为什么WebMvcConfigurer实现要加@EnableWebMvc?
@EnableWebMvc注解类上导入了DelegatingWebMvcConfiguration类,该类是WebMvcConfigurationSupport的子类,该类除了实例化WebMvcConfigurationSupport实例以外,另一个作用就是收集BeanFactory中所有WebMvcConfigurer的实现,汇集到WebMvcConfigurerComposite中,在WebMvcConfigurationSupport实例化过程中会分别调用这些实现,将相应的实例传入这些实现中,供开发者在此基础上添加自定义的配置。这也就是在WebMvcConfigurer子类上要加@EnableWebMvc的原因,因为要先实例化WebMvcConfigurationSupport。
2.为什么可以存在多个WebMvcConfigurer的实现?
一般来讲一个应用中一个WebMvcConfigurer的已经足够,设计成收集多个是不是有些多余?从springboot的autoconfigure机制来看并不多余,反而更灵活,比如我要写一个mybatis的AutoConfiguration和JPA的AutoConfiguration,我就可以在不同的AutoConfiguration里面定义一个WebMvcConfigurer的实现,里面只配置与mybatis或JPA相关的配置,这样需要那个启用那个,不需要人工通过注释代码来转换mybatis和JPA,注意:在springboot下自定义的WebMvcConfigurer
实现配置类上是不需要添加@EnableWebMvc
的,因为springboot已经实例化了WebMvcConfigurationSupport,如果添加了该注解,默认的WebMvcConfigurationSupport配置类是不会生效的,也就是以用户定义的为主,一般建议还是不覆盖默认的好。
解决问题引出三种方法
@EnableWebMvc
此注解有坑,注释掉即可
@Configuration
//此注解有坑,注释掉即可
//@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowCredentials(true)
.maxAge(3600);
}
}