Spring源码学习-MVC的WEB源码解析

目录

      • SpringMVC官方文档
      • SpringMVC的父子容器
        • 父子关系的定义
        • 自定义快速启动器
        • 启动过程
          • 容器创建的过程
          • 容器刷新启动
        • 父子容器示例图
      • 网络请求链路分析
        • DispatcherServlet请求链路
      • DispatcherServlet详解(MVC核心功能类)
        • DispatcherServlet九大组件
        • 九大组件的初始化
          • 默认策略
          • 初始化时机
        • HandlerMapping详解
          • RequestMappingHandlerMapping
        • HandlerAdapter详解
          • 整体的流程图
        • RequestMappingHandlerAdapter
          • 初始化流程
          • 执行流程(总体来讲)
          • 参数解析原理
          • 返回值处理器
          • 处理结果
      • 视图渲染解析
      • SpringMVC异常处理
        • ResponseStatus
        • 异常注解版处理
      • @EnableWebMvc原理
        • EnableWebMvc图示

SpringMVC官方文档

先放最标准的官方文档在这,作为这篇技术文章的定海神针SpringMVC官方文档传送门

SpringMVC的父子容器

SpringMVC有一个父子容器的关系.Spring的IOC容器是为父(根)容器,SpringMVC的web-IOC容器是为子容器,子容器找组件的时候先找自己,在找父容器(递归调用),最后进行合并

注意这里父子容器不是一个强制标准,完全可以不指定父容器,这时候MVC的容器扫描所有的Bean组件信息

父子关系的定义

通过servlet规范中的init初始化方法调用过来(tomcat进行触发),在FrameworkServletinitServletBean方法中的initWebApplicationContext方法定义了两者之间的父子关系,此时父容器已经经历过了初始化,完全准备就绪,示例代码如下:

protected WebApplicationContext initWebApplicationContext() {
		//先尝试获取一下之前的父容器
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
		if (this.webApplicationContext != null) {
			//当前的web-ioc容器
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						//父子容器的体现
						cwac.setParent(rootContext);
					}
					//配置并刷新容器
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}

		return wac;
	}

自定义快速启动器

AbstractAnnotationConfigDispatcherServletInitializer属于是能够快速整合注解版的SpringMVC和Spring的一个抽象类,能够指定两个IOC容器的配置类和servlet的路径映射(通过的是模板方法设计模式,给子类预留了方法去填充,还有很多其他可以重写的方法但只有这三个是必须重写去实现的)

/**
 * 最快速的整合注解版SpringMVC和Spring的
 */
public class QuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer {

	//根容器的配置(spring的配置类==spring的配置文件)
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[]{SpringConfig.class};
	}

	//web容器的配置(springMVC的配置类==springMVC的配置文件)
	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class[]{SpringMVCConfig.class};
	}

	//servlet的映射:DispatcherServlet的映射路径
	@Override
	protected String[] getServletMappings() {
		return new String[]{"/app/*"};
	}
}

启动过程

容器创建的过程

WebApplicationInitializer先引导我们按照事先指定的配置类把父子容器创建出来(还是SPI那一套去调用到onStartup方法)

  1. tomcat启动,扫描所有的WebApplicationInitializer
  2. 找到我们自定义实现的快速启动类,他继承了抽象类AbstractAnnotationConfigDispatcherServletInitializer
  3. 调用onStartup方法,他是继承自AbstractDispatcherServletInitializer
  4. 先调用父类的onStartup方法,其中逻辑如下
  • registerContextLoaderListener(servletContext)注册servlet规范的监听器
  • 根据返回的spring配置类创建了一个容器,类型是AnnotationConfigWebApplicationContext(此时还未初始化容器)
  • 把创建好的容器添加到刚才的监听器当中(监听器是tomcat自己加载完当前的web应用后会调用监听器的contextInitialized方法,此时会触发容器的刷新初始化)
  1. 在调用registerDispatcherServlet方法注册DispatcherServlet
  • 根据返回的SpringMVC的配置类在创建一个容器,类型为AnnotationConfigWebApplicationContext,此容器也未初始化
  • 直接new新建了一个DispatcherServlet,并且保存了上面刚新建的容器
容器刷新启动
  1. 在tomcat对web应用启动完成的时候,会触发监听器的钩子函数contextInitialized启动根容器(spring的ioc容器),在这个方法的底层会调用容器刷新的refresh方法
  2. 在tomcat启动以后,tomcat调用DispatcherServlet的初始化(init)方法,此时会初始化子容器(在此过程中行程了父子容器)

父子容器示例图

父子容器创建初始化完毕之后,整个应用就已经准备完毕了
Spring源码学习-MVC的WEB源码解析_第1张图片

网络请求链路分析

DispatcherServlet请求链路

  • javax的servlet处理
  1. Servlet接口定义了service方法
  2. GenericServlet抽象类实现了servlet接口,不过没有重写service方法
  3. HttpServlet继承了GenericServlet并重写了service方法,根据请求方法的类型,去分别调用不同的方法(类似于doGet,doPost等等)
  • spring家的servlet处理
  1. HttpServletBean抽象类继承自HttpServlet没有处理这些被拆分的方法
  2. FrameworkServlet抽象类继承自HttpServletBean重写了所有的拆分方法,这些方法都统一调用了processRequest(request, response)方法,他是SpringMVC统一处理请求的入口方法
  3. DispatcherServlet类继承了FrameworkServlet,上面的processRequest方法会调用到他的doService方法
  4. doService方法中会进行一些request域属性的准备,最后就会调用到大名鼎鼎的doDispatch(request, response)方法,相信大家或多或少应该都听过,这里就会进行请求的整个派发逻辑

DispatcherServlet详解(MVC核心功能类)

DispatcherServlet九大组件

DispatcherServlet的九大组件(官方文档只介绍了八个,只需要关注这八个就可以)全部都是接口,我们完全可以自定义其实现方式,不过spring默认都准备好了这些组件的实现,一般不需要我们去重写

  • MultipartResolver:文件上传解析器
  • LocaleResolver:国际化解析器(区域信息)
  • ThemeResolver:主题解析器
  • HandlerMapping:Handler(处理器,能处理请求的组件(controller))的映射 保存的是所有的请求都由谁来处理的映射关系
  • HandlerAdapter:Handler(处理器,能处理请求的组件(controller))的适配器 超级反射工具
  • HandlerExceptionResolver:Handler的异常解析器
  • RequestToViewNameTranslator:把请求转成视图名(要跳转的页面地址)的翻译器,这个官方文档没有介绍,重要程度不高
  • FlashMapManager:闪存管理器
  • ViewResolver:视图解析器(去哪些页面,怎么过去)

九大组件的初始化

九大组件通过下面的方法进行初始化操作,其实都是先根据名称和类型先直接从IOC容器中获取,没有的话就根据默认策略去创建,这样就留给我们一个口子可以自己实现这些接口的具体实现放到容器中,从而使用自己的逻辑(一般不会这么做)

protected void initStrategies(ApplicationContext context) {
		//容器中有就拿来用,没有就是null
		initMultipartResolver(context);
		//下面的都是从容器中拿,拿不到根据策略自行创建
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}
默认策略

默认的策略指定了去DispatcherServlet的类路径下面找到DispatcherServlet.properties文件,他里面定义了默认的实现类.
九大组件除了文件上传组件,都会有默认值,文件上传功能,我们需要自己导入相关包并进行配置

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
初始化时机

容器刷新12大步的最后一步发送上下文环境刷新完成的事件,触发九大组件的初始化方法publishEvent(new ContextRefreshedEvent(this));,最后调用到DispatcherServlet的如下方法

protected void onRefresh(ApplicationContext context) {
		//初始化九大组件
		initStrategies(context);
	}

是利用了Spring的事件机制去触发初始化流程的

HandlerMapping详解

它是能够保证,每一个需要处理的网络请求url映射能够注册进来,至于后面怎么处理它不管,它的默认策略一共初始化了三个具体实现类,分别为:

  1. BeanNameUrlHandlerMapping:bean的名字作为url路径,进行映射,基本没有这种用法(扫描所有名字以/开始的组件,注册url到映射中)
  2. RequestMappingHandlerMapping:我们熟知的@RequestMapping注解作为url路径进行映射,也是我们经常用到的(扫描所有的@Controller有方法标注了@RequestMapping注解的,注册到url映射中)
  3. RouterFunctionMapping:支持函数式处理以及webflux相关功能
RequestMappingHandlerMapping

初始化的流程(书接上文):

  1. DispatcherServlet创建对象后,Tomcat会调用回调的钩子触发initServletBean函数
  2. 最终容器启动完成,Spring发送事件,回调到DispatcherServlet的onRefresh函数
  3. onRefresh函数会初始化九大组件
  4. RequestMappingHandlerMapping开始初始化
  • 创建所有配置中指定的handlerMapping对象
  • 调用了IOC容器的creatBean方法去开始创建RequestMappingHandlerMapping对象
  • RequestMappingHandlerMapping创建完成赋值等等操作后,就开始初始化流程
  • RequestMappingHandlerMapping实现了InitializingBean
  • 调用了RequestMappingHandlerMapping的初始化方法afterPropertiesSet
  • 拿到子(web)容器中的所有组件,挨个遍历处理,判断是否有Controller或者RequestMapping注解
  • 把分析完成后的RequestMapping信息放入到registry当中

我们一直在使用的就是它,它保存了所有的映射处理信息,在MappingRegistry这个内部类中.

//放到了这个registry之中
		private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

他的初始化利用了InitializingBean的生命周期.这里注意一个点位,在Spring中,如果是所有组件可能都会用的功能(比如自动装配,AOP等)都是用后置处理器BeanPostProcessor来做到的.如果是单组件的增强,跟其他人没什么关系,最好是利用生命周期InitializingBean来做

HandlerAdapter详解

属于是终极的适配器,通过它去适配执行目标方法并处理返回值,他一共有四个默认的适配器

  1. HttpRequestHandlerAdapter判断是否是HttpRequestHandler接口的
  2. SimpleControllerHandlerAdapter判断是否是Controller接口的
  3. RequestMappingHandlerAdapter判断是否是一个方法,直接返回True
  4. HandlerFunctionAdapter流式编程,webflux的处理适配器

根据supports方法判断是否能够应用这个适配器,如果可以的话就去执行Adapter的handle方法

整体的流程图

Spring源码学习-MVC的WEB源码解析_第2张图片

RequestMappingHandlerAdapter

它是我们最常用的适配器,通过反射去调用我们定义的controller中的方法

初始化流程

同上面的RequestMappingHandlerMapping初始化流程是一样的,在执行Bean的生命周期接口InitializingBean,此时会填充参数解析器和返回值处理器进来(放到组合对象中保存起来),而且会初始化了ControllerAdvice相关功能

执行流程(总体来讲)
  1. 调用adapter接口定义的handle方法,开始执行目标方法
  2. 判断是否采用会话锁(默认值是false不采用),它直接锁住了session会话,限制一个会话一次只处理一个请求线程
  3. 此时调用invokeHandlerMethod执行目标方法
  4. 做一些基本数据的准备
  5. 封装handlerMethod变为ServletInvocableHandlerMethod提供handlerMethod里面信息的快速获取,并且准备argumentResolvers参数解析器(未来用来反射解析目标方法中每一个参数的值)和returnValueHandlers返回值处理器(未来用来处理目标方法执行后的返回值,无论目标方法返回什么,想办法变成适配器能用的ModelAndView)放到封装好的HandlerMethod中
  6. 创建ModelAndViewContainer模型和视图的容器,把处理过程中产生的模型与视图相关的数据存放在这里,他是为了能够在整个请求处理线程期间共享数据ModelAndView
  7. 调用invocableMethod.invokeAndHandle(webRequest, mavContainer)执行目标方法,执行期间的数据存到mavContainer中
  8. 调用getModelAndView(mavContainer)方法提取出ModelAndView数据准备返回,后面就可以直接解析处理ModelAndView去view指定的页面展示出model中的数据
参数解析原理
  1. 通过getMethodArgumentValues获取到所有参数的值
  2. for循环MethodParameter参数列表来解析确定每个参数的值
  3. 通过解析器的supportsParameter方法循环遍历所有的解析器,找到能够处理该参数的参数解析器
  • 注意这里边有一个参数解析器的缓存,符合条件的话会放入缓存中,下次这个接口的请求过来就不需要再去挨个遍历解析器判断了
  • 基本上每一种参数解析器对应一个spring家的参数注解,基本上是通过注解还有一些固定类去判断的(一共有27个参数解析器),可以参考参数解析
  1. 调用resolveArgument方法进行真正的参数解析,此时就根据不同的参数解析器策略,执行不同的逻辑,这里不挨个一一介绍了,有兴趣的可以自己去看看
返回值处理器

SpringMVC的controller方法能够返回的返回值官网文档传送门

  1. 调用handleReturnValue进行返回值处理
  2. 同之前逻辑一样,调用selectHandler找到合适的返回值处理器(注意这里没有缓存),一共15个处理器挨个遍历,调用返回值处理器接口的supportsReturnType方法(基本上通过函数的返回类型去做的判断)
  3. 然后调用控制器的handleReturnValue方法处理解析返回值

其中像咱们最常用的@ResponseBody是用MessageConvert把对象转换成为了流直接write出去的,里面的过程比较复杂,不详细展开了,可以自己看下RequestResponseBodyMethodProcessor这个类

处理结果

调用processDispatchResult方法,上面流程如果捕捉到异常先不抛出,捕获到传递给processDispatchResult方法,此方法先判断异常是否为空,如果有异常先处理异常,用所有的异常解析器尝试去解析,处理完之后再度抛出异常,外层捕获后在调用triggerAfterCompletion方法去判断执行链中是否有异常拦截器,如果有的话执行拦截器,最后在将异常抛出去.如果没有异常接下来会用视图解析器去解析得到视图

视图渲染解析

  1. 如果是@ResponseBody标注的方法,实际上在之前的步骤就已经直接把返回对象转为流写出去了,视图解析对他没意义
  2. 否则调用render(mv, request, response)方法开启视图的解析
  3. 先确定国际化的信息,AcceptHeaderLocaleResolver会根据请求头中的Accept-Language字段决定浏览器能接受哪种中文/英文页面
  4. 获取视图的名称,再根据名称通过resolveViewName(viewName, mv.getModelInternal(), locale, request)方法获取到真正的视图View对象
  5. 跟之前的逻辑类似,动态策略,用多个视图解析器去挨个遍历解析,通过模板方法控制逻辑
  6. 先看缓存中有没有,没有的话真正去创建视图
  • 进入到UrlBasedViewResolver解析器中,判断视图名称是否以**redirect:**开始,是的话就准备重定向视图RedirectView对象
  • 再判断视图名称是否以**forward:**开始,是的话就准备转发视图InternalResourceView对象
  • 否则的话就直接调用父类的模板方法,去创建视图对象(创建出来的话会把视图放入到缓存中的,下次直接就从缓存去获取)
  1. 创建完视图之后,再去调用AbstractView的render方法,再此继续通过模板方法还有策略模式去调用到具体视图类型的render方法

SpringMVC异常处理

  1. 执行目标方法,抛出的所有异常暂存起来,继续调用最后的结果处理processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)方法
  2. 判断异常不为空,用所有的异常解析器去解析该异常,调用processHandlerException(request, response, handler, exception)方法
  3. 调用到模板方法的实现最终去解析异常doResolveException
  • AbstractHandlerMethodExceptionResolver:所有@ExceptionHandler注解方式的异常处理交给他来做
  • ResponseStatusExceptionResolver找异常类上有没有@ResponseStatus注解
  • DefaultHandlerExceptionResolver:看异常是否属于我指定去处理的那些异常,如果是的话直接返回错误页(错误代码和消息)
  1. 返回一个空的ModelAndView,不会再进行其他的处理,执行后续的拦截器
  2. 如果所有的异常解析器都发现处理不了异常,就再次抛出异常(下面的渲染方法就不再去执行)
  3. 上面捕获到异常,再次执行拦截器,然后再次抛出异常,直接就抛出给Tomcat等容器了(没人处理的异常就一直往外抛出,一直到最顶层)

ResponseStatus

用这个注解可以返回自己定义的状态码还有错误信息.示例代码如下

@ResponseStatus(value = HttpStatus.CONFLICT,reason = "非法用户")
public class InvalidUserException extends RuntimeException{

	private static final long serialVersionUID = -778778787878L;

}

异常注解版处理

  1. ExceptionHandlerExceptionResolver借助InitializingBean生命周期的初始化方法afterPropertiesSet将所有的@ControllerAdvice注解的Bean扫描到缓存中,并且解析Bean中所有标注了@ExceptionHandler注解的方法,同样放入到了Map缓存中(为后续的处理做好了准备)
  2. 后面进入到异常解析流程时,直接从缓存中获取到相应的异常处理方法(上一步同样初始化处理参数和返回值解析器),通过参数解析器拿到所有的入参再加上异常,执行反射调用到自定义的异常处理方法

@EnableWebMvc原理

  1. 通过Import注解给容器中导入DelegatingWebMvcConfiguration组件
  2. 拿到容器中所有的WebMvcConfigurer(通过这里可以对组件进行很多扩展,组件的核心方法都给他留了模板方法)
  3. 最重要的一点是它继承自WebMvcConfigurationSupport类,这个类通过@Bean的方式把RequestMappingHandlerMapping等等的DispatcherServlet所需要用到的九大组件全部注册到容器中

EnableWebMvc会给容器中导入了九大组件(我们还可以用WebMvcConfigurer去定制,给留下了模板方法入口),DispatcherServlet初始化组件直接就可以从容器中获取到了,而不是用配置文件(配置文件里面写死了一些默认的类路径)默认

EnableWebMvc图示

Spring源码学习-MVC的WEB源码解析_第3张图片

你可能感兴趣的:(Spring源码解读,spring,学习,mvc)