SpringMVC源码深度解析(下)

接着上一遍博客《SpringMVC源码深度解析(中)》继续聊。上一篇博客中,返回的是对象的情况下SpringMVC框架会怎么处理,这种情况也是现在用得最多的,因为都是前后端分离。如果返回的是ModelAndView,则是另外的处理逻辑了,主要看ModelAndViewMethodReturnValueHandler对象,代码如下:

到这里位置,视图的解析渲染就讲完了,回到DispatcherServlet#processDispatchResult()方法,代码如下:

该注解可以添加到类/方法上,最终会注解中设置的异常原因和状态码设置到响应中。回到DispatcherServlet#processDispatchResult()方法,再看看这里:

再回到DispatcherServlet#processDispatchResult()方法被调用的地方。 由上面的代码可知:如果有异常,但是没有配置异常处理解析器或者异常解析处理器返回的视图为空的话,异常会继续往外抛,由于最外层还有一个try/catch,最终会调用DispatcherServlet#triggerAfterCompletion()方法,代码如下:

到这里为止,DispatcherServlet的流程大概讲完了。当然地方也没有讲到,有兴趣的朋友可以自行研究,起码我觉得了解到这个程度,在日常开发中应该是完全够用了。接下来我会讲一下流程中没有讲到的东西,比较零散,讲到哪里是哪里,望理解。

第一个想到的就是 @EnableWebMvc注解,它的作用是什么呢?先看看这个注解:

看到了@Import注解,马上就能想到,这是Spring框架的扩展点之一,该注解中的类会被放入Spring容器中。看看该类,代码如下:

java 复制代码
package org.springframework.web.servlet.config.annotation;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;

/**
 * A subclass of {@code WebMvcConfigurationSupport} that detects and delegates
 * to all beans of type {@link WebMvcConfigurer} allowing them to customize the
 * configuration provided by {@code WebMvcConfigurationSupport}. This is the
 * class actually imported by {@link EnableWebMvc @EnableWebMvc}.
 *
 * @author Rossen Stoyanchev
 * @since 3.1
 */
@Configuration(proxyBeanMethods = false)
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);
		}
	}


	@Override
	protected void configurePathMatch(PathMatchConfigurer configurer) {
		this.configurers.configurePathMatch(configurer);
	}

	@Override
	protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		this.configurers.configureContentNegotiation(configurer);
	}

	@Override
	protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
		this.configurers.configureAsyncSupport(configurer);
	}

	@Override
	protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
		this.configurers.configureDefaultServletHandling(configurer);
	}

	@Override
	protected void addFormatters(FormatterRegistry registry) {
		this.configurers.addFormatters(registry);
	}

	@Override
	protected void addInterceptors(InterceptorRegistry registry) {
		this.configurers.addInterceptors(registry);
	}

	@Override
	protected void addResourceHandlers(ResourceHandlerRegistry registry) {
		this.configurers.addResourceHandlers(registry);
	}

	@Override
	protected void addCorsMappings(CorsRegistry registry) {
		this.configurers.addCorsMappings(registry);
	}

	@Override
	protected void addViewControllers(ViewControllerRegistry registry) {
		this.configurers.addViewControllers(registry);
	}

	@Override
	protected void configureViewResolvers(ViewResolverRegistry registry) {
		this.configurers.configureViewResolvers(registry);
	}

	@Override
	protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		this.configurers.addArgumentResolvers(argumentResolvers);
	}

	@Override
	protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
		this.configurers.addReturnValueHandlers(returnValueHandlers);
	}

	@Override
	protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		this.configurers.configureMessageConverters(converters);
	}

	@Override
	protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
		this.configurers.extendMessageConverters(converters);
	}

	@Override
	protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);
	}

	@Override
	protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		this.configurers.extendHandlerExceptionResolvers(exceptionResolvers);
	}

	@Override
	@Nullable
	protected Validator getValidator() {
		return this.configurers.getValidator();
	}

	@Override
	@Nullable
	protected MessageCodesResolver getMessageCodesResolver() {
		return this.configurers.getMessageCodesResolver();
	}

}

有很多实现方法,逻辑类似,如DelegatingWebMvcConfiguration#addArgumentResolvers()方法为例:

看到上面的这段代码就可以知道:从Spring容器中获取到的WebMvcConfigurer对象的集合,并调用DelegatingWebMvcConfiguration#addArgumentResolvers(),即遍历WebMvcConfigurer对象的集合,将设置的HandlerMethodArgumentResolver的集合设置给WebMvcConfigurer对象。

当然,除了设置HandlerMethodArgumentResolver外,还可以设置HttpMessageConverter、HandlerMethodReturnValueHandler、InterceptorRegistry、CorsRegistry、HandlerExceptionResolver 等等。由于DelegatingWebMvcConfiguration类还是被@Configuration注解所修饰,因此它还是个配置类,配置相关都在它的父类中,即WebMvcConfigurationSupport,看看这个类,截取部分代码如下:

这是添加拦截器的逻辑,因此可以知道,除了添加@EnableWebMvc注解外,还需要定义一个WebMvcConfigurer接口的实现了,并将其注册到Spring容器中。以添加拦截器为例:实现了WebMvcConfigurer接口,需要实现WebMvcConfigurer#addInterceptors()方法,入参为InterceptorRegistry,将自定义的拦截器添加到传入的 InterceptorRegistry对象中。在DelegatingWebMvcConfiguration类中,会获取到Spring容器中WebMvcConfigurer对象的集合。在WebMvcConfigurationSupport中,比如创建HandlerMapping对象的时候,就会调用到WebMvcConfigurationSupport#getInterceptors()方法,进而调用到DelegatingWebMvcConfiguration#addInterceptors()方法,传入InterceptorRegistry对象,实际上就是进行拦截器的设置,最终会将自定义的拦截器设置到InterceptorRegistry对象中。

如果是设置其他的对象,如HandlerMethodArgumentResolver、HttpMessageConverter、CorsRegistry、HandlerMethodReturnValueHandler,是同样的原理。对于HttpMessageConverter而言,有点不同,看看代码:

到这里为止,@EnableWebMvc注解的原理讲完了。再看看@ControllerAdvice注解,这是示例,代码如下:

一般@ControllerAdvice和@ExceptionHandler注解是配合使用的,想知道原理,线看看注解,代码如下:

对于这个注解是怎么生效的,还是需要先回到WebMvcConfigurationSupport中,由于是跟异常相关,因此肯有可能看HandlerExceptionResolver类,看看WebMvcConfigurationSupport中哪里有配置HandlerExceptionResolver类,搜的时候也确实搜到了,代码如下:

看看 ExceptionHandlerExceptionResolver类,同样只截取核心的代码:

发现ExceptionHandlerExceptionResolver实现了InitializingBean,当然需要看看ExceptionHandlerExceptionResolver#afterPropertiesSet()方法,代码如下:

通过ControllerAdviceBean#findAnnotatedBeans()方法,可以获取ControllerAdviceBean对象的集合(被@ControllerAdvice修饰的Class被封装成该类),遍历,获取到beanType,也就是被@ControllerAdvice修饰的Class,创建ExceptionHandlerMethodResolver对象,调用它的有参构造,传入beanType,看看ExceptionHandlerMethodResolver类,代码如下:

到这里为止,ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache属性,已经不为空了,缓存的是ExceptionHandlerMethodResolver对象,而ExceptionHandlerMethodResolver对象的mappedMethods属性,缓存的才是我们想要的,key为异常类型,value为被@ExceptionHandler注解修饰的方法。

再想想会在哪里使用呢?由于是报错,前面我讲到的DispatcherServlet流程中,一定会进到某个方法,先看看那块的代码:

可知,前面通过try/catch捕获到异常,并调用DispatcherServlet#processDispatchResult()方法进行处理的时候,传入异常对象dispatchException,再点进这个方法看看:

看看ExceptionHandlerExceptionResolver#getExceptionHandlerMethod()方法,代码如下:

再看看ServletInvocableHandlerMethod#invokeAndHandle()方法,代码如下:

到这里为止,@ControllerAdvice和@ExceptionHandler注解的原理讲完了。

SpringMVC框架,到这里也算是讲完了,可能有一些遗漏。如果对SpringMVC框架中,有些我没讲到的地方还有疑问,欢迎留言,谢谢!

相关推荐
程序员与背包客_CoderZ几秒前
C++设计模式——Command命令模式
c语言·开发语言·c++·设计模式·命令模式
月临水2 分钟前
JavaEE:多线程进阶(CAS)
java·开发语言·java-ee
caperxi8 分钟前
vue 踩坑记录
前端·javascript·vue.js
旺旺碎冰冰、11 分钟前
Java之类和对象
java·开发语言
ForRunner12313 分钟前
如何使用 Ruby 中的 Selenium 解决 CAPTCHA
开发语言·selenium·ruby
2301_7969821415 分钟前
location.protocol+‘//‘+location.hostname实现什么功能?
java·前端·javascript·python
工程师老罗16 分钟前
Java笔试面试题AI答之JDBC(1)
java·开发语言
陈在天box18 分钟前
《Python 面试热门问题五》
开发语言·python
伟大的图酷酷24 分钟前
openharmony native c++文件读写
开发语言·c++·harmonyos
188_djh25 分钟前
# 利刃出鞘_Tomcat 核心原理解析(十一)-- Tomcat 附加功能 WebSocket -- 2
java·websocket·tomcat·tomcat源码分析·tomcat核心原理·websocket案例·tomcat附件功能