我们前面两篇做了基本的开发,相信大家对Spring MVC的流程有了基本的了解,这些我们来确认一下一些细节。
1、Spring MVC是如何初始化的
在Servlet 3.0规范中,web.xml再也不是一个必需的配置文件。为了适应这个规范,Spring MVC从3.1版本开始也进行了支持,也就是我们已经不再需要通过任何的XML去配置Spring MVC的运行了。为了支持对于Spring MVC的配置,Spring提供了接口WebMvcConfigurer,其大部分方法都是default类型的空实现,这样开发者只需要实现这个接口,重写需要自定义的方法即可,这样就很方便进行开发了。在Spring Boot中,自定义是通过配置类WebMvcAutoConfiguration定义的,它有一个静态的内部类WebMvcAutoConfigurationAdapter,通过它Spring Boot就可以自定义配置Spring MVC的初始化,它们之间的关系如下图所示。
这里我们可以看到 WebMvcAutoConfigurationAdapter的源码,如下。
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
private final Resources resourceProperties;
private final WebMvcProperties mvcProperties;
private final ListableBeanFactory beanFactory;
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;
private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
private ServletContext servletContext;
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = webProperties.getResources();
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider
.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
if (this.beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) {
Object taskExecutor = this.beanFactory
.getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
if (taskExecutor instanceof AsyncTaskExecutor asyncTaskExecutor) {
configurer.setTaskExecutor(asyncTaskExecutor);
}
}
Duration timeout = this.mvcProperties.getAsync().getRequestTimeout();
if (timeout != null) {
configurer.setDefaultTimeout(timeout.toMillis());
}
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
if (this.mvcProperties.getPathmatch()
.getMatchingStrategy() == WebMvcProperties.MatchingStrategy.ANT_PATH_MATCHER) {
configurer.setPathMatcher(new AntPathMatcher());
this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
String servletUrlMapping = dispatcherPath.getServletUrlMapping();
if (servletUrlMapping.equals("/") && singleDispatcherServlet()) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setAlwaysUseFullPath(true);
configurer.setUrlPathHelper(urlPathHelper);
}
});
}
}
private boolean singleDispatcherServlet() {
return this.servletRegistrations.stream()
.map(ServletRegistrationBean::getServlet)
.filter(DispatcherServlet.class::isInstance)
.count() == 1;
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties.getContentnegotiation();
configurer.favorParameter(contentnegotiation.isFavorParameter());
if (contentnegotiation.getParameterName() != null) {
configurer.parameterName(contentnegotiation.getParameterName());
}
Map<String, MediaType> mediaTypes = this.mvcProperties.getContentnegotiation().getMediaTypes();
mediaTypes.forEach(configurer::mediaType);
}
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
return resolver;
}
@Bean
@ConditionalOnBean(View.class)
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
@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;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat());
return resolver;
}
return null;
}
@Override
public void addFormatters(FormatterRegistry registry) {
ApplicationConversionService.addBeans(registry, this.beanFactory);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(),
"classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern,
Consumer<ResourceHandlerRegistration> customizer) {
if (registry.hasMappingForPattern(pattern)) {
return;
}
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
customizer.accept(registration);
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
customizeResourceHandlerRegistration(registration);
}
private Integer getSeconds(Duration cachePeriod) {
return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
}
private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
if (this.resourceHandlerRegistrationCustomizer != null) {
this.resourceHandlerRegistrationCustomizer.customize(registration);
}
}
@Bean
@ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class })
@ConditionalOnMissingFilterBean(RequestContextFilter.class)
public static RequestContextFilter requestContextFilter() {
return new OrderedRequestContextFilter();
}
}
你可以看到很多组件的初始化就在这里。
如果我们以后需要定制也可以继承WebMvcConfigurer对相关组件进行初始化,关于这点,我们未来会看到。
2、在Spring MVC中,我们能配置些什么??
看回上节的代码,里面存在两个配置文件WebMvcProperties和WebProperties,WebMvcProperties主要配置MVC的内容,而WebProperties主要配置网页资源的内容。
下面是Spring MVC常见的配置项(可以参考文件WebMvcProperties),如下:
# SPRING MVC (WebMvcProperties)
# 异步请求超时时间(单位为毫秒)
spring.mvc.async.request-timeout=
# 是否使用请求参数(默认参数为"format")来确定请求的媒体类型
spring.mvc.contentnegotiation.favor-parameter=false
# 是否使用URL中的路径扩展来确定请求的媒体类型
spring.mvc.contentnegotiation.favor-path-extension=false
# 设置内容协商向媒体类型映射文件扩展名。例如,YML文本/YAML
spring.mvc.contentnegotiation.media-types.*=
# 当启用favor-parameter参数是,自定义参数名
spring.mvc.contentnegotiation.parameter-name=
# 日期格式配置,如yyyy-MM-dd
spring.mvc.date-format=
# 是否让FrameworkServlet doService()方法支持TRACE请求
spring.mvc.dispatch-trace-request=false
# 是否启用 FrameworkServlet doService 方法支持OPTIONS请求
spring.mvc.dispatch-options-request=true
# spring MVC的图标是否启用
spring.mvc.favicon.enabled=true
# Servlet规范要求表格数据可用于HTTP POST而不是HTTP PUT或PATCH请求,这个选项将使得过滤器拦截
# HTTP PUT和PATCH,且内容类型是application/x-www-form-urlencoded的请求,
# 并且将其转换为POST请求
spring.mvc.formcontent.putfilter.enabled=true
# 如果配置为default,那么它将忽略模型重定向的场景
spring.mvc.ignore-default-model-on-redirect=true
# 默认国际化选项,默认取Accept-Language
spring.mvc.locale=
# 国际化解析器,如果需要固定可以使用fixed
spring.mvc.locale-resolver=accept-header
# 是否启用警告日志异常解决
spring.mvc.log-resolved-exception=false
# 消息代码的格式化策略。例如,' prefix_error_code '
spring.mvc.message-codes-resolver-format=
# 是否对spring.mvc.contentnegotiation.media-types.*注册的扩展采用后缀模式匹配
spring.mvc.pathmatch.use-registered-suffix-pattern=false
# 当匹配模式到请求时,是否使用后缀模式匹配(.*)
spring.mvc.pathmatch.use-suffix-pattern=false
# 启用Spring Web服务Serlvet的优先顺序配置
spring.mvc.servlet.load-on-startup=-1
# 指定静态资源路径
spring.mvc.static-path-pattern=/**
# 如果请求找不到处理器,是否抛出 NoHandlerFoundException异常
spring.mvc.throw-exception-if-no-handler-found=false
# Spring MVC视图前缀
spring.mvc.view.prefix=
# Spring MVC视图后缀
spring.mvc.view.suffix=
# Thymeleaf模板常用配置项
# 是否启用Thymeleaf模板机制
spring.thymeleaf.enabled=true
# Thymeleaf模板前缀
spring.thymeleaf.prefix=classpath:/templates/
# Thymeleaf模板后缀名
spring.thymeleaf.suffix=.html
3、DispatcherServlet的流程源码
好了,这里配置也谈到了,下面我们来探访DispatcherServlet的源码,主要是流程的代码,也就是doDispatch()方法,我们不妨尝试看看它。
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 将HttpServletRequest转换为MultipartHttpServletRequest,一种处理文件上传的请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 找到对应的处理器,也就是HandlerMapping机制
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) { // 找不到处理器的处理
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 找到对应的HandlerAdapter准备执行(未执行)处理器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// 请求头处理
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 拦截器前置方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 使用HandlerAdapter执行处理器
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 视图解析器,定位视图
applyDefaultViewName(processedRequest, mv);
// 拦截器后置方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new ServletException("Handler dispatch failed: " + err, err);
}
// 处理结果,主要是视图渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 执行拦截器完成方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 执行拦截器完成方法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new ServletException("Handler processing failed: " + err, err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
// 执行拦截器完成方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
这个方法已经标注了@SuppressWarnings("deprecation"),说明将来会被取代掉。
我在这个方法中只是添加了中文注释,目的是让你更好的查看源码,请依据我的注释看回Spring MVC流程图,相信你会有更深的认识。
到这里,基础的Spring MVC,我就讲述完成了,下面我们需要在深入细节去讨论。