一、介绍
这周在针对旧有系统改造的同时遇到一个问题,队友把通用泛型通过ControllerAdvice和ResponseBodyAdvice的方式封装到common层。这样做本身是没有问题的,但系统由于新的考虑需要对接一些原生的平台(泛型并不适用),强行加入泛型反而弄巧成拙。搞了好久后发现同事对底层框架并不熟悉,因此觉得有必要梳理一下。
二、分析
1、将通用泛型封入顶层是思路并没有错,但不应该强制写死,应该采用约定优于配置的方式处理,任何时候考虑系统不可能只有一个场景(正与反),除非是强制对外提供的官方渠道。
2、框架顺序的问题。对于框架的逻辑,我们应该有一个基本的思路,也即是:当tomcat启动之后,基于NIO的多路复用选择器,创建连接后如果连接接收到请求,会把发送过来的的参数封装进入请求对象,创建一个用于返回回去的对象。将这两者丢到上下文context容器里面。然后从线程池里面拿一个线程交给他,线程拿着自己的盘缠会去通过URL进行handler适配,适配到指定的方法,执行后将结果放到返回对象里面,重新返回给前端。
三、步骤
tomcat启动后会根据server.xml里面的项目地址找到项目,开始解析加载项目的参数信息,:
-2、javaee标准规定了,servlet容器需要在应用项目启动时,应该给项目初始化一个ServletContext作为公共环境容器存放公共信息,也即是上下文对象,ServletContext中的信息都是由容器提供的。
说明:在contextLoaderListener只要实现了ServletContextListener就可以从servletContext中获取我们注入到context中的参数。
说明:至于contextDestroyed则可以在容器结束的执行。
总结:此处的方式思路也就是接下来我们说的WebApplicationContext的基本思路。
在tomcat中的servletContext是一个对象(容器),而web匹配的mvc逻辑也算一个子对象 ,在servletContext中,如下,tomcat本质上跑的是servlet对象
-1、那么WebApplicationContext是什么东西呢?
说明1:servlet容器需要在应用项目启动时,给应用项目初始化一个ServletContext作为公共容器存放公共信息。WebApplicationContext是继承ApplicationContext的接口,扩展ApplicationContext,是专门为Web应用准备的,它允许从相对于Web根目录的路径中装载配置文件完成初始化。
说明2:WebApplicationContext本质上也是基于ContextLoaderListener的一个公共容器。
Spring分别提供了用户启动WebApplicationContext的Servlet和Web容器监听器:
org.springframework.web.context.ContextLoaderListener
org.springframework.web.context.ContextLoaderServlet
说明3:webApplicationContext中是什么内容
说明:webApplicationContext是在web.xml中被声明,然后加载了ContextLoaderListener方法,并在web.xml参数中指明了加载哪些配置文件,由公共的和特有的参数信息组成最终的对象。
总结:Spring容器先根据监听机制初始化WebApplicationContext,然后再初始化web.xml中其他配置的Servlet,将其初始化为自己的上下文信息ServletContext,并加载其设置的配置信息和参数信息到该上下文中,将WebApplicationContext设置为它的父容器。所以最后的关系是作为基础容器的ServletContext包含了WebApplicationContext,WebApplicationContext包含了其他的Servlet上下文环境。
所以:DisPatcherServlet是不是也是一个基于ContextLoaderListener创建的用于管理各种和网络有关的Servlet的容器呢,并且最终作为webApplicationContext的子容器,存放在其中呢?
(以下为SpringMvc的梳理,从小白进入常识的开始)
0、在tomcat的web.xml注册的是servlet,springMvc本质是一个servlet类对象组织起来的框架。
说明:任何想要让tomcat启动的容器都必须实现【servlet】方法,并在tomcat的web.xml中声明!
说明:在genericServlet中看到init()的空心方法,此处采用了模板方法。这个init()方法是在了HttpServletBean中的init()方法进行了首次实现。目标是去调度initServletBean()模板方法。
说明:在FrameworkServlet中进行了实现。实现内容是初始化webApplicationContext
说明:在initFrameworkServlet中
说明:onRefresh是各种spring中的ApplicationContext中的刷新方法,在spring启动之后在【FrameworkServlet】通过监听机制触发。
说明:监听事件
说明:那么什么时候容器会触发刷新呢?
总结:spring在启动的时候无论的什么controller还是什么config都是以单独的bean对象的形式存放在SpringIoc容器中。当spring的容器基于Servlet初始化成功,并加入到webApplicationContext中后则以监听的形式将其他的各种初始化对象参数从springIoc容器中取出来放到webApplicationContext中。此事件基于监听机制进行闭环。
(应用的基本逻辑就是上面,以下描述一个请求怎么进来)
最初的方法就是【FrameworkServlet】,该方法接收到请求后根据不同的类型调度【processRequest】去组织参数后调度【doService】方法
说明:porcessRequest去调度doService
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
//执行Service方法,service完成各种参数处理后去调度doDispatcher,进入springMvc中
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
说明:doService主要是对request对象的封装,加载参数和系统基础参数等
说明:调度DoDispatcher
说明:DoService和DoDispatccher已经进入DoDispatcherServlet方法
说明:基于FlshMap我们也可以去灵活地使用Request方法中的参数内容
说明:前置理解到此结束!
说明:DisPatcherServlet在启动的时候会去加载配置文件里面的参数
说明:
1、这是我们研发平台的一个SpringMvc项目模板。tomcat的底层也拥有一份super【web.xml】我们所有基础tomcat的开发,都是起源于这两份web.xml,从springmvc的整合,到现有的springboot框架, 万变不离其宗!
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<!--开始:配置的名称-->
<display-name>Archetype Created Web Application</display-name>
<!--通过加载 applicationContext.xml 对web和spring容器进行整合加载-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext.xml,
classpath*:/applicationContext-datasource.xml
</param-value>
</context-param>
<!--字符集编码的过滤器:设置所有的请求都走CharacterEncodingFilter 这个类-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern><!--请求类型-->
</filter-mapping>
<!--Session过滤器-->
<filter>
<filter-name>SessionExpireFilter</filter-name>
<filter-class>com.mmall.controller.common.SessionExpireFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SessionExpireFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<!--跨域共享-->
<!--<filter>
<filter-name>CrosAOrginsFilter</filter-name>
<filter-class>com.mmall.controller.common.CrosAOrginsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CrosAOrginsFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>-->
<!-- RequestContextListener实现ServletRequestListener监听器接口接,Web服务器接收的每次请求都会通知该监听器,将Spring容器与Web容器结合的更加密切 -->
<!--对web和spring容器进行监听的容器:继承底层的事件监听机制-->
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<!--监听web容器的关闭和 启动(可选)-->
<!--<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>-->
<!--加载SpringMvc的-中央调度处理器-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup><!-->=0的时候,随容器启动而初始化-->
</servlet>
<!--设置映射器-->
<!--<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>-->
<!--为了改造restful需要自定义springmvc拦截http请求的方式-->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
说明:在代码中可以看到web.xml将filter与代表Mvc的dispatcherServlet给整合在一起。说明了filter不在mvc的体系中,受spring控制的,因此他的层级要平行于mvc体系,排在mvc体系之前。
说明:我们可以用filter来拦截几乎一切的URL,对token进行处理,URL的改造等,但遗憾的是filter只能是进来的控制, 对于出去只有过滤失败的返回。
2、关于springmvc的流程说明
步骤1:http请求打到tomcat监听的端口后交给tomcat进行转发到项目容器中。
步骤2:请求首先会进入dispatcherServlet,在这里通过handler去查找匹配handerMapping得到【handlerExecutionChain】.这个里面存放通过【request请求参数】收集的HandlerAdapter。
步骤3:通过【handlerExecutionChain】得到【handlerAdapter】适配器
步骤4:通过【handlerAdapte适配器】可以得到具体的适配器【controller】
步骤5:handlerAdapter最终在执行完成匹配到的controller后会输出【ModelAndView 】对象。
步骤6:得到handlerAdaper后并不是马上执行,而是通过【HandlerExecutionCHain】提供的【applyPreHandle(processedRequest, response)】方法,去循环执行所有拦截器的前置方法。
步骤7:如果在执行拦截器的过程有地方报错,就会在直接去跳过业务方法,去执行afterCompletion方法。
步骤8:如果拦截器前置方法的执行都没有问题,则会开始去执行我们通过handlerAdapter匹配到的controller方法,如果该controller方法具有AOP则会先去 执行AOP,如果发生异常时AOP没做处理就会把异常给到ControllerAdvice方法去统一处理。因此很多人会实现ResponseBodyAdvice在controller的body返回给前端前完成改造。ResponseBodyAdvice只有在controller实现了responseBody注解后才会生效。
步骤9:如果在此处拦截器或者AOP均是正常执行就去执行拦截器的【postHandler】方法,adaptr捕获到的controller方法是被包裹在postHandler中的。如果我们的controller执行失败,postHandler是不会继续往下执行的。因此在拦截器的postHandler对controller对象的处理会被看成是controllerAdvice之前完成的。postHandler是影响不了controllerAdvice。基于这个思路很多人会在controllerAdvice实现通用泛型的封装/统一异常的处理/固定参数类型的改造。
三、源码
//springmvc的中转对象
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 {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//mappedHandle是一个HandlerExecutionChain对象,这个对象包含了要处理请求的handler和注册是拦截器们
// Determine handler for the current request.
//从这一步就是我们经常说的通过handler去匹配hangdlerMapping
//只是我们匹配到的相关的在handlerExecutionChain中
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//获取handelrAdapter对象,通过handlerAdapter捕获到controller
//通过HandlerExecutionChain对象中的handler找到一个合适的handler适配器
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//applyPreHandle方法就是循环调用们注册的拦截器的preHandle方法,
//如果拦截器返回true,就接着执行下一个,
//如果有返回false的,就结束执行,
//并触发 triggerAfterCompletion 执行拦截器中的afterCompletion方法,请求也不会进入我们的handler
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//这里开始调用我们的handler方法,一般为controller的方法
//当调用的方法为我们aop的切点,那就会触发我们的aop执行
//如果这里抛出异常,并且aop中没有进行捕获处理,那么就会进入我们的ControllerAdvice的异常处理方法
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//执行我们注册的拦截器的postHandle方法,
//可见如果我们的handler不能正常返回,是不会调用postHandle方法的
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 NestedServletException("Handler dispatch failed", err);
}
//在以上代码有异常未处理的时候会进入processHandlerException方法,
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", 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);
}
}
}
}
说明:DoDispacher主要核心思路就是去同个Handler去匹配HandlerMapping,找到HandlerAdapter方法,利用handlerAdapter去获取controller。
说明:在调度controller判断是否有拦截器,有则执行PreHandler拦截器前置器。
//processHandlerException方法
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
//会在这里调用我们的ControllerAdvice的异常处理方法
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
说明:handlerExecutionChain中存放的众多拦截器的前置全部进行执行
//此处是循环地去执行handerEexcutionChain中的拦截器的
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//从第一个往后依次执行preHandle方法
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//从最后一个依次向前执行postHandle
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
//从已执行preHandle方法的最后一个拦截器向前执行afterCompletion
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
说明:HandlerExecution对象中的功能非常少,但都和我们springboot项目正常开放的拦截器息息相关。我们可以再看其他的几个方法,可以看出HandlerExecution就是一个拦截器集合的管理对象
说明:applyPreHandler方法的执行思路如下:
说明:handlerExecutionChain里面就是一个拦截器的存储器
说明:HandlerAdapter类是做什么的?
public interface HandlerAdapter {
//匹配类型和属性
boolean supports(Object handler);
//拿到URL的参数类型去匹配得到具体的DoService
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
说明:先不管怎么实现,单从接口提供方法而言,可以看到是根据handler方法接收请求来返回视图,可以理解为一个适配器。
说明:调用具体的方法来响应用户的请求。当handlerMapping获取到执行请求的controller时,DispatcherServlte会根据controller对应的controller类型来调用相应的HandlerAdapter来进行处理。
四、思路流程