1. Spring MVC 自动装配
1.1 WebMvcAutoConfiguration 装配原理
前文已经分析过,@EnableAutoConfiguration
这个注解会给容器中导入 AutoConfigurationImportSelector
组件,AutoConfigurationImportSelector
实现了 DeferredImportSelector 接口,所以这里会延时导入,之前版本没有实现这个接口,那么就会调用 selectImports()
方法,实现了就会调用 AutoConfigurationGroup#process()
方法,这个方法会把 META-INF\spring.factories
文件中 org.springframework.boot.autoconfigure.EnableAutoConfiguration
所对应的类导入到 Spring 容器中,org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
也是其中之一
Spring Boot 自动装配组件的条件(适用于大多数组件):先判断容器中是否有这个组件,如果没有就会加入一个默认的组件,如果有(即用户自定义了)就不会添加;如果这个组件允许有多个,那么就会将用户配置的和默认的组合起来使用,而且一般都会提供 customize()
方法
可以使用 debug=true
属性来让控制台打印自动配置报告,这样就可以知道哪些自动配置类生效
1.2 静态资源访问
1.2.1 静态资源文件夹映射
webjars 就是以 jar 包的方式引入静态资源,可以参考 https://www.webjars.org/
所有 /webjars/**
请求都去 classpath:/META-INF/resources/webjars/
找资源
/**
访问当前项目的任何资源,都去静态资源的文件夹找映射,有 5 个静态资源路径:classpath:/META-INF/resources/
, classpath:/resources/
,classpath:/static/
,classpath:/public/
,/
(/
表示当前项目根路径)
public void addResourceHandlers(ResourceHandlerRegistry registry) {
...
// 映射 webjars 资源路径
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache()
.getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod))
.setCacheControl(cacheControl));
}
// 映射静态资源路径,staticPathPattern 是 /**
// getResourceLocations() 包含了 5 个路径:"classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/","/"(表示当前项目根路径)
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(
this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod))
.setCacheControl(cacheControl));
}
}
1.2.2 配置欢迎页映射
静态资源文件夹下的所有 index.html
页面;被 /**
映射
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ApplicationContext applicationContext) {
return new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext),
applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
}
1.2.3 配置网站图标
所有的 **/favicon.ico
都是在静态资源文件下找
@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled",
matchIfMissing = true)
public static class FaviconConfiguration implements ResourceLoaderAware {
private final ResourceProperties resourceProperties;
private ResourceLoader resourceLoader;
public FaviconConfiguration(ResourceProperties resourceProperties) {
this.resourceProperties = resourceProperties;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
faviconRequestHandler()));
return mapping;
}
@Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler.setLocations(resolveFaviconLocations());
return requestHandler;
}
private List<Resource> resolveFaviconLocations() {
String[] staticLocations = getResourceLocations(
this.resourceProperties.getStaticLocations());
List<Resource> locations = new ArrayList<>(staticLocations.length + 1);
Arrays.stream(staticLocations).map(this.resourceLoader::getResource)
.forEach(locations::add);
locations.add(new ClassPathResource("/"));
return Collections.unmodifiableList(locations);
}
}
1.3 Spring MVC 提供的自动配置组件
1.3.1 ContentNegotiatingViewResolver
它包含了所有的视图解析器,Spring 会自动选择一个最佳的视图解析器解析,如果想定制自己的视图解析器,只需要往容器中注入一个 ViewResolver
组件即可
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver",
value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(
beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver uses all the other view resolvers to locate
// a view so it should have a high precedence
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
1.3.2 FormattingConversionService
this.mvcProperties.getDateFormat()
添加了一个时间转换器,可以用 spring.mvc.dateFormat
控制
@Bean
@Override
public FormattingConversionService mvcConversionService() {
WebConversionService conversionService = new WebConversionService(
this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
addFormatters
方法会调用如下方法,所以想添加自定义的格式化器和转换器,只需要放在容器中即可
public void addFormatters(FormatterRegistry registry) {
for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
registry.addConverter(converter);
}
for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
registry.addConverter(converter);
}
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
registry.addFormatter(formatter);
}
}
1.3.3 WebMvcAutoConfigurationAdapter
注意这个方法只有一个有参构造器,所以它的构造器参数都是来源于容器中,所以我们可以定制自己的 HttpMessageConverter
,然后放到容器中即可
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter
implements WebMvcConfigurer, ResourceLoaderAware {
...
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
...
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider
.getIfAvailable();
}
2. Spring MVC 组件扩展
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-boot-core/tutorial-spring-boot-web
工程
编写一个配置类(@Configuration
),是 WebMvcConfigurer 类型,不能标注 @EnableWebMvc
2.1 环境搭建
1. CustomSpringMvcConfig
通过实现 WebMvcConfigurer 接口,可以定制大量的 MVC 组件
@Configuration
class CustomSpringMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
}
}
2.2 原理分析
1. WebMvcAutoConfigurationAdapter
因为 WebMvcAutoConfiguration 中的静态 WebMvcAutoConfigurationAdapter 类标注了注解 @Import(EnableWebMvcConfiguration.class)
,即向容器中导入了 EnableWebMvcConfiguration
组件
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter
implements WebMvcConfigurer, ResourceLoaderAware {
2. EnableWebMvcConfiguration
EnableWebMvcConfiguration 继承了 DelegatingWebMvcConfiguration
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
3. DelegatingWebMvcConfiguration
它会将所有的 WebMvcConfigurer
相关的配置一起调用
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
3. Spring MVC 禁用自动装配
Spring Boot 对 Spring MVC 的自动配置不需要了,所有的配置都是我们自己配,只需要标注 @EnableWebMvc
即可
3.1 环境搭建
@EnableWebMvc
@SpringBootApplication
public class WebApplication {
3.2 原理分析
1. EnableWebMvc
@EnableWebMvc
会向容器中导入 DelegatingWebMvcConfiguration
组件
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
2. WebMvcAutoConfiguration
WebMvcAutoConfiguration
启用的前提条件是 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,而 DelegatingWebMvcConfiguration 正好是 WebMvcConfigurationSupport 类型,所以此注解会导致 Spring Mvc 自动装配全部失效
@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 {