欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring源码学习系列3-springmvc原理 博客分类: springtomcat springservletweb 

程序员文章站 2024-02-12 20:37:52
...
问题:
springmvc是如何控制浏览器显示与返回的modelAndView是一致的,而不是一个用户请求的数据返回给了另一个人?
response中存有客户端的信息

分析DispatcherServlet调用效率
springmvc应用大量缓存来提高其工作效率

springmvc的分发器设计的借鉴意义?
一些特殊url不走springmvc,需单独设计servlet,可以借鉴spring是如何封装请求的,要做哪些处理

controller可以应用aop吗,拦截器与aop有什么区别和相同之处?

spring版本3.2.2


核心接口:
DispatcherServlet

HandlerExecutionChain

HandlerAdapter



1.springmvc启动入口
GenericServlet#init(ServletConfig config)->httpServletBean#init

2.springmvc访问入口
HttpServlet#service->FrameworkServlet#&doGet&doPost->FrameworkServlet#processRequest->DispatcherServlet#doService->DispatcherServlet#doDispatch




1.springmvc启动入口
首先在项目web.xml中配置,springmvc的servlet
<servlet>  
        <servlet-name>SpringMVC</servlet-name>  
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
        <init-param>  
            <param-name>contextConfigLocation</param-name>  
            <param-value>classpath*:spring-mvc.xml</param-value>  
        </init-param>  
    </servlet>  
      
    <servlet-mapping>  
        <servlet-name>SpringMVC</servlet-name>  
        <url-pattern>/</url-pattern>  
    </servlet-mapping>  


注意:这里<url-pattern>应该配置/,表示其他默认servlet没有找到的路径都交由DispatcherServlet处理。如果配置成了/*,那么会拦截所有请求包括.jsp,.js,.css等,访问.jsp时由于找不到默认的handler将无法访问页面。
讲到其他默认servlet找不到的路径,由DispatcherServlet处理,那么有哪些默认servlet呢,到TOMCAT_HOME/conf/web.xml下,
    <!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- The mappings for the JSP servlet -->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

其实DispatcherServlet覆盖的就是default,而jsp servlet拦截了.jsp后缀的访问路径

学习springmvc的过程中,意识到访问.jsp时,不走DispatcherServlet,不觉对.jsp的访问入口感到好奇,进而了解了到相关的知识点。


Class DispatcherServlet

java.lang.Object
javax.servlet.GenericServlet
javax.servlet.http.HttpServlet
org.springframework.web.servlet.HttpServletBean
org.springframework.web.servlet.FrameworkServlet
org.springframework.web.servlet.DispatcherServlet
All Implemented Interfaces:
Serializable, Servlet, ServletConfig, Aware, ApplicationContextAware, EnvironmentAware, EnvironmentCapable

DispatcherServlet本质上是servlet,根据servlet生命周期,首先会执行init方法
httpServletBean#init
/**
	 * Map config parameters onto bean properties of this servlet, and
	 * invoke subclass initialization.
	 * @throws ServletException if bean properties are invalid (or required
	 * properties are missing), or if subclass initialization fails.
	 */
	@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		try {
//获取属性,web.xml配置的init-param。spring中一般通过beanWrapper设置属性,BeanWrapper封装了实例的反射方法
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册属性编辑器,如contextConfigLocation。目标对象是Resource可以直接在xml定义这个Resource为String类型
//DispatcherServlet中并没有定义为Resource类型的属性,所以这里也就用不到这个属性编辑器
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
//初始化属性
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}
//初始化web环境
		// Let subclasses do whatever initialization they like.
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}


//初始化web环境
FrameworkServlet#initServletBean
/**
	 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
	 * have been set. Creates this servlet's WebApplicationContext.
	 */
	@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
//1 创建servletContext上下文环境
			this.webApplicationContext = initWebApplicationContext();
//2 提供给子类初始化的扩展点
			initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		catch (RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}


FrameworkServlet#initWebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
//根据属性contextAttr返回context,若未设置contextAttr为空,返回空
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
//1.1 创建并初始化servletContext(XmlWebApplicationContext)
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			onRefresh(wac);
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}



FrameworkServlet#createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
		return createWebApplicationContext((ApplicationContext) parent);
	}



FrameworkServlet#createWebApplicationContext
/**
	 * Instantiate the WebApplicationContext for this servlet, either a default
	 * {@link org.springframework.web.context.support.XmlWebApplicationContext}
	 * or a {@link #setContextClass custom context class}, if set.
	 * <p>This implementation expects custom contexts to implement the
	 * {@link org.springframework.web.context.ConfigurableWebApplicationContext}
	 * interface. Can be overridden in subclasses.
	 * <p>Do not forget to register this servlet instance as application listener on the
	 * created context (for triggering its {@link #onRefresh callback}, and to call
	 * {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
	 * before returning the context instance.
	 * @param parent the parent ApplicationContext to use, or {@code null} if none
	 * @return the WebApplicationContext for this servlet
	 * @see org.springframework.web.context.support.XmlWebApplicationContext
	 */
	protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
// 1.1.1 实例化容器类XmlWebApplicationContext
// 扩展-可以自定义WebApplicationContext,默认是XmlWebApplication
		Class<?> contextClass = getContextClass();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Servlet with name '" + getServletName() +
					"' will try to create custom WebApplicationContext context of class '" +
					contextClass.getName() + "'" + ", using parent context [" + parent + "]");
		}
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

// 1.1.2 初始化容器类XmlWebApplicationContext
		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		wac.setConfigLocation(getContextConfigLocation());

		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

扩展-自定义WebApplicationContext


FrameworkServlet#configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
				// Generate default id...
				ServletContext sc = getServletContext();
				if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
					// Servlet <= 2.4: resort to name specified in web.xml, if any.
					String servletContextName = sc.getServletContextName();
					if (servletContextName != null) {
						wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
								"." + getServletName());
					}
					else {
						wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
					}
				}
				else {
					// Servlet 2.5's getContextPath available!
					wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
							ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
				}
			}
		}

		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
//定义并注册applicationListener,context初始化结束时调用。以便回调FrameworkServlet#onRefresh
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);
//扩展-servlet设置参数globalInitializerClasses,调用ApplicationContextInitializer#initialize
		applyInitializers(wac);
//解析并初始化配置文件(如:spring-servlet.xml)中的配置
//具体见:http://newjava-sina-cn.iteye.com/blog/2369741
		wac.refresh();
	}



创建容器
====================================职责分界线==============================
在容器中初始化springmvc信息

XmlWebApplicationContext#refresh

DispatcherServlet#doService

对于这些父子类分工合作的类而言,最终子类总是实现最核心的部分,而其父类则很多是一些环境准备或初始化工作。用户可以自定义这些子类,沿用相似的自定义继承架构



监听器回调-初始化springmvc信息-springmvc核心
DispatcherServlet#onRefresh
/**
	 * This implementation calls {@link #initStrategies}.
	 */
	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
//初始化映射处理器 handlerMappings
		initHandlerMappings(context);
//初始化适配器处理器
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
//初始化视图解析器 viewResolvers
		initViewResolvers(context);
		initFlashMapManager(context);
	}











2.springmvc访问入口
以doPost为例
FrameworkServlet#doPost
/**
	 * Delegate POST requests to {@link #processRequest}.
	 * @see #doService
	 */
	@Override
	protected final void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//1 http请求统一转给processRequest处理
		processRequest(request, response);
	}


FrameworkServlet#processRequest
/**
	 * Process this request, publishing an event regardless of the outcome.
	 * <p>The actual event handling is performed by the abstract
	 * {@link #doService} template method.
	 */
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;

//1.1 绑定请求信息到当前线程
//创建localeContext
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// zh_CN
		LocaleContext localeContext = buildLocaleContext(request);

//创建requestAttributes
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

//Spring 中的WebAsyncManager 有什么应用场景?
//https://segmentfault.com/q/1010000009403867/a-1020000009411777
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

//将请求信息绑定到当前线程
		initContextHolders(request, localeContext, requestAttributes);

		try {
//1.2 处理请求信息
			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 {
			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, startTime, failureCause);
		}
	}




DispatcherServlet#doService
//由中文注释可知,设置特殊的request attributes并代理doDispatch处理
/**
	 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
	 * for the actual dispatching.
	 */
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
		}

//如父jsp页面包含<jsp:include page="${basePath}login.do?method=getIncludePage"></jsp:include>。父页面请求login.do?method=getIncludePage路径时,则属性中包含javax.servlet.include.request_uri等include页面信息的相关属性
// 缓存这些include页面相关属性,防止处理器改变这些信息。在处理器处理完成后,如果属性值发生变化则恢复restoreAttributesAfterInclude(request, attributesSnapshot);
		// 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)) {
			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());
// spring mvc支持页面显示的国际化配置
//涉及的spring元素:<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
//ResourceBundleMessageSource  CookieLocaleResolver
//<spring:message code=""/> <fmt:message key="" />
//参考:http://blog.lifw.org/post/26098052
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
// spring mvc支持主题
//涉及的spring元素:<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
//ResourceBundleThemeSource,SessionThemeResolver
//<spring:theme code=''/>
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

//获取前一次request中设置的flashAttribute
//https://t.hao0.me/spring/2016/01/15/spring-flash-map.html
		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
		}
//将值保存到request中。这样就不需要通过浏览器跳转组装链接来传递参数了
//如:
//1-flashAttribute
//redirectAttributes.addFlashAttribute("message", "Submit successfully!");
//redirect:url 
//2-没有flashAttribute
//redirect:url+"?" + successMsg=
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

		try {
//springmvc转发请求的核心方法
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}




DispatcherServlet#doDispatch
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);

//1 获取执行请求的链HandlerExecutionChain(包括controller和拦截器)
				// 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.
//2 获取执行handler的适配器
				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()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

//3 执行拦截器的preHandle,如:未登录直接返回
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

//4 执行用户定义的业务逻辑
// 见<spring源码学习系列3.2-handlerAdapter执行>
				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(request, mv);
//5 执行拦截器的postHandle
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Error err) {
			triggerAfterCompletionWithError(processedRequest, response, mappedHandler, 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);
				}
			}
		}
	}



DispatcherServlet#getHandler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		for (HandlerMapping hm : this.handlerMappings) {
			if (logger.isTraceEnabled()) {
				logger.trace(
						"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
			}
//1.1 委托handlerMapping获取HandlerExecutionChain
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}



AbstractHandlerMapping#getHandler
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//1.1.1 获取默认HandlerExecutionChain(包括controller和默认interceptor)
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}
//1.1.2 springmvc扩展点-HandlerExecutionChain添加其他自定义的interceoptor
		return getHandlerExecutionChain(handler, request);
	}



AbstractHandlerMapping#getHandlerExecutionChain-执行链中添加自定义拦截器
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
//1.1.2.1 abstractHandlerMapping.adaptedInterceptors属性
//abstractHandlerMapping.interceptoers转化而来,见<spring源码学习系列3.1-handlerMapping初始化>
		chain.addInterceptors(getAdaptedInterceptors());

		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
//1.1.2.2 abstractHandlerMapping.mappedInterceptor属性
//<mvc:interceptor/>和abstractHandlerMapping.interceptoers转化而来
		for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}

		return chain;
	}







参考:
Spring IOC BeanWrapper
http://www.infocool.net/kb/Other/201611/227714.html

Spring IOC BeanWrapper
http://blog.csdn.net/u012410733/article/details/53346345

SpringMVC源码剖析(一)- 从抽象和接口说起
https://my.oschina.net/lichhao/blog/99039?p=2&temp=1494051282915#blog-comments-list

WEB请求处理五:MVC框架请求处理
http://www.jianshu.com/p/6462e69ce241

困惑:谁能帮我解答一个关于BeanWrapperImpl设计的问题
http://stamen.iteye.com/blog/43048

你真的了解DispatcherServlet的url-pattern配置吗?
http://www.itwendao.com/article/detail/382888.html

SpingMVC第一个拦截器未执行BUG分析
http://uuhorse.iteye.com/blog/2311494
http://www.cnblogs.com/gkaigk1987/articles/5466052.html

handlerAdapter
http://www.cnblogs.com/wade-luffy/p/6085172.html


http://www.cnblogs.com/JemBai/archive/2010/01/07/1641048.html
http://blog.csdn.net/u012420654/article/details/58687790