先放最标准的官方文档在这,作为这篇技术文章的定海神针SpringMVC官方文档传送门
SpringMVC有一个父子容器的关系.Spring的IOC容器是为父(根)容器,SpringMVC的web-IOC容器是为子容器,子容器找组件的时候先找自己,在找父容器(递归调用),最后进行合并
注意这里父子容器不是一个强制标准,完全可以不指定父容器,这时候MVC的容器扫描所有的Bean组件信息
通过servlet规范中的init初始化方法调用过来(tomcat进行触发),在FrameworkServlet
的initServletBean
方法中的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方法)
AbstractAnnotationConfigDispatcherServletInitializer
AbstractDispatcherServletInitializer
registerContextLoaderListener(servletContext)
注册servlet规范的监听器AnnotationConfigWebApplicationContext
(此时还未初始化容器)contextInitialized
方法,此时会触发容器的刷新初始化)registerDispatcherServlet
方法注册DispatcherServletAnnotationConfigWebApplicationContext
,此容器也未初始化contextInitialized
启动根容器(spring的ioc容器),在这个方法的底层会调用容器刷新的refresh方法Servlet
接口定义了service
方法GenericServlet
抽象类实现了servlet接口,不过没有重写service方法HttpServlet
继承了GenericServlet并重写了service方法,根据请求方法的类型,去分别调用不同的方法(类似于doGet,doPost等等)HttpServletBean
抽象类继承自HttpServlet没有处理这些被拆分的方法FrameworkServlet
抽象类继承自HttpServletBean重写了所有的拆分方法,这些方法都统一调用了processRequest(request, response)
方法,他是SpringMVC统一处理请求的入口方法DispatcherServlet
类继承了FrameworkServlet,上面的processRequest方法会调用到他的doService
方法doDispatch(request, response)
方法,相信大家或多或少应该都听过,这里就会进行请求的整个派发逻辑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的事件机制去触发初始化流程的
它是能够保证,每一个需要处理的网络请求url映射能够注册进来,至于后面怎么处理它不管,它的默认策略一共初始化了三个具体实现类,分别为:
BeanNameUrlHandlerMapping
:bean的名字作为url路径,进行映射,基本没有这种用法(扫描所有名字以/开始的组件,注册url到映射中)RequestMappingHandlerMapping
:我们熟知的@RequestMapping
注解作为url路径进行映射,也是我们经常用到的(扫描所有的@Controller有方法标注了@RequestMapping注解的,注册到url映射中)RouterFunctionMapping
:支持函数式处理以及webflux相关功能初始化的流程(书接上文):
InitializingBean
afterPropertiesSet
我们一直在使用的就是它,它保存了所有的映射处理信息,在MappingRegistry
这个内部类中.
//放到了这个registry之中
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
他的初始化利用了
InitializingBean
的生命周期.这里注意一个点位,在Spring中,如果是所有组件可能都会用的功能(比如自动装配,AOP等)都是用后置处理器BeanPostProcessor
来做到的.如果是单组件的增强,跟其他人没什么关系,最好是利用生命周期InitializingBean
来做
属于是终极的适配器,通过它去适配执行目标方法并处理返回值,他一共有四个默认的适配器
HttpRequestHandlerAdapter
判断是否是HttpRequestHandler接口的SimpleControllerHandlerAdapter
判断是否是Controller接口的RequestMappingHandlerAdapter
判断是否是一个方法,直接返回TrueHandlerFunctionAdapter
流式编程,webflux的处理适配器根据supports方法判断是否能够应用这个适配器,如果可以的话就去执行Adapter的handle方法
它是我们最常用的适配器,通过反射去调用我们定义的controller中的方法
同上面的RequestMappingHandlerMapping
初始化流程是一样的,在执行Bean的生命周期接口InitializingBean,此时会填充参数解析器和返回值处理器进来(放到组合对象中保存起来),而且会初始化了ControllerAdvice相关功能
invokeHandlerMethod
执行目标方法ServletInvocableHandlerMethod
提供handlerMethod里面信息的快速获取,并且准备argumentResolvers参数解析器(未来用来反射解析目标方法中每一个参数的值)和returnValueHandlers返回值处理器(未来用来处理目标方法执行后的返回值,无论目标方法返回什么,想办法变成适配器能用的ModelAndView)放到封装好的HandlerMethod中invocableMethod.invokeAndHandle(webRequest, mavContainer)
执行目标方法,执行期间的数据存到mavContainer中getModelAndView(mavContainer)
方法提取出ModelAndView数据准备返回,后面就可以直接解析处理ModelAndView去view指定的页面展示出model中的数据getMethodArgumentValues
获取到所有参数的值MethodParameter
参数列表来解析确定每个参数的值supportsParameter
方法循环遍历所有的解析器,找到能够处理该参数的参数解析器resolveArgument
方法进行真正的参数解析,此时就根据不同的参数解析器策略,执行不同的逻辑,这里不挨个一一介绍了,有兴趣的可以自己去看看SpringMVC的controller方法能够返回的返回值官网文档传送门
handleReturnValue
进行返回值处理selectHandler
找到合适的返回值处理器(注意这里没有缓存),一共15个处理器挨个遍历,调用返回值处理器接口的supportsReturnType
方法(基本上通过函数的返回类型去做的判断)handleReturnValue
方法处理解析返回值其中像咱们最常用的
@ResponseBody
是用MessageConvert把对象转换成为了流直接write出去的,里面的过程比较复杂,不详细展开了,可以自己看下RequestResponseBodyMethodProcessor
这个类
调用processDispatchResult
方法,上面流程如果捕捉到异常先不抛出,捕获到传递给processDispatchResult方法,此方法先判断异常是否为空,如果有异常先处理异常,用所有的异常解析器尝试去解析,处理完之后再度抛出异常,外层捕获后在调用triggerAfterCompletion
方法去判断执行链中是否有异常拦截器,如果有的话执行拦截器,最后在将异常抛出去.如果没有异常接下来会用视图解析器去解析得到视图
@ResponseBody
标注的方法,实际上在之前的步骤就已经直接把返回对象转为流写出去了,视图解析对他没意义render(mv, request, response)
方法开启视图的解析AcceptHeaderLocaleResolver
会根据请求头中的Accept-Language字段决定浏览器能接受哪种中文/英文页面resolveViewName(viewName, mv.getModelInternal(), locale, request)
方法获取到真正的视图View
对象UrlBasedViewResolver
解析器中,判断视图名称是否以**redirect:**开始,是的话就准备重定向视图RedirectView
对象InternalResourceView
对象AbstractView
的render方法,再此继续通过模板方法还有策略模式去调用到具体视图类型的render方法processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)
方法processHandlerException(request, response, handler, exception)
方法doResolveException
AbstractHandlerMethodExceptionResolver
:所有@ExceptionHandler
注解方式的异常处理交给他来做ResponseStatusExceptionResolver
找异常类上有没有@ResponseStatus
注解DefaultHandlerExceptionResolver
:看异常是否属于我指定去处理的那些异常,如果是的话直接返回错误页(错误代码和消息)用这个注解可以返回自己定义的状态码还有错误信息.示例代码如下
@ResponseStatus(value = HttpStatus.CONFLICT,reason = "非法用户")
public class InvalidUserException extends RuntimeException{
private static final long serialVersionUID = -778778787878L;
}
ExceptionHandlerExceptionResolver
借助InitializingBean
生命周期的初始化方法afterPropertiesSet
将所有的@ControllerAdvice
注解的Bean扫描到缓存中,并且解析Bean中所有标注了@ExceptionHandler
注解的方法,同样放入到了Map缓存中(为后续的处理做好了准备)DelegatingWebMvcConfiguration
组件WebMvcConfigurer
(通过这里可以对组件进行很多扩展,组件的核心方法都给他留了模板方法)WebMvcConfigurationSupport
类,这个类通过@Bean的方式把RequestMappingHandlerMapping
等等的DispatcherServlet所需要用到的九大组件全部注册到容器中EnableWebMvc会给容器中导入了九大组件(我们还可以用
WebMvcConfigurer
去定制,给留下了模板方法入口),DispatcherServlet初始化组件直接就可以从容器中获取到了,而不是用配置文件(配置文件里面写死了一些默认的类路径)默认