0
点赞
收藏
分享

微信扫一扫

高级基础_框架的执行顺序(入门篇半成品)

就是耍帅 2022-04-25 阅读 32

一、介绍

     这周在针对旧有系统改造的同时遇到一个问题,队友把通用泛型通过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来进行处理。

四、思路流程

举报

相关推荐

0 条评论