首页 > 编程语言 >DispatcherServlet类源码分析

DispatcherServlet类源码分析

时间:2022-09-04 01:56:35浏览次数:73  
标签:分析 null mappedHandler request 源码 ex context DispatcherServlet response

DispatcherServlet类结构图

DispatcherServlet类结构图

 

DispatcherServlet源码分析

1. 加载配置文件

/** 
 * This implementation calls {@link #initStrategies}. 
 */  
@Override  
protected void onRefresh(ApplicationContext context) {  
	initStrategies(context);  
}  

/** 
 * 初始化定位解析器、主题解析器、处理器映射器、处理器适配器、异常解析器、视图解析器等等
 */  
protected void initStrategies(ApplicationContext context) {  
	initMultipartResolver(context);  
	initLocaleResolver(context);  
	initThemeResolver(context);  
	initHandlerMappings(context);  
	initHandlerAdapters(context);  
	initHandlerExceptionResolvers(context);  
	initRequestToViewNameTranslator(context);  
	initViewResolvers(context);  
	initFlashMapManager(context);  
} 

initStrategies()方法我们可以看出DispatcherServlet实例化时会初始化web层相关的bean,如HandlerMapping,HandlerAdapter等,并且如果我们没有进行配置,DispatcherServlet会提供默认的配置。以上的Servlet的体系结构以及DispatcherServlet的实例化过程我们可以看出主要完成以下几个事情:

 

(1)通过配置Servlet实现SpringMVC核心控制器DispatcherServlet的初始化;

 

(2)通过ServletContext共享Spring根上下文,使得每一个Servlet实例获取根上下文中的bean,用于实例化SpringMVC web层的相关bean。

(3)初始化DispatcherServlet作为核心控制器,接收处理请求需要的相关资源,如HandlerMapping,HandlerAdapter等。

(4)通过Servlet体系结构中的继承关系以及抽象方法,可以根据具体的需求对各个层级的Servlet抽象方法进行重写以满足不同的功能需要,父类中只定义流程和方法引用,具体实现由子Servlet完成,实现定义与实现的分离,便于扩展。

2. processRequest()方法

@Override  
protected final void doGet(HttpServletRequest request, HttpServletResponse response)  
        throws ServletException, IOException {  
    processRequest(request, response);  
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)  
        throws ServletException, IOException {  
  
    long startTime = System.currentTimeMillis();  
    Throwable failureCause = null;  
  
    // Expose current LocaleResolver and request as LocaleContext.  
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();  
    LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);  
  
    // Expose current RequestAttributes to current thread.  
    RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();  
    ServletRequestAttributes requestAttributes = null;  
    if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {  
        requestAttributes = new ServletRequestAttributes(request);  
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);  
    }  
  
    if (logger.isTraceEnabled()) {  
        logger.trace("Bound request context to thread: " + request);  
    }  
  
    try {  
        doService(request, response);  
    }  
    catch (ServletException ex) {  
        failureCause = ex;  
        throw ex;  
    }  
    catch (IOException ex) {  
        failureCause = ex;  
        throw ex;  
    }  
    catch (Throwable ex) {  
        failureCause = ex;  
        throw new NestedServletException("Request processing failed", ex);  
    }  
  
    finally {  
        // Clear request attributes and reset thread-bound context.  
        LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);  
        if (requestAttributes != null) {  
            RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);  
            requestAttributes.requestCompleted();  
        }  
        if (logger.isTraceEnabled()) {  
            logger.trace("Cleared thread-bound request context: " + request);  
        }  
  
        if (logger.isDebugEnabled()) {  
            if (failureCause != null) {  
                this.logger.debug("Could not complete request", failureCause);  
            }  
            else {  
                this.logger.debug("Successfully completed request");  
            }  
        }  
        if (this.publishEvents) {  
            // Whether or not we succeeded, publish an event.  
            long processingTime = System.currentTimeMillis() - startTime;  
            this.webApplicationContext.publishEvent(  
                    new ServletRequestHandledEvent(this,  
                            request.getRequestURI(), request.getRemoteAddr(),  
                            request.getMethod(), getServletConfig().getServletName(),  
                            WebUtils.getSessionId(request), getUsernameForRequest(request),  
                            processingTime, failureCause));  
        }  
    }  
}

DispatcherServlet也是通过自己的service()方法来接收和转发Http请求到具体的doGet()或doPost()这些方法的。以一次典型的GET请求为例,经过HttpServlet基类中service()方法的委派,请求会被转发到doGet()方法中。doGet()方法,在DispatcherServlet的父类FrameworkServlet类中被覆写。

 

processRequest()方法理解的要点是以doService()方法为区隔,前一部分是将当前请求的Locale对象和属性,分别设置到LocaleContextHolder和RequestContextHolder这两个抽象类中的ThreadLocal对象中,也就是分别将这两个东西和请求线程做了绑定。在doService()处理结束后,再恢复回请求前的LocaleContextHolder和RequestContextHolder,也即解除线程绑定。每次请求处理结束后,容器上下文都发布了一个ServletRequestHandledEvent事件,你可以注册监听器来监听该事件。
可以看到,processRequest()方法只是做了一些线程安全的隔离,真正的请求处理,发生在doService()方法中。

3. doService()方法

@Override  
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {  
	if (logger.isDebugEnabled()) {  
		String requestUri = urlPathHelper.getRequestUri(request);  
		logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +  
				" request for [" + requestUri + "]");  
	}  

	// Keep a snapshot of the request attributes in case of an include,  
	// to be able to restore the original attributes after the include.  
	Map<string, object=""> attributesSnapshot = null;  
	if (WebUtils.isIncludeRequest(request)) {  
		logger.debug("Taking snapshot of request attributes before include");  
		attributesSnapshot = new HashMap<string, object="">();  
		Enumeration attrNames = request.getAttributeNames();  
		while (attrNames.hasMoreElements()) {  
			String attrName = (String) attrNames.nextElement();  
			if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {  
				attributesSnapshot.put(attrName, request.getAttribute(attrName));  
			}  
		}  
	}  

	// Make framework objects available to handlers and view objects.  
	request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());  
	request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);  
	request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);  
	request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());  

	FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);  
	if (inputFlashMap != null) {  
		request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));  
	}  
	request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());  
	request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);  

	try {  
		doDispatch(request, response); //这边最终也是调用了doDispatch方法,该方法主要用来处理SPring框架的具体业务分发逻辑。  
	}  
	finally {  
		// Restore the original attribute snapshot, in case of an include.  
		if (attributesSnapshot != null) {  
			restoreAttributesAfterInclude(request, attributesSnapshot);  
		}  
	}  
}

doService()方法中requet.setAttribute()方法的调用,将前面在初始化流程中实例化的对象设置到http请求的属性中,供下一步处理使用,其中有容器的上下文对象、本地化解析器等SpringMVC特有的编程元素。不同于Struts2中的ValueStack,SpringMVC的数据并没有从HttpServletRequest对象中抽离出来再存进另外一个编程元素,这也跟SpringMVC的设计思想有关。因为从一开始,SpringMVC的设计者就认为,不应该将请求处理过程和Web容器完全隔离。所以,真正发生请求转发的方法doDispatch()中,它的参数是HttpServletRequest和HttpServletResponse对象。

4. doDispatch()方法

//Spring框架最终的分发都是通过该方法的  
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  
	HttpServletRequest processedRequest = request;  
	HandlerExecutionChain mappedHandler = null;  
	int interceptorIndex = -1;  

	try {  
		ModelAndView mv;  
		boolean errorView = false;  

		try {  
			processedRequest = checkMultipart(request);  

			// Determine handler for the current request.  
			mappedHandler = getHandler(processedRequest, false);  
			if (mappedHandler == null || mappedHandler.getHandler() == null) {  
				noHandlerFound(processedRequest, response);  
				return;  
			}  

			// 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 (logger.isDebugEnabled()) {  
					String requestUri = urlPathHelper.getRequestUri(request);  
					logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);  
				}  
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {  
					return;  
				}  
			}  

			// 这里是处理前置拦截器  
			HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();  
			if (interceptors != null) {  
				for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); return; } interceptorIndex = i; } } //处理最终的Action逻辑 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // Do we need view name translation? if (mv != null && !mv.hasView()) { mv.setViewName(getDefaultViewName(request)); } //处理后置拦截器 if (interceptors != null) { for (int i = interceptors.length - 1; i >= 0; i--) {  
					HandlerInterceptor interceptor = interceptors[i];  
					interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);  
				}  
			}  
		}  
		catch (ModelAndViewDefiningException ex) {  
			logger.debug("ModelAndViewDefiningException encountered", ex);  
			mv = ex.getModelAndView();  
		}  
		catch (Exception ex) {  
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);  
			mv = processHandlerException(processedRequest, response, handler, ex);  
			errorView = (mv != null);  
		}  

		// Did the handler return a view to render?  
		if (mv != null && !mv.wasCleared()) {  
			render(mv, processedRequest, response);  
			if (errorView) {  
				WebUtils.clearErrorRequestAttributes(request);  
			}  
		}  
		else {  
			if (logger.isDebugEnabled()) {  
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +  
						"': assuming HandlerAdapter completed request handling");  
			}  
		}  

		// Trigger after-completion for successful outcome.  
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);  
	}  

	catch (Exception ex) {  
		// Trigger after-completion for thrown exception.  
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);  
		throw ex;  
	}  
	catch (Error err) {  
		ServletException ex = new NestedServletException("Handler processing failed", err);  
		// Trigger after-completion for thrown exception.  
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);  
		throw ex;  
	}  

	finally {  
		// Clean up any resources used by a multipart request.  
		if (processedRequest != request) {  
			cleanupMultipart(processedRequest);  
		}  
	}  
}

doDispatch()是整个请求转发流程中最核心的方法,DispatcherServlet所接收的Http请求,经过层层转发,最终都是汇总到这个方法中来进行最后的请求分发和处理。它通过高度抽象的接口,描述出了一个MVC(Model-View-Controller)设计模式的实现方案。Model、View、Controller三种层次的编程元素,在SpringMVC中都有大量的实现类,各种处理细节也是千差万别。但是,它们最后都是由,也都能由doDispatch()方法来统一描述,这就是接口和抽象的威力,万变不离其宗。

标签:分析,null,mappedHandler,request,源码,ex,context,DispatcherServlet,response
From: https://www.cnblogs.com/bkycnd/p/16654132.html

相关文章

  • DispatcherServlet工作原理,处理流程
    工作原理1.当DispatcherServlet接到请求时,他先回查找适当的处理程序来处理请求。DispatcherServlet通过一个或者多个处理程序映射,将每个请求映射到处理程序中。处理程序......
  • DispatcherServlet初始化顺序详解
    1. Web容器启动时将调用HttpServletBean的init方法publicabstractclassHttpServletBeanextendsHttpServletimplementsEnvironmentAware{@Overridepubli......
  • Explain-SQL语句分析
    explainmysql数据库下,为了判断一条sql是如何执行的,我们需要explain命令。explain命令并不会真正去执行sql语句,而是对语句做一个分析。explain可以告诉我们什么sql......
  • 小迪安全D4笔记:基础入门-web源码拓展
    title:小迪安全D4笔记:基础入门-web源码拓展author:TTdate:2022-09-02一、web源码目录结构后台目录模板目录数据库目录数据库配置文件二、web源码脚本类型ASP......
  • 时序分析第6讲 input delay 实操
    实操      比起之前添加了一些信号 生成bit后   这个就是我们要添加约束的工具在这里主时钟sdrclk既没有送到PLL里边,也没有送到其他的时钟......
  • 时序分析4 IO 时序分析 5input delay介绍
    IO时序分析IO时序分析需要分析哪些关键的参数,又跟哪些关键参数有关联 要进行时序分析就离不开寄存器  寄存器要满足建立时间,保持时间的门限值fpga引脚(pin)与外部......
  • Django CBV源码执行流程
         ......
  • 开关电源辅助电源芯片供电分析
    随着开关电源的输出功率越来越大,开关电源控制芯片供电一般由单独的电力电子电路组成,与主电路一样,是从同一个输入源或者输出源取电。可能是反激电路,也可能是Buck或者推......
  • Elastic.Apm 源码解析
    源码中有如下sample:1vardistributedTracingData=DistributedTracingData.TryDeserializeFromString(args[0]);23WriteLineToConsole($"Calleeprocessstar......
  • 零售案例分析(PowerBI)
    零售案例分析(PowerBI)步骤:1.从SQL中取出订单数据orderinfo2.将数据导入PowerBI,使用PowerQuery对数据检查,校正3.分析计算维度,进行计算金额合计=SUM(orderinfo[......