SpringBoot 学习笔记 Part08
1. WebMvcAutoConfiguration
SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类),WebMvcAutoConfiguration 是 SpringMVC 功能的自动配置类。
根据我们之前的学习,打开libs里的spring的自动配置类包,
找到 WebMvcAutoConfiguration ,
观察它的按需加载条件,我们可以发现这个配置类是生效的。
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {}
2. WebMvcAutoConfigurationAdapter
接下来我们可以观察这个配置类给容器中放了些什么组件,其中有一个静态内部类为WebMvcAutoConfigurationAdapter,也是一个配置类。
@Configuration(
proxyBeanMethods = false
)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}
在研究自动配置时,一定要注意@EnableConfigurationProperties这个注解,它是用于把配置文件里的数据和配置文件类进行绑定。我们可以顺着这个线索去找到它配置了什么。
打开WebMvcProperties和ResourceProperties配置文件类的源码,我们可以发现WebMvcProperties的绑定前缀为spring.mvc、ResourceProperties的绑定前缀为spring.resources。
2.1 有参构造器
现在看静态内部类WebMvcAutoConfigurationAdapter的方法体,方法体中有一个有参构造器。在springboot中,若只有一个有参构造器,那么这个构造方法的所有参数的值都将从IoC容器中找。
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = webProperties.getResources();
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
this.mvcProperties.checkConfiguration();
}
对上面的各种参数进行分析,可以知道它们的功能和作用:
- ResourceProperties resourceProperties:获取和spring.resources绑定的所有的值的对象
- WebMvcProperties mvcProperties:获取和spring.mvc绑定的所有的值的对象
- ListableBeanFactory:相当于找到Spring的IoC容器,也就是Spring的bean工厂
- HttpMessageConverters:找到系统中所有的HttpMessageConverters(后面会学习)
- ResourceHandlerRegistrationCustomizer:找到资源处理器的自定义器
- DispatcherServletPath:DispatcherServlet处理的路径
- ServletRegistrationBean:给应用注册Servlet、Filter、Listener等的组件
继续看方法体,可以发现该配置类为容器中加入了许多组件,且都是按需加载@ConditionalOnMissingBean,以下用springmvc自动注入视图解析器的源码为例,还有许多组件如国际化消息解析器、格式化器、文件上传等。
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
return resolver;
}
2.2 资源处理的默认规则
接着往下,我们可以找到一个方法 addResourceHandlers,作用是添加资源处理器。所有的资源默认的规则都可以在这里观察到。
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
//配置 webjars 的规则
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
//配置 静态资源路径 的规则
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
在 this.resourceProperties.isAddMappings( )我们可以发现,在spring.resources配置文件下,有一个boolean属性addMappings。
addMappings属性:默认是 true,若配置为 false 则 else 里注册一系列组件(观察代码可以发现都是一些静态资源访问的相关配置)的代码段将不会生效。
spring:
web:
resources:
add-mappings: false # 修改为false,最终效果是禁用掉项目中的所有静态资源,无论怎么样都访问不了了
在addResourceHandlers里有addResourceHandler方法,观察可发现这个方法它其实就是用来注册访问规则的。
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
if (!registry.hasMappingForPattern(pattern)) {
ResourceHandlerRegistration registration = registry.addResourceHandler(new String[]{pattern});
customizer.accept(registration);
//这个方法还有缓存控制的功能,用于将这些资源缓存一段时间。也就是我们访问过一次后,浏览器会为我们缓存,就不用每次都向服务器请求这个静态文件了。 registration.setCachePeriod(this.getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
this.customizeResourceHandlerRegistration(registration);
}
}
addResourceHandlers一共调用了两次,一次用于webjar规则、一次用于静态资源访问规则:
-
这就解释了为什么之前学习到webjar时,webjar只需要 /webjars/** 就能访问,因为再注册访问规则之后,webjar的静态资源路径就被这个方法设置成了 classpath:/META-INF/resources/webjars/ 。
-
静态资源路径也调用了这个方法,同理。getStaticPathPattern和getStaticLocations方法最终获取到的值如下:
private String staticPathPattern = "/**"; private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS; private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{ "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" };
这就解释了为什么我们之前学习时会有4个默认的静态资源路径。
当我们修改静态资源路径时,则就会按照我们修改过的静态资源路径来。在 “/**”的请求时,就会来到设置的静态资源路径拿资源,且也有缓存策略(静态资源都有缓存策略)。
此外,addResourceHandler方法还有缓存控制的功能,用于将这些资源缓存一段时间。也就是我们访问过一次后,浏览器会为我们缓存,就不用每次都向服务器请求这个静态文件了。我们也可以在配置文件中设置缓存策略的相关信息:
spring:
web:
resources:
cache:
period: 10000
3. 欢迎页的处理规则
回到一开始最外层的WebMvcAutoConfiguration自动配置类,我们可以发现里面还有一个配置类EnableWebMvcConfiguration,它也给IoC容器中也放了好多组件。其中就有我们要分析的欢迎页。
xxxHandlerMapping是springmvc的核心组件,用于处理器映射,它里面保存了每一Handler能处理哪些请求。即拿到请求后,handlermapping负责查找这个请求谁处理、那个请求谁处理,找到之后就利用反射调用能处理这个请求的那个方法。
welcomePageHandlerMapping相当于是 “欢迎页谁能处理“ 的请求映射规则,以下开始分析它的源码。
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
return welcomePageHandlerMapping;
}
welcomePageHandlerMapping 有许多形参,这些参数spring都会自动从IoC容器中拿。
接着我们进入 new WelcomePageHandlerMapping()的构造函数底层,源码如下。
final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext,
Resource welcomePage,
String staticPathPattern) {
if (welcomePage != null && "/**".equals(staticPathPattern)) {
//要使用欢迎页功能,必须是/**
logger.info("Adding welcome page: " + welcomePage);
this.setRootViewName("forward:index.html");
} else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
//若静态资源路径不是/**,则提交一个名为index的视图交给Controller处理
logger.info("Adding welcome page template: index");
this.setRootViewName("index");
}
}
}
我们终于发现,spring底层写死了 “/“.equals(staticPathPattern) ,这也解释了为什么之前我们一修改静态资源路径,欢迎页就失效了,因为在spring底层源码中,这是完全写死了的,非 “/” 不可。
总结:由于spring底层写死的缘故,当没有修改静态资源路径时,欢迎页就为index.html。但是我们实际开发中都会修改静态资源路径以区分Controller的请求,在我们修改之后,欢迎页功能将会改为提交一个路径名为 index 的视图交给Controller处理,我们可以编写这个名为index的Controller实现一下业务和跳转。
4. favicon的处理规则
了解springmvc底层源码后,我们发现favicon跟代码没有任何关系,因为这是浏览器做的事情。浏览器会默认去找当前项目下的 /favicon.ico 并实现该功能。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/84475.html