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 {