Spring Core 官方文档阅读笔记(十五)

时间:2020-05-22
本文章向大家介绍Spring Core 官方文档阅读笔记(十五),主要包括Spring Core 官方文档阅读笔记(十五)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

注意啦,现在开始填坑了!!!

1. Handler

Handler,在spring中标识了一个处理,对应的HandlerMapping即为该处理的映射,HandlerAdapter即为该处理的适配器。那么问题来了。。。HandlerMapping是将处理与什么组成映射关系,而HandlerAdapter又是要适配什么?我们一步一步来解析。
先来回顾一下我们前面整理的,DispatcherServlet处理一个请求的过程:

  1. 绑定WebApplicationContext到请求上。
  2. 绑定LocaleResolver到请求上。
  3. 绑定ThemeResolver到请求上。
  4. 检查请求是否是Multipart,如果是,则将请求包装成MultipartHttpServletRequest。
  5. 搜索对应的Handler,并获取Model。
  6. 返回Model,并渲染视图。

在第五步,就开始搜索对应的Handler了,还是先看代码,DispatcherServlet的doDispatch方法:

/**
	 * Process the actual dispatching to the handler.
	 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
	 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
	 * to find the first that supports the handler class.
	 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
	 * themselves to decide which methods are acceptable.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @throws Exception in case of any kind of processing failure
	 */
	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);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				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()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

				applyDefaultViewName(processedRequest, mv);
				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);
			}
			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);
				}
			}
		}
	}

在检查完multipart以后,通过getHandler(processedRequest)方法获取Handler。点进去看一下getHandler方法:

/**
	 * Return the HandlerExecutionChain for this request.
	 * <p>Tries all handler mappings in order.
	 * @param request current HTTP request
	 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
	 */
	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() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}

逐步看一下,首先,这个getHandler处理的第一步是遍历了一个this.handlerMappings。这个handlerMappings是哪里来的呢?看一下声明的地方:

/** List of HandlerMappings used by this servlet */
	private List<HandlerMapping> handlerMappings;

根据注释,这个handlerMappings是当前servlet所使用的的一个HandlerMapping的list集合。再看这个集合是赋值的地方:

/**
	 * Initialize the HandlerMappings used by this class.
	 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
	 * we default to BeanNameUrlHandlerMapping.
	 */
	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

还是老样子,先看注释,上面说通过这个方法来初始化HandlerMappings,而且如果没有HandlerMapping被定义在BeanFactory中,将会默认使用BeanNameUrlHandlerMapping。这两个又是什么鬼?来看一下:

  • HandlerMapping
/**
 * Interface to be implemented by objects that define a mapping between
 * requests and handler objects.
 *
 * <p>This class can be implemented by application developers, although this is not
 * necessary, as {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}
 * and {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}
 * are included in the framework. The former is the default if no
 * HandlerMapping bean is registered in the application context.
 *
 * <p>HandlerMapping implementations can support mapped interceptors but do not
 * have to. A handler will always be wrapped in a {@link HandlerExecutionChain}
 * instance, optionally accompanied by some {@link HandlerInterceptor} instances.
 * The DispatcherServlet will first call each HandlerInterceptor's
 * {@code preHandle} method in the given order, finally invoking the handler
 * itself if all {@code preHandle} methods have returned {@code true}.
 *
 * <p>The ability to parameterize this mapping is a powerful and unusual
 * capability of this MVC framework. For example, it is possible to write
 * a custom mapping based on session state, cookie state or many other
 * variables. No other MVC framework seems to be equally flexible.
 *
 * <p>Note: Implementations can implement the {@link org.springframework.core.Ordered}
 * interface to be able to specify a sorting order and thus a priority for getting
 * applied by DispatcherServlet. Non-Ordered instances get treated as lowest priority.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see org.springframework.core.Ordered
 * @see org.springframework.web.servlet.handler.AbstractHandlerMapping
 * @see org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
 * @see org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
 */
public interface HandlerMapping {

	/**
	 * Name of the {@link HttpServletRequest} attribute that contains the path
	 * within the handler mapping, in case of a pattern match, or the full
	 * relevant URI (typically within the DispatcherServlet's mapping) else.
	 * <p>Note: This attribute is not required to be supported by all
	 * HandlerMapping implementations. URL-based HandlerMappings will
	 * typically support it, but handlers should not necessarily expect
	 * this request attribute to be present in all scenarios.
	 */
	String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

	/**
	 * Name of the {@link HttpServletRequest} attribute that contains the
	 * best matching pattern within the handler mapping.
	 * <p>Note: This attribute is not required to be supported by all
	 * HandlerMapping implementations. URL-based HandlerMappings will
	 * typically support it, but handlers should not necessarily expect
	 * this request attribute to be present in all scenarios.
	 */
	String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

	/**
	 * Name of the boolean {@link HttpServletRequest} attribute that indicates
	 * whether type-level mappings should be inspected.
	 * <p>Note: This attribute is not required to be supported by all
	 * HandlerMapping implementations.
	 */
	String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

	/**
	 * Name of the {@link HttpServletRequest} attribute that contains the URI
	 * templates map, mapping variable names to values.
	 * <p>Note: This attribute is not required to be supported by all
	 * HandlerMapping implementations. URL-based HandlerMappings will
	 * typically support it, but handlers should not necessarily expect
	 * this request attribute to be present in all scenarios.
	 */
	String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

	/**
	 * Name of the {@link HttpServletRequest} attribute that contains a map with
	 * URI matrix variables.
	 * <p>Note: This attribute is not required to be supported by all
	 * HandlerMapping implementations and may also not be present depending on
	 * whether the HandlerMapping is configured to keep matrix variable content
	 * in the request URI.
	 */
	String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

	/**
	 * Name of the {@link HttpServletRequest} attribute that contains the set of
	 * producible MediaTypes applicable to the mapped handler.
	 * <p>Note: This attribute is not required to be supported by all
	 * HandlerMapping implementations. Handlers should not necessarily expect
	 * this request attribute to be present in all scenarios.
	 */
	String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

	/**
	 * Return a handler and any interceptors for this request. The choice may be made
	 * on request URL, session state, or any factor the implementing class chooses.
	 * <p>The returned HandlerExecutionChain contains a handler Object, rather than
	 * even a tag interface, so that handlers are not constrained in any way.
	 * For example, a HandlerAdapter could be written to allow another framework's
	 * handler objects to be used.
	 * <p>Returns {@code null} if no match was found. This is not an error.
	 * The DispatcherServlet will query all registered HandlerMapping beans to find
	 * a match, and only decide there is an error if none can find a handler.
	 * @param request current HTTP request
	 * @return a HandlerExecutionChain instance containing handler object and
	 * any interceptors, or {@code null} if no mapping found
	 * @throws Exception if there is an internal error
	 */
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

emmm。。。是个接口,来读一下注释。这个接口定义了请求和处理之间的映射关系。而真正的处理则被包含在HandlerExecutionChain中,其中包括HandlerInterceptor实现。而DispatcherServlet也会先调用HandlerInterceptor的preHandler,当所有的preHandler方法都返回true的时候,才会去执行handler。

  • BeanNameUrlHandlerMapping
/**
 * Implementation of the {@link org.springframework.web.servlet.HandlerMapping}
 * interface that map from URLs to beans with names that start with a slash ("/"),
 * similar to how Struts maps URLs to action names.
 *
 * <p>This is the default implementation used by the
 * {@link org.springframework.web.servlet.DispatcherServlet}, along with
 * {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}.
 * Alternatively, {@link SimpleUrlHandlerMapping} allows for customizing a
 * handler mapping declaratively.
 *
 * <p>The mapping is from URL to bean name. Thus an incoming URL "/foo" would map
 * to a handler named "/foo", or to "/foo /foo2" in case of multiple mappings to
 * a single handler. Note: In XML definitions, you'll need to use an alias
 * name="/foo" in the bean definition, as the XML id may not contain slashes.
 *
 * <p>Supports direct matches (given "/test" -> registered "/test") and "*"
 * matches (given "/test" -> registered "/t*"). Note that the default is
 * to map within the current servlet mapping if applicable; see the
 * {@link #setAlwaysUseFullPath "alwaysUseFullPath"} property for details.
 * For details on the pattern options, see the
 * {@link org.springframework.util.AntPathMatcher} javadoc.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see SimpleUrlHandlerMapping
 */
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {

	/**
	 * Checks name and aliases of the given bean for URLs, starting with "/".
	 */
	@Override
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<String>();
		if (beanName.startsWith("/")) {
			urls.add(beanName);
		}
		String[] aliases = getApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) {
				urls.add(alias);
			}
		}
		return StringUtils.toStringArray(urls);
	}

}

注释上面说,这个BeanNameUrlHandlerMapping是HandlerMapping的一个实现类,通过以"/"开头的名称构建URL和Bean的映射关系。并且,这个是个被DispatcherServlet使用的默认实现类。

大致了解了HandlerMapping和BeanNameUrlHandlerMapping,我们接着看initHandlerMapping的逻辑,首先,判断detectAllHandlerMappings是否是true,从字面意思上来看,这个值就是控制是否需要查找所有的HandlerMapping。来看看它的定义:

/** Detect all HandlerMappings or just expect "handlerMapping" bean? */
private boolean detectAllHandlerMappings = true;

查找所有HandlerMappings或者只需要名为handlerMapping的bean。

那么我们就可以猜测,根据这个值而分出来的分支就是在做这个事情,查找所有HandlerMapping或者只查找名为handlerMapping的bean。

接着看代码,如果detectAllHandlerMappings为true,则调用

BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false)

来查找ApplicationContext以及父类Context下所有的HandlerMapping,这里注意后面两个boolean参数,分别是includeNonSingletons和allowEagerInit,includeNonSingletons用于控制是否包含非singleton的bean,而allowEagerInit则用于控制是否需要初始化lazy-init的单例bean和被FactoryBean创建的对象。

通过上面这个方法找到容器中所有的HandlerMapping类型的bean,然后塞到this.handlerMappings里,进行排序。

而如果detectAllHandlerMapping是false,则只通过

context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);

来查找名为handlerMapping的HandlerMapping。

倘若这两个分支查找的最终结果是null,则调用

getDefaultStrategies(context, HandlerMapping.class);

来创建默认的HandlerMapping。这个默认的是什么呢?记得我们之前说过,DispatcherServlet在WebApplicationContext中检查每个特殊的Bean,如果没有匹配的Bean,它将使用DispatcherServlet.properties中配置的默认类型。没错,这个默认的HandlerMapping也是在这个文件中定义的。

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

默认的即是BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping。

考虑一下,既然BeanNameUrlHandlerMapping是DispatcherServlet默认的HandlerMapping,那么它一定是能够被容器找到的,也就是说,在执行到BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false)方法的时候,BeanNameUrlHandlerMapping的实例已经存在于容器中了,那么是什么时候被实例化的呢?
Spring在初始化BeanFactory的时候,会查询并调用所有的BeanPostProccessor,其中就包括ApplicationContextAwareProccessor,而这个方法中则会调用setApplicationContext方法去设置ApplicationContext。ApplicationObjectSupport实现了ApplicationContextAware接口,在它的setApplicationContext方法中调用了initApplicationContext方法。恰好BeanNameUrlHandlerMapping的父类AbstractDetectingUrlHandlerMapping重写了这个方法:

/**
 * Calls the {@link #detectHandlers()} method in addition to the
 * superclass's initialization.
 */
@Override
public void initApplicationContext() throws ApplicationContextException {
	super.initApplicationContext();
	detectHandlers();
}

在detectHandlers方法中,查询了所有Handler。

	/**
	 * Register all handlers found in the current ApplicationContext.
	 * <p>The actual URL determination for a handler is up to the concrete
	 * {@link #determineUrlsForHandler(String)} implementation. A bean for
	 * which no such URLs could be determined is simply not considered a handler.
	 * @throws org.springframework.beans.BeansException if the handler couldn't be registered
	 * @see #determineUrlsForHandler(String)
	 */
	protected void detectHandlers() throws BeansException {
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
		}
		String[] beanNames = (this.detectHandlersInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
				getApplicationContext().getBeanNamesForType(Object.class));

		// Take any bean name that we can determine URLs for.
		for (String beanName : beanNames) {
			String[] urls = determineUrlsForHandler(beanName);
			if (!ObjectUtils.isEmpty(urls)) {
				// URL paths found: Let's consider it a handler.
				registerHandler(urls, beanName);
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
				}
			}
		}
	}

同样是使用BeanFactoryUtils.beanNamesForTypeIncludingAncestors方法来获取所有Bean,然后遍历,通过BeanNameUrlHandlerMapping类实现的determineUrlsForHandler方法,来筛选:

	/**
	 * Checks name and aliases of the given bean for URLs, starting with "/".
	 */
	@Override
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<String>();
		if (beanName.startsWith("/")) {
			urls.add(beanName);
		}
		String[] aliases = getApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) {
				urls.add(alias);
			}
		}
		return StringUtils.toStringArray(urls);
	}

只要是beanName或者alias是"/"开头的,都会添加到url的数组中。然后在detectHandlers中,通过获取到的数组,来注册Handler。

	/**
	 * Register the specified handler for the given URL paths.
	 * @param urlPaths the URLs that the bean should be mapped to
	 * @param beanName the name of the handler bean
	 * @throws BeansException if the handler couldn't be registered
	 * @throws IllegalStateException if there is a conflicting handler registered
	 */
	protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
		Assert.notNull(urlPaths, "URL path array must not be null");
		for (String urlPath : urlPaths) {
			registerHandler(urlPath, beanName);
		}
	}
	/**
	 * Register the specified handler for the given URL path.
	 * @param urlPath the URL the bean should be mapped to
	 * @param handler the handler instance or handler bean name String
	 * (a bean name will automatically be resolved into the corresponding handler bean)
	 * @throws BeansException if the handler couldn't be registered
	 * @throws IllegalStateException if there is a conflicting handler registered
	 */
	protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
		Assert.notNull(urlPath, "URL path must not be null");
		Assert.notNull(handler, "Handler object must not be null");
		Object resolvedHandler = handler;

		// Eagerly resolve handler if referencing singleton via name.
		if (!this.lazyInitHandlers && handler instanceof String) {
			String handlerName = (String) handler;
			if (getApplicationContext().isSingleton(handlerName)) {
				resolvedHandler = getApplicationContext().getBean(handlerName);
			}
		}

		Object mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {
			if (mappedHandler != resolvedHandler) {
				throw new IllegalStateException(
						"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
						"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
			}
		}
		else {
			if (urlPath.equals("/")) {
				if (logger.isInfoEnabled()) {
					logger.info("Root mapping to " + getHandlerDescription(handler));
				}
				setRootHandler(resolvedHandler);
			}
			else if (urlPath.equals("/*")) {
				if (logger.isInfoEnabled()) {
					logger.info("Default mapping to " + getHandlerDescription(handler));
				}
				setDefaultHandler(resolvedHandler);
			}
			else {
				this.handlerMap.put(urlPath, resolvedHandler);
				if (logger.isInfoEnabled()) {
					logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
				}
			}
		}
	}

首先,通过bean名称获取handler的实例,然后从handlerMap中通过url来获取handler,如果获取到的结果不是null,则判断与前面通过bean名称获取到的实例是否相等,若相等,说明已经注册过了,直接跳过,若不等,则说明有冲突,此时就会抛出IllegalStateException异常。如果handlerMap中获取不到,说明没有注册过,那么就要将handler与url绑定起来。

我们把SpringMVC的启动过程整体串起来看一下,SpringMVC中一个HttpServletBean继承了HttpServlet,并重写了init()方法。我们知道创建servlet的时候会调用init(ServletConfig)方法,而这个方法里调用了init()。那么也就是通过HttpServletBean来创建Servlet。

	/**
	 * 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 {

		// Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();
	}

方法里有一大部分代码都是在准备init-param,最后有个initServletBean方法,注释上说是让子类做自定义的初始化,这个方法在HttpServletBean里是一个空方法,我们找到他的子类--FrameworkServlet,点进去看一下:

	/**
	 * 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 " + getClass().getSimpleName() + " '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("Initializing Servlet '" + getServletName() + "'");
		}
		long startTime = System.currentTimeMillis();

		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isDebugEnabled()) {
			String value = this.enableLoggingRequestDetails ?
					"shown which may lead to unsafe logging of potentially sensitive data" :
					"masked to prevent unsafe logging of potentially sensitive data";
			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
					"': request parameters and headers will be " + value);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}

在FrameworkServlet中重写了父类的方法,也就是最终调用的其实是FrameworkServlet中的initServletBean。也就是说,FrameworkServlet继承了HttpServletBean抽象类,而HttpServletBean又继承了HttpServlet,所以当初始化Servlet的时候,其实就是初始化的FrameworkServlet。继续看代码,在try-catch块里,先调用了initWebApplicationContext,看方法名我们可以猜到,这里应该就是初始化Spring IOC容器的地方了。

	/**
	 * Initialize and publish the WebApplicationContext for this servlet.
	 * <p>Delegates to {@link #createWebApplicationContext} for actual creation
	 * of the context. Can be overridden in subclasses.
	 * @return the WebApplicationContext instance
	 * @see #FrameworkServlet(WebApplicationContext)
	 * @see #setContextClass
	 * @see #setContextConfigLocation
	 */
	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
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			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.
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

一行一行看,第一句代码

WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());

这句代码我们慢慢分析,首先,getServletContext方法是GenericServlet中的方法,用来获取ServletContext,不多说。然后我们看下WebApplicationContextUtils如何通过这个ServletContext来获取一个WebApplicationContext:

    /**
     * Find the root {@code WebApplicationContext} for this web app, typically
     * loaded via {@link org.springframework.web.context.ContextLoaderListener}.
     * <p>Will rethrow an exception that happened on root context startup,
     * to differentiate between a failed context startup and no context at all.
     * @param sc the ServletContext to find the web application context for
     * @return the root WebApplicationContext for this web app, or {@code null} if none
     * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
     */
    @Nullable
    public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    	return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }

只有一个处理,点进去看看:

	/**
	 * Find a custom {@code WebApplicationContext} for this web app.
	 * @param sc the ServletContext to find the web application context for
	 * @param attrName the name of the ServletContext attribute to look for
	 * @return the desired WebApplicationContext for this web app, or {@code null} if none
	 */
	@Nullable
	public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
		Assert.notNull(sc, "ServletContext must not be null");
		Object attr = sc.getAttribute(attrName);
		if (attr == null) {
			return null;
		}
		if (attr instanceof RuntimeException) {
			throw (RuntimeException) attr;
		}
		if (attr instanceof Error) {
			throw (Error) attr;
		}
		if (attr instanceof Exception) {
			throw new IllegalStateException((Exception) attr);
		}
		if (!(attr instanceof WebApplicationContext)) {
			throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
		}
		return (WebApplicationContext) attr;
	}

直接从ServletContext里获取了一个key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的属性,这个常量的值是什么呢:

	/**
	 * Context attribute to bind root WebApplicationContext to on successful startup.
	 * <p>Note: If the startup of the root context fails, this attribute can contain
	 * an exception or error as value. Use WebApplicationContextUtils for convenient
	 * lookup of the root WebApplicationContext.
	 * @see org.springframework.web.context.support.WebApplicationContextUtils#getWebApplicationContext
	 * @see org.springframework.web.context.support.WebApplicationContextUtils#getRequiredWebApplicationContext
	 */
	String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

留意一下注释,这个attribute在startUp成功的时候被绑定在root WebApplicationContext上。接着看,如果root context的startup处理失败,这个属性就包含了一个异常或者错误的信息。那我们就可以推断,这个startup指的就是root context。前面我们整理Spring MVC的上下文结构的时候提到,上下文分为root context和servlet context,root context包含了一些基础服务、数据库连接等,而servlet context则包含了controller、service等。而上面代码在获取的时候是直接从servletContext中获取的,那就可以推断,在SpringMVC中,root context等同于servletContext,而servlet context就是Spring容器的WebApplicationContext。再回到上面的问题,在root startup的时候进行绑定,那我们去找找servletContext的startUp处理:

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	/**
	 * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
	 * implementations present on the application classpath.
	 * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
	 * Servlet 3.0+ containers will automatically scan the classpath for implementations
	 * of Spring's {@code WebApplicationInitializer} interface and provide the set of all
	 * such types to the {@code webAppInitializerClasses} parameter of this method.
	 * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
	 * this method is effectively a no-op. An INFO-level log message will be issued notifying
	 * the user that the {@code ServletContainerInitializer} has indeed been invoked but that
	 * no {@code WebApplicationInitializer} implementations were found.
	 * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
	 * they will be instantiated (and <em>sorted</em> if the @{@link
	 * org.springframework.core.annotation.Order @Order} annotation is present or
	 * the {@link org.springframework.core.Ordered Ordered} interface has been
	 * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
	 * method will be invoked on each instance, delegating the {@code ServletContext} such
	 * that each instance may register and configure servlets such as Spring's
	 * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
	 * or any other Servlet API componentry such as filters.
	 * @param webAppInitializerClasses all implementations of
	 * {@link WebApplicationInitializer} found on the application classpath
	 * @param servletContext the servlet context to be initialized
	 * @see WebApplicationInitializer#onStartup(ServletContext)
	 * @see AnnotationAwareOrderComparator
	 */
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}
}

servlet容器启动的时候会调用上面的onStartup方法,遍历实例化的WebApplicationInitializer,然后调用WebApplicationInitializer的onStartup方法。这个实例化的WebApplicationInitializer是由我们来创建的,里面可以自定义处理,如:

/**
 * @Author: kuromaru
 * @Date: Created in 13:56 2019/8/5
 * @Description:
 * @modified:
 */
public class WebMvcInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(WebMvcConfig.class);
        context.setServletContext(servletContext);

        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
        registration.addMapping("/");
        registration.setLoadOnStartup(1);

    }
}

以上就是Servlet启动的一些处理,Servlet还提供了一个监听器,用于监听servlet容器的创建和销毁:

public interface ServletContextListener extends EventListener {

    /**
     * Receives notification that the web application initialization
     * process is starting.
     *
     * <p>All ServletContextListeners are notified of context
     * initialization before any filters or servlets in the web
     * application are initialized.
     *
     * @param sce the ServletContextEvent containing the ServletContext
     * that is being initialized
     *
     * @implSpec
     * The default implementation takes no action.
     */
    default public void contextInitialized(ServletContextEvent sce) {}

    /**
     * Receives notification that the ServletContext is about to be
     * shut down.
     *
     * <p>All servlets and filters will have been destroyed before any
     * ServletContextListeners are notified of context
     * destruction.
     *
     * @param sce the ServletContextEvent containing the ServletContext
     * that is being destroyed
     *
     * @implSpec
     * The default implementation takes no action.
     */
    default public void contextDestroyed(ServletContextEvent sce) {}
}

Spring提供了这个接口的实现类ContextLoaderListener,这个类大家应该很熟悉,我们在配置web.xml的时候,经常会配一个listener:

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

当我们配置了这个listener,servletContext被加载的时候就会触发相应的方法去加载ApplicationContext:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	/**
	 * Create a new {@code ContextLoaderListener} that will create a web application
	 * context based on the "contextClass" and "contextConfigLocation" servlet
	 * context-params. See {@link ContextLoader} superclass documentation for details on
	 * default values for each.
	 * <p>This constructor is typically used when declaring {@code ContextLoaderListener}
	 * as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
	 * required.
	 * <p>The created application context will be registered into the ServletContext under
	 * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
	 * and the Spring application context will be closed when the {@link #contextDestroyed}
	 * lifecycle method is invoked on this listener.
	 * @see ContextLoader
	 * @see #ContextLoaderListener(WebApplicationContext)
	 * @see #contextInitialized(ServletContextEvent)
	 * @see #contextDestroyed(ServletContextEvent)
	 */
	public ContextLoaderListener() {
	}

	/**
	 * Create a new {@code ContextLoaderListener} with the given application context. This
	 * constructor is useful in Servlet 3.0+ environments where instance-based
	 * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
	 * API.
	 * <p>The context may or may not yet be {@linkplain
	 * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
	 * (a) is an implementation of {@link ConfigurableWebApplicationContext} and
	 * (b) has <strong>not</strong> already been refreshed (the recommended approach),
	 * then the following will occur:
	 * <ul>
	 * <li>If the given context has not already been assigned an {@linkplain
	 * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
	 * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
	 * the application context</li>
	 * <li>{@link #customizeContext} will be called</li>
	 * <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer org.springframework.context.ApplicationContextInitializer ApplicationContextInitializers}
	 * specified through the "contextInitializerClasses" init-param will be applied.</li>
	 * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
	 * </ul>
	 * If the context has already been refreshed or does not implement
	 * {@code ConfigurableWebApplicationContext}, none of the above will occur under the
	 * assumption that the user has performed these actions (or not) per his or her
	 * specific needs.
	 * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
	 * <p>In any case, the given application context will be registered into the
	 * ServletContext under the attribute name {@link
	 * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
	 * application context will be closed when the {@link #contextDestroyed} lifecycle
	 * method is invoked on this listener.
	 * @param context the application context to manage
	 * @see #contextInitialized(ServletContextEvent)
	 * @see #contextDestroyed(ServletContextEvent)
	 */
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}


	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}


	/**
	 * Close the root web application context.
	 */
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}

}

这里我们就可以看到,在servlet容器初始化的时候,会触发contextinitialized处理,在这个处理里面,通过initWebApplicationContext方法初始化WebApplicationContext。这个方法被定义在父类ContextLoader中,点进去看看:

	/**
	 * Initialize Spring's web application context for the given servlet context,
	 * using the application context provided at construction time, or creating a new one
	 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
	 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
	 * @param servletContext current servlet context
	 * @return the new WebApplicationContext
	 * @see #ContextLoader(WebApplicationContext)
	 * @see #CONTEXT_CLASS_PARAM
	 * @see #CONFIG_LOCATION_PARAM
	 */
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}

		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				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 ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}

可以看到,在这个方法里设置了WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性。但是我们自己的WebMvcInitializer中,并没有配置listener,所以,这里获取到的rootContext其实是个null。如果想要注册ContextLoaderListener,可以这么写:

/**
 * @Author: kuromaru
 * @Date: Created in 13:56 2019/8/5
 * @Description:
 * @modified:
 */
public class WebMvcInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(WebMvcConfig.class);
        context.setServletContext(servletContext);

        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
        registration.addMapping("/");
        registration.setLoadOnStartup(1);

        servletContext.addListener(new ContextLoaderListener());

    }
}

在处理的最后我们添加了ContextLoaderListener,此时,就会将WebApplicationContext绑定到ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE上(需要配置applicationContext.xml,否则会报错找不到applicationContext.xml)。
好了,我们已经清楚了ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性的配置,那么,回到initWebApplicationContext方法,我们能获取到rootContext就是null。接着往下看:

	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
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			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.
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

获取完rootContext,会去判断this.webApplicationContext是否为null,这个this.webApplicationContext就是在创建DispatcherServlet的时候传进去的AnnotationConfigWebApplicationContext的实例:

public class WebMvcInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(WebMvcConfig.class);
        context.setServletContext(servletContext);

        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
        registration.addMapping("/");
        registration.setLoadOnStartup(1);
    }
}

spring容器启动的时候,会先去创建一个BeanFactory,在创建BeanFactory的时候,会尝试将handler注入,并与url绑定。容器启动调用refresh方法时,会触发刷新事件,这个刷新事件是FrameServlet中注册的,在onApplicationEvent方法中会调用DispatcherServlet的onRefresh方法,这个方法里会初始化HandlerMapping,如果没有,则通过DispathcerServlet.properties中的配置创建一个,注册到容器中。

这里还要说一下Handler,我之前对Handler的理解是Handler即是Controller,后面发现这个理解太片面了,Controller是特殊的Handler,但是Handler并不全是Controller,还有如HttpRequestHandler之类的,我们也可以自定义Handler。可以这么理解,Spring MVC默认将Controller当成Handler来处理。

在SpringMVC里,默认会把Controller做为handler,但是handler并不全是controller,Spring启动的时候,会尝试通过WebMvcConfigurationSupport去完成配置项,在这一步处理中,会尝试注入RequestMappingHandlerMapping类型的实例,而这个RequestMappingHandlerMapping继承了AbstractHandlerMethodMapping类,父类AbstractHandlerMethodMapping又实现了InitializerBean接口,实现了afterProperitesSet方法,在方法里尝试去初始化HandlerMethod。首先,还是要遍历所有容器托管的Bean,然后对于名称不是“scopedTarget.”开头的,都去判断一下是否是Handler,而判断是否为Handler的标准就是是否是以@Controller或者@RequestMapping注解的。如果是以这两个的任意一个注解的类,则去获取类中被@RequestMapping注解的方法,并通过注解的参数(如value、methode)生成一个RequestMappingInfo,从而将uri和方法对应起来。然后,会使用handler(即controller)和Method(反射)注册一个HandlerMethod,然后以RequestMappingInfo为key,HandlerMethod为value保存到名为mappingLookup的map中。并且将此RequestMappingInfo对应的所有uri遍历出来,以uri为key,RequestMappingInfo为value,保存在名为urlLookUp的map中。同时,会根据Handler和HandlerMehod的名称,生成一个方法的摘要名,如WebController的hello()方法,生成的名称即为WC#hello,并以此为key,HandlerMethod为value,存入名为nameLookUp的Map中。
HandlerMapping的初始化在HandlerMethod之后进行。那么当HandlerMapping初始化的时候,就可以查询到两个HandlerMapping:BeanNameUrlHandlerMapping和RequestMappingHandlerMapping。

这里看到几个比较实用的工具类,在这标注一下:AnnotatedElementUtils、BeanFactoryUtils、AopUtils

好了,handlerMapping的相关处理我们都已经整理好了,继续看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() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}

遍历获取到的HandlerMapping,然后通过getHandler方法获取一个HandlerExecutionChain。这个getHandler方法是HandlerMapping接口里声明的接口方法,在AbstractHandlerMapping抽象类里实现了这个方法。一起来看一下。

	/**
	 * Look up a handler for the given request, falling back to the default
	 * handler if no specific one is found.
	 * @param request current HTTP request
	 * @return the corresponding handler instance, or the default handler
	 * @see #getHandlerInternal
	 */
	@Override
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		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);
		}

		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;
	}

注意最开始的getHandlerInternal方法,这个方法被声明在AbstractHandlerMapping抽象类中,是一个抽象方法,它被AbstractHandlerMethodMapping和AbstractUrlHandlerMapping实现。根据类名,我们也很好推测,AbstractHandlerMethodMapping是RequestMappingHandlerMapping的父类,而AbstractUrlHandlerMapping则是BeanNameUrlHandlerMapping的父类,因此,在调用此方法时,根据实例的类型,调用不同的方法实现。如果是RequestMappingHandlerMapping,则去urlLookUp中查找对应的RequestMappingInfo,然后再去mappingLookup中根据RequestMappingInfo去查找对应的Handler;如果是BeanNameUrlHandlerMapping,则直接去handlerMap中查找对应的handler。

如果最终找不到对应的handlerMap,则会抛出404异常。

找到Handler以后,则通过Handler和Request生成HandlerExecutionChain,其中就把uri对应的Interceptor,并挂载到执行链中。这里的Interceptor集合的获取比较麻烦,先是通过WebMvcConfigurationSupport里注入各个HandlerMapping时绑定interceptor,然后在初始化interceptor的时候,通过下面的方法

BeanFactoryUtils.beansOfTypeIncludingAncestors(
						obtainApplicationContext(), MappedInterceptor.class, true, false).values()

来查找,也就是查找容器里所有的MappedInterceptor类型的实例,然后把两部分interceptor合并在一起。

至此,我们获取到了mappedHandler。那么在doDispatch的主逻辑当中,获取完mappedHandler以后,紧接着就是要获取一个HandlerAdapter。

/**
 * Return the HandlerAdapter for this handler object.
 * @param handler the handler object to find an adapter for
 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
 */
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(handler)) {
				return adapter;
			}
		}
	}
	throw new ServletException("No adapter for handler [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

跟获取HandlerExecutionChain类似,这个也是遍历了一个叫handlerAdapter的属性。还是来看下一下这个属性是在哪里设值的。之前在整理handlerMapping的时候,我们看到有一个initStrategies方法,这里面做了一系列的初始化工作,handlerAdapter的初始化也在这个方法里被执行:

/**
	 * Initialize the HandlerAdapters used by this class.
	 * <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
	 * we default to SimpleControllerHandlerAdapter.
	 */
	private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;

		if (this.detectAllHandlerAdapters) {
			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerAdapters = new ArrayList<>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerAdapters);
			}
		}
		else {
			try {
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerAdapter later.
			}
		}

		// Ensure we have at least some HandlerAdapters, by registering
		// default HandlerAdapters if no other adapters are found.
		if (this.handlerAdapters == null) {
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

看起来眼熟吧~这个跟获取HandlerMapping的逻辑几乎一样,也是通过BeanFactoryUtils.beansOfTypeIncludingAncestors方法去容器里获取所有已注入的HandlerAdapter。那么容器里有哪些HandlerAdapter呢?前面我们说Spring MVC通过WebMvcConfigurationSupport来完成配置,那我们去这个里面找一下,看有没有。找之前,我们还得先看一下HandlerAdapter的实现类,有下面几个:

  • AbstractHandlerMethodAdapter
  • HttpRequestHandlerAdapter
  • RequestMappingHandlerAdapter
  • SimpleControllerHandlerAdapter
  • SimpleServletHandlerAdapter
    第一个是一个抽象类,这个先不考虑,第五个SimpleServletHandlerAdapter好像也没啥用,查了一下,并没有哪个地方调用,而且在注释中也说默认情况下不激活,暂时不考虑。那剩下的就还有三个,我们去WebMvcConfigurationSupport中找一下:
/**
 * Returns a {@link RequestMappingHandlerAdapter} for processing requests
 * through annotated controller methods. Consider overriding one of these
 * other more fine-grained methods:
 * <ul>
 * <li>{@link #addArgumentResolvers} for adding custom argument resolvers.
 * <li>{@link #addReturnValueHandlers} for adding custom return value handlers.
 * <li>{@link #configureMessageConverters} for adding custom message converters.
 * </ul>
 */
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
	RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
	adapter.setContentNegotiationManager(mvcContentNegotiationManager());
	adapter.setMessageConverters(getMessageConverters());
	adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
	adapter.setCustomArgumentResolvers(getArgumentResolvers());
	adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

	if (jackson2Present) {
		adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
		adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
	}

	AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
	configureAsyncSupport(configurer);
	if (configurer.getTaskExecutor() != null) {
		adapter.setTaskExecutor(configurer.getTaskExecutor());
	}
	if (configurer.getTimeout() != null) {
		adapter.setAsyncRequestTimeout(configurer.getTimeout());
	}
	adapter.setCallableInterceptors(configurer.getCallableInterceptors());
	adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

	return adapter;
}
	
	...
	
/**
 * Returns a {@link HttpRequestHandlerAdapter} for processing requests
 * with {@link HttpRequestHandler HttpRequestHandlers}.
 */
@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
	return new HttpRequestHandlerAdapter();
}

/**
 * Returns a {@link SimpleControllerHandlerAdapter} for processing requests
 * with interface-based controllers.
 */
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
	return new SimpleControllerHandlerAdapter();
}

...

如上,三个类的实例都有配置。RequestMappingHandlerAdapter和RequestMappingHandlerMapping的初始化套路也基本一样,而且RequestMappingHandlerAdapter也实现了InitializerBean,在对应的afterPropertiesSet方法中,初始化了ControllerAdvice ,然后对应的argumentResolver、initBinderArgumentResolvers和returnValueHandlers也做了初始化。这里简单介绍一下ControllerAdvice,字面意思是Controller的增强,一个被@ControllerAdvice注解的类,可以使用@ExceptionHandler、@InitBinder和@ModeAttribute来注解方法。详细的后面再介绍。

接着看doDispatch的逻辑

/**
 * Return the HandlerAdapter for this handler object.
 * @param handler the handler object to find an adapter for
 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
 */
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(handler)) {
				return adapter;
			}
		}
	}
	throw new ServletException("No adapter for handler [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

取得所有的handlerAdapter,然后遍历判断是否适用于当前的handler,如果handler是Controller类型的话,则适用SimpleControllerHandlerAdapter;如果handler是HttpRequestHandler,则适用HttpRequestHandlerAdapter;如果是HandlerMethod,则适用RequestMappingHandlerAdapter。

OK,到此为止,HandlerMapping、HandlerMethod以及HandlerAdaptor都基本整理完了。

原文地址:https://www.cnblogs.com/kuromaru/p/12935571.html