WebMvcConfigurer是spring内部配置的一种方式,使用JavaBean的方式代替传统的xml配置;也可以自定义扩展配置类,实现方式是继承WebMvcConfigurer接口;WebMvcConfigurer其实就是一个接口,具体的配置是由实现类来决定的,现在会有两个问题,具体的实现类有哪些?这些实现类是如何加载到容器之中并生效的?带着这两个问题开启我们源码的探索之旅。
WebMvcConfigurer具体实现类
1.系统自带实现类
- WebMvcAutoConfigurationAdapter是Spring的主要配置类(会集成其它配置类到当前类),几乎所有的缺省配置都是在此类中配置,此配置类的优先级是0
- SpringDataWebConfiguration一些系统配置类,暂无仔细研究,此配置的优先级是最高的
- WebMvcConfigurerComposite此类是一个委托代理类,在DelegatingWebMvcConfiguration类中实例化,并将系统自带或者自定义的配置类注入到成员变量delegates之中。
2.自定义配置实现类
自定义实现类需要实现WebMvcConfigurer接口,优先级默认介于上述两个系统自带配置类,可以通过@Order注解或者Order接口来调整优先级
自定义配置文件示例如下(以配置路由规则方法为例):
@Configuration
@EnableConfigurationProperties(WebProperties.class)
public class WebAutoConfiguration implements WebMvcConfigurer {
@Autowired
private WebProperties webProperties;
/**
* 配置路由规则
*
* @param configurer
*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
AntPathMatcher matcher = new AntPathMatcher();
//区分大小写,默认true
matcher.setCaseSensitive(webProperties.getPath().isCaseSensitive());
//是否去除前后空格,默认false
matcher.setTrimTokens(webProperties.getPath().isTrimTokens());
//分隔符
matcher.setPathSeparator(CharacterUtils.PATH_SEPARATOR);
//是否缓存匹配规则,默认null等于true
matcher.setCachePatterns(webProperties.getPath().isCachePatterns());
//设置路由匹配规则
configurer.setPathMatcher(matcher);
//设置URL末尾是否支持斜杠,默认true,如/a/b/有效,/a/b也有效
configurer.setUseTrailingSlashMatch(webProperties.getPath().isUseTrailingSlashMatch());
//给所有的接口统一添加前缀
configurer.addPathPrefix(webProperties.getPath().getPrefix(), c -> {
if (c.isAnnotationPresent(RestController.class) || c.isAnnotationPresent(Controller.class)) {
return true;
}
return false;
});
}
}
系统自带配置实现类和自定义配置实现类如何生效?
DelegatingWebMvcConfiguration是对Spring MVC进行配置的一个代理类,它结合缺省配置和用户自定义配置最终确定使用的配置。
DelegatingWebMvcConfiguration继承自WebMvcConfigurationSupport,而WebMvcConfigurationSupport为Spring MVC提供缺省配置,它提供的就是上面提到的缺省配置。
看如下源码,DelegatingWebMvcConfiguration代理类会创建一个WebMvcConfigurerComposite代理类,并将容器之中的缺省配置类和自定义配置类注入到代理类之中;
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
//创建代理类实例对象
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//将容器之中实现了WebMvcConfigurer接口的缺省配置类和自定义配置类注入到参数configurers之中
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
//将配置类添加到代理类属性List集合中
this.configurers.addWebMvcConfigurers(configurers);
}
}
...
}
看下代理类WebMvcConfigurerComposite的源码:
/**
* 代理类继承WebMvcConfigurer接口,但是它是在DelegatingWebMvcConfiguration类中通过
* new 实例化的对象,所以不会注入到代理类之中
**/
class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
/**
* 将WebMvcConfigurer实现类的bean添加到代理类集合
**/
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);
}
}
...
}
EnableWebMvcConfiguration配置类继承了DelegatingWebMvcConfiguration代理类,类似@EnableWebMvc注解的功能,代理类中初始化配置的方法几乎都是在这个类中通过super来调用启动的;如下是调用代理类DelegatingWebMvcConfiguration获取控制器方法进行初始化的方法:
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
resourceUrlProvider);
}
父类DelegatingWebMvcConfiguration(WebMvcConfigurationSupport)的requestMappingHandlerMapping方法:
/**
* Return a {@link RequestMappingHandlerMapping} ordered at 0 for mapping
* requests to annotated controllers.
*/
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
//初始化RequestMappingHandlerMapping类,初始化完成后会调用afterPropertiesSet方法
//接下来就是加载容器中所有的控制器方法信息,将RequestMappingInfo和HandlerMethod方法注册入
//缓存
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
UrlPathHelper pathHelper = configurer.getUrlPathHelper();
if (pathHelper != null) {
mapping.setUrlPathHelper(pathHelper);
}
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
mapping.setPathMatcher(pathMatcher);
}
Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
return mapping;
}
具体的缺省配置基本上都是在代理类DelegatingWebMvcConfiguration的父类WebMvcConfigurationSupport中实现的,像RequestMappingHandlerMapping初始化、RequestMappingHandlerAdapter适配器类初始化等等;
WebMvcAutoConfigurationAdapter是一个适配器类,使用@Import注解将EnableWebMvcConfiguration配置引入,可以说是spring 缺省配置的一个集合;
WebMvcAutoConfiguration是一个自动化配置类,会在bean WebMvcConfigurationSupport不存在的时候初始化,所以这也是我们实现自定义配置的时候为什么不继承WebMvcConfigurationSupport类的原因;