摘要

Spring Web MVC是基于Servlet API构建的原始Web框架,基于servlet3.0的规范实现的web框架。spring mvc主要实现请求的接受,请求解析,请求响应等步骤。本文将详细的介绍spring MVC的源码和spring MVC的启动流程与相关原理。

Spring——spring MVC源码分析与实战_初始化

一、传统spring MVC执行流程

Spring——spring MVC源码分析与实战_初始化_02

  • 用户发送请求至前端控制器DispatcherServlet;
  • DispatcherServlet收到请求后,调用处理器映射器HandlerMapping,请求获取Handle;
  • 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给前端控制器DispatcherServlet;
  • DispatcherServlet通过处理器适配器HandlerAdapte调用处理器Handle;
  • 执行处理器(Handler,也叫后端控制器,需要程序员做处理);
  • 处理器Handler执行完成返回ModelAndView;
  • 处理器适配器HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
  • DispatcherServlet将ModelAndView传给视图解析器ViewReslover进行解析;
  • 视图解析器ViewReslover解析后返回具体View;
  • DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
  • DispatcherServlet响应用户。

上面就是一个传统的完整的 Spring MVC 流程,为什么要说这是传统的流程呢?因为这个流程是用于前后端没有分离的时候,后台直接返回页面给浏览器进行渲染,而现在大部分应用都是前后端分离,后台直接生成一个 Json 字符串就直接返回前端,不需要经过视图解析器进行处理。

二、前后端分离的spring MVC执行流程

Spring MVC主要可以分为两大过程,一是初始化,二就是处理请求。初始化的过程主要就是将我们定义好的 RequestMapping 映射路径和 Controller 中的方法进行一一映射存储,这样当收到请求之后就可以处理请求调用对应的方法,从而响应请求。

2.1 spring mvc的初始化

初始化:初始化过程的入口方法是 DispatchServletinit() 方法,而实际上 DispatchServlet 中并没有这个方法,所以我们就继续寻找父类,会发现 init 方法在其父类(FrameworkServlet)的父类 HttpServletBean 中。

Spring——spring MVC源码分析与实战_初始化_03

HttpServletBean:init()方法

在这个方法中,首先会去家在一些 Servlet 相关配置(web.xml),然后会调用 initServletBean() 方法,这个方法是一个空的模板方法,业务逻辑由子类 FrameworkServlet 来实现。

public final void init() throws ServletException {
        PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                // 定义资源
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                // 加载servlet相关配置
                ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
                this.initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            } catch (BeansException var4) {
                if (this.logger.isErrorEnabled()) {
                    this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
                }

                throw var4;
            }
        }
        // 核心方法 由子类FrameworkServlet实现
        this.initServletBean();
    }

FrameworkServlet-initServletBean()

这个方法本身没有什么业务逻辑,主要是初始化 WebApplicationContext 对象,WebApplicationContext 继承自 ApplicationContext,主要是用来处理 web 应用的上下文。

/**
	 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
	 * have been set. Creates this servlet's WebApplicationContext.
	 * {@link HttpServletBean} 的重写方法,在设置任何 bean 属性后调用。创建此 servlet 的 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-initWebApplicationContext()

initWebApplicationContext() 方法主要就是为了找到一个上下文,找不到就会创建一个上下文,创建之后,最终会调用方法configureAndRefreshWebApplicationContext(cwac) 方法,而这个方法最终在设置一些基本容器标识信息之后会去调用 refresh() 方法。这个就是初始化IOC容器。

protected WebApplicationContext initWebApplicationContext() {
		// 从serlvetContent 中获取父类容器 webApplicationContext()
		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);
					}
					// 调用ioc容器的refresh()方法来初始化ioc信息
					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;
	}

当调用 refresh() 方法初始化 ioc 容器之后,最终会调用方法 onRefresh(),这个方法也是一个模板钩子方法,由子类实现,也就是回到了我们 Spring MVC 的入口类 DispatcherServlet

DispatchServlet-onRefresh()

onRefresh() 方法就是 Spring MVC 初始化的最后一个步骤,在这个步骤当中会初始化 Spring MVC 流程中可能需要使用到的九大组件。

*/
	@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);// 初始化模板处理器
		initHandlerMappings(context);// 初始化 HandlerMappings
		initHandlerAdapters(context);// 初始化HandlerAdapters
		initHandlerExceptionResolvers(context);// 初始化异常拦截器
		initRequestToViewNameTranslator(context);// 初始化视图预处理器
		initViewResolvers(context);// 初始化视图转化器
		initFlashMapManager(context);// 解决用户重的定向是参数丢失问题
	}

2.1.1 spring MVC的九大组件

MultipartResolver:这个组件比较熟悉,主要就是用来处理文件上传请求,通过将普通的 Request 对象包装成 MultipartHttpServletRequest 对象来进行处理。

LocaleResolverLocaleResolver 用于初始化本地语言环境,其从 Request 对象中解析出当前所处的语言环境,如中国大陆则会解析出 zh-CN 等等,模板解析以及国际化的时候都会用到本地语言环境。

ThemeResolver:这个主要是用户主题解析,在 Spring MVC 中,一套主题对应一个 .properties 文件,可以存放和当前主题相关的所有资源,如图片,css样式等。

HandlerMapping:用于查找处理器(Handler),比如我们 Controller 中的方法,这个其实最主要就是用来存储 url 和 调用方法的映射关系,存储好映射关系之后,后续有请求进来,就可以知道调用哪个 Controller 中的哪个方法,以及方法的参数是哪些。

HandlerAdapter:这是一个适配器,因为 Spring MVC 中支持很多种 Handler,但是最终将请求交给 Servlet 时,只能是 doService(req,resp) 形式,所以 HandlerAdapter 就是用来适配转换格式的。

HandlerExceptionResolver:这个组件主要是用来处理异常,不过看名字也很明显,这个只会对处理 Handler 时产生的异常进行处理,然后会根据异常设置对应的 ModelAndView,然后交给 Render 渲染成页面。

RequestToViewNameTranslator:这个主键主要是从 Request 中获取到视图名称。

ViewResolver:这个组件会依赖于 RequestToViewNameTranslator 组件获取到的视图名称,因为视图名称是字符串格式,所以这里会将字符串格式的视图名称转换成为 View 类型视图,最终经过一系列解析和变量替换等操作返回一个页面到前端。

FlashMapManager:这个主键主要是用来管理 FlashMap,那么 FlashMap 又有什么用呢?要明白这个那就不得不提到重定向了,有时候我们提交一个请求的时候会需要重定向,那么假如参数过多或者说我们不想把参数拼接到 url 上(比如敏感数据之类的),这时候怎么办呢?因为参数不拼接在 url 上重定向是无法携带参数的。FlashMap 就是为了解决这个问题,我们可以在请求发生重定向之前,将参数写入 request 的属性 OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的 handler 中,Spring 会自动将其设置到 Model 中,这样就可以从 Model 中取到我们传递的参数了。

2.2 spring mvc的请求处理

在九大组件初始化完成之后,Spring MVC的初始化就完成了,接下来就是接收并处理请求了,那么处理请求的入口在哪里呢?处理请求的入口方法就是 DispatcherServlet 中的 doService 方法,而 doService 方法又会调用 doDispatch 方法。

@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		logRequest(request);

		// 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<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					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());

		if (this.flashMapManager != null) {
			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);
		}

		RequestPath previousRequestPath = null;
		if (this.parseRequestPath) {
			previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
			ServletRequestPathUtils.parseAndCache(request);
		}

		try {
			// 核心的方法
			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);
				}
			}
			if (this.parseRequestPath) {
				ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
			}
		}
	}
@SuppressWarnings("deprecation")
	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.
				//获取处理器,也就是controller中的方法
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == 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 = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					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);
				}
			}
		}
	}

这个方法最关键的就是调用了 getHandler 方法,这个方法就是会获取到前面九大组件中的 HandlerMapping,然后进行反射调用对应的方法完成请求,完成请求之后后续还会经过视图转换之类的一些操作,最终返回 ModelAndView,不过现在都是前后端分离,基本也不需要用到视图模型,在这里我们就不分析后续过程,主要就是分析 HandlerMapping 的初始化和查询过程。

DispatcherServlet-getHandler()

这个方法里面会遍历 handllerMappings,这个 handllerMappings 是一个 List 集合,因为 HandlerMapping 有多重实现,也就是 HandlerMapping 不止一个实现,其最常用的两个实现为 RequestMappingHandlerMappingBeanNameUrlHandlerMapping

@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			// 遍历的map 中的Handler处理  HandlerMapping 中有多重实现 多重类型
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

AbstractHandlerMapping-getHandler()

AbstractHandlerMapping 是一个抽象类,其 getHandlerInternal 这个方法也是一个模板方法:

@Nullable
	public Object getDefaultHandler() {
		return this.defaultHandler;
	}

getHandlerInternal 方法最终其会调用子类实现,而这里的子类实现会有多个,其中最主要的就是 AbstractHandlerMethodMappingAbstractUrlHandlerMapping 两个抽象类,那么最终到底会调用哪个实现类呢?这时候如果拿捏不准我们就可以看一下类图,上面我们提到,HandlerMapper 有两个非常主要的实现类:RequestMappingHandlerMappingBeanNameUrlHandlerMapping。那么我们就分别来看一下这两个类的类图关系:

Spring——spring MVC源码分析与实战_初始化_04

Spring——spring MVC源码分析与实战_初始化_05

可以看到,这两个实现类的抽象父类正好对应了 AbstractHandlerMapping 的两个子类,所以这时候具体看哪个方法,那就看我们想看哪种类型了。

  • RequestMappingHandlerMapping:主要用来存储 RequestMapping 注解相关的控制器和 url 的映射关系。
  • BeanNameUrlHandlerMapping:主要用来处理 Bean name 直接以 / 开头的控制器和 url 的映射关系。

其实除了这两种 HandlerMapping 之外,Spring 中还有其他一些 HandllerMapping,如 SimpleUrlHandlerMapping 等。提到的这几种 HandlerMapping,对我们来说最常用,最熟悉的那肯定就是 RequestMappingHandlerMapping ,在这里我们就以这个为例来进行分析。

AbstractHandlerMethodMapping-getHandlerInternal()

这个方法本身也没有什么逻辑,其主要的核心查找 Handler 逻辑在 lookupHandlerMethod 方法中,这个方法主要是为了获取一个 HandlerMethod 对象,前面的方法都是 Object,而到这里变成了 HandlerMethod 类型,这是因为 Handler 有各种类型,目前我们已经基本跟到了具体类型之下,所以类型就变成了具体类型,而如果我们看的的另一条分支线,那么返回的就会是其他对象,正是因为支持多种不同类型的 HandlerMapping 对象,所以最终为了统一执行,才会需要在获得 Hanlder 之后,DispatcherServlet 中会再次通过调用 getHandlerAdapter 方法来进一步封装成 HandlerAdapter 对象,才能进行方法的调用。

/**
	 * Look up a handler method for the given request.
	 * 查找给定请求的处理程序方法。
	 */
	@Override
	@Nullable
	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = initLookupPath(request);// 查找请求的路径
		this.mappingRegistry.acquireReadLock();
		try {
			// 寻找handler
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}

AbstractHandlerMethodMapping-lookupHandlerMethod()

这个方法主要会从 mappingRegistry 中获取命中的方法,获取之后还会经过一系列的判断比较判断比较,因为有些 url 会对应多个方法,而方法的请求类型不同,比如一个 GET 方法,一个 POST 方法,或者其他一些属性不相同等等,都会导致最终命中到不同的方法,这些逻辑主要都是在addMatchingMappings 方法去进一步实现,并最终将命中的结果加入到 matches 集合内。

@Nullable
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
		List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
		if (directPathMatches != null) {
			// 找到合适的Mapping 这一步进一步根据属性查找准确的Hadnler
			addMatchingMappings(directPathMatches, matches, request);
		}
		if (matches.isEmpty()) {
			// 如果没有找打 那就遍历mapping中的HandlerMapping
			addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
		}
		if (!matches.isEmpty()) {
			Match bestMatch = matches.get(0);
			if (matches.size() > 1) {
				Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
				matches.sort(comparator);
				bestMatch = matches.get(0);
				//将HandlerMethod进一步进行包装
				if (logger.isTraceEnabled()) {
					logger.trace(matches.size() + " matching mappings: " + matches);
				}
				if (CorsUtils.isPreFlightRequest(request)) {
					for (Match match : matches) {
						if (match.hasCorsConfig()) {
							return PREFLIGHT_AMBIGUOUS_MATCH;
						}
					}
				}
				else {
					// 获取第二个 Handler 进行进一步的比较。
					Match secondBestMatch = matches.get(1);
					if (comparator.compare(bestMatch, secondBestMatch) == 0) {
						Method m1 = bestMatch.getHandlerMethod().getMethod();
						Method m2 = secondBestMatch.getHandlerMethod().getMethod();
						String uri = request.getRequestURI();
						throw new IllegalStateException(
								"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
					}
				}
			}
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.getHandlerMethod();
		}
		else {
			return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
		}
	}

在这个方法中,有一个对象非常关键,那就是 mappingRegistry,因为最终我们根据url到这里获取到对应的 HandlerMtthod,所以这个对象很关键:

class MappingRegistry {

		private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

		private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();

		private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

		private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

		private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

		/**
		 * Return all registrations.
		 * @since 5.3
		 */
		public Map<T, MappingRegistration<T>> getRegistrations() {
			return this.registry;
		}

		/**
		 * Return matches for the given URL path. Not thread-safe.
		 * @see #acquireReadLock()
		 */
		@Nullable
		public List<T> getMappingsByDirectPath(String urlPath) {
			return this.pathLookup.get(urlPath);
		}

		/**
		 * Return handler methods by mapping name. Thread-safe for concurrent use.
		 */
		public List<HandlerMethod> getHandlerMethodsByMappingName(String mappingName) {
			return this.nameLookup.get(mappingName);
		}

		/**
		 * Return CORS configuration. Thread-safe for concurrent use.
		 */
		@Nullable
		public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
			HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
			return this.corsLookup.get(original != null ? original : handlerMethod);
		}

看这个对象其实很明显可以看出来,这个对象其实只是维护了一些 Map对象,所以我们可以很容易猜测到,一定在某一个地方,将 urlHandlerMapping 或者 HandlerMethod 的映射关系存进来了,这时候其实我们可以根据 getMappingsByUrl 方法来进行反推,看看 urlLookup 这个 Map 是什么时候被存入的,结合上面的类图关系,一路反推,很容易就可以找到这个 Map 中的映射关系是 AbstractHandlerMethodMapping 对象的 afterPropertiesSet 方法实现的。(AbstractHandlerMethodMapping 实现了 InitializingBean 接口),也就是当这个对象初始化完成之后,我们的 urlHandler 映射关系已经存入了 MappingRegistry 对象中的集合 Map 中。

AbstractHandlerMethodMapping 的初始化

afterPropertiesSet 方法中并没有任何逻辑,而是直接调用了 initHandlerMethods

AbstractHandlerMethodMapping-initHandlerMethods()

initHandlerMethods 方法中,首先还是会从 Spring 的上下文中获取所有的 Bean,然后会进一步从带有 RequestMapping 注解和 Controller 注解中的 Bean 去解析并获得 HandlerMethod

/**
	 * Scan beans in the ApplicationContext, detect and register handler methods.
	 * 扫描 ApplicationContext 中的 bean,检测并注册 handler方法。
	 * @see #getCandidateBeanNames()
	 * @see #processCandidateBean
	 * @see #handlerMethodsInitialized
	 */
	protected void initHandlerMethods() {
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				processCandidateBean(beanName);
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

AbstractHandlerMethodMapping-detectHandlerMethods()

这个方法中,其实就是通过反射获取到 Controller 中的所有方法,然后调用 registerHandlerMethod 方法将相关信息注册到 MappingRegistry 对象中的各种 Map 集合之内:

/**
	 * 在指定的处理程序 bean 中查找处理程序方法
	 * @param handler either a bean name or an actual handler instance
	 * @see #getMappingForMethod
	 */
	protected void detectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
							return getMappingForMethod(method, userType);
						}
						catch (Throwable ex) {
							throw new IllegalStateException("Invalid mapping on handler class [" +
									userType.getName() + "]: " + method, ex);
						}
					});
			if (logger.isTraceEnabled()) {
				logger.trace(formatMappings(userType, methods));
			}
			else if (mappingsLogger.isDebugEnabled()) {
				mappingsLogger.debug(formatMappings(userType, methods));
			}
			// 遍历Controller中所有的方法
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				// 注册HandlerMethod
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

AbstractHandlerMethodMapping-register()

registerHandlerMethod 方法中会直接调用 AbstractHandlerMethodMapping 对象持有的 mappingRegistry 对象中的 regidter 方法,这里会对 Controller 中方法上的一些元信息进行各种解析,比如参数,路径,请求方式等等,然后会将各种信息注册到对应的 Map 集合中,最终完成了整个初始化。

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
		this.mappingRegistry.register(mapping, handler, method);
	}
public void register(T mapping, Object handler, Method method) {
			this.readWriteLock.writeLock().lock();
			try {
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				validateMethodMapping(handlerMethod, mapping);

				Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
				for (String path : directPaths) {
					this.pathLookup.add(path, mapping);
				}

				String name = null;
				if (getNamingStrategy() != null) {
					name = getNamingStrategy().getName(handlerMethod, mapping);
					addMappingName(name, handlerMethod);
				}

				CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
					corsConfig.validateAllowCredentials();
					this.corsLookup.put(handlerMethod, corsConfig);
				}

				this.registry.put(mapping,
						new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

三、spring mvc中适配器设计模式

3.1 适配器模式定义

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。

Spring——spring MVC源码分析与实战_spring_06

适配器模式包含了几个角色,它们是:

Target(目标角色):该角色定义其他类转化成何种接口,可以是一个抽象类或接口,也可以是具体类。

Adaptee(源角色):你想把谁转换成目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象。

Adapter(适配器角色):适配器模式的核心角色,职责就是通过继承或是类关联的方式把源角色转换为目标角色。

3.2 类适配器

首先有一个已存在的将被适配的类

public class Adaptee {
    public void adapteeRequest() {
        System.out.println("被适配者的方法");
    }
}

定义一个目标接口

public interface Target {
    void request();
}

怎么才可以在目标接口中的 request() 调用 Adaptee 的 adapteeRequest() 方法呢?

public class ConcreteTarget implements Target {
    @Override
    public void request() {
        System.out.println("concreteTarget目标方法");
    }
}

如果直接实现 Target 是不行的?

如果通过一个适配器类,实现 Target 接口,同时继承了 Adaptee 类,然后在实现的 request() 方法中调用父类的 adapteeRequest() 即可实现

public class Adapter extends Adaptee implements Target{
    @Override
    public void request() {
        //...一些操作...
        super.adapteeRequest();
        //...一些操作...
    }
}
我们来测试一下
public class Test {
    public static void main(String[] args) {
        Target target = new ConcreteTarget();
        target.request();

        Target adapterTarget = new Adapter();
        adapterTarget.request();
    }
}

代码输出

concreteTarget目标方法
被适配者的方法

Spring——spring MVC源码分析与实战_初始化_07

 这样我们即可在新接口 Target 中适配旧的接口或类。

3.3 对象适配器

对象适配器与类适配器不同之处在于,类适配器通过继承来完成适配,对象适配器则是通过关联来完成,这里稍微修改一下 Adapter 类即可将转变为对象适配器。

public class Adapter implements Target{
    // 适配者是对象适配器的一个属性
    private Adaptee adaptee = new Adaptee();

    @Override
    public void request() {
        //...
        adaptee.adapteeRequest();
        //...
    }
}

Spring——spring MVC源码分析与实战_MVC_08

注意这里的 Adapter 是将 Adaptee 作为一个成员属性,而不是继承它。

电压适配器:我们国家的民用电都是 220V,日本是 110V,而我们的手机充电一般需要 5V,这时候要充电,就需要一个电压适配器,将 220V 或者 100V 的输入电压变换为 5V 输出

定义输出交流电接口,输出220V交流电类和输出110V交流电类。

public interface AC {
    int outputAC();
}

public class AC110 implements AC {
    public final int output = 110;

    @Override
    public int outputAC() {
        return output;
    }
}

public class AC220 implements AC {
    public final int output = 220;

    @Override
    public int outputAC() {
        return output;
    }
}

适配器接口,其中 support() 方法用于检查输入的电压是否与适配器匹配,outputDC5V() 方法则用于将输入的电压变换为 5V 后输出。

public interface DC5Adapter {
    boolean support(AC ac);

    int outputDC5V(AC ac);
}
public class ChinaPowerAdapter implements DC5Adapter {
    public static final int voltage = 220;
    
    @Override
    public boolean support(AC ac) {
        return (voltage == ac.outputAC());
    }
    
    @Override
    public int outputDC5V(AC ac) {
        int adapterInput = ac.outputAC();
        //变压器...
        int adapterOutput = adapterInput / 44;
        System.out.println("使用ChinaPowerAdapter变压适配器,输入AC:" + adapterInput + "V" + ",输出DC:" + adapterOutput + "V");
        return adapterOutput;
    }
}

public class JapanPowerAdapter implements DC5Adapter {
    public static final int voltage = 110;

    @Override
    public boolean support(AC ac) {
        return (voltage == ac.outputAC());
    }

    @Override
    public int outputDC5V(AC ac) {
        int adapterInput = ac.outputAC();
        //变压器...
        int adapterOutput = adapterInput / 22;
        System.out.println("使用JapanPowerAdapter变压适配器,输入AC:" + adapterInput + "V" + ",输出DC:" + adapterOutput + "V");
        return adapterOutput;
    }
}
public class Test {
    private List<DC5Adapter> adapters = new LinkedList<DC5Adapter>();

    public Test() {
        this.adapters.add(new ChinaPowerAdapter());
        this.adapters.add(new JapanPowerAdapter());
    }

    // 根据电压找合适的变压器
    public DC5Adapter getPowerAdapter(AC ac) {
        DC5Adapter adapter = null;
        for (DC5Adapter ad : this.adapters) {
            if (ad.support(ac)) {
                adapter = ad;
                break;
            }
        }
        if (adapter == null){
            throw new  IllegalArgumentException("没有找到合适的变压适配器");
        }
        return adapter;
    }

    public static void main(String[] args) {
        Test test = new Test();
        AC chinaAC = new AC220();
        DC5Adapter adapter = test.getPowerAdapter(chinaAC);
        adapter.outputDC5V(chinaAC);

        // 去日本旅游,电压是 110V
        AC japanAC = new AC110();
        adapter = test.getPowerAdapter(japanAC);
        adapter.outputDC5V(japanAC);
    }
}

输出

使用ChinaPowerAdapter变压适配器,输入AC:220V,输出DC:5V
使用JapanPowerAdapter变压适配器,输入AC:110V,输出DC:5V

3.2 spring mvc中有几种适配器?

使用getHandlerAdapter 获取对应的handler的具体的handlerAdapter。

HandlerAdapter的实现类有

  • HttpRequestHandlerAdapter (org.springframework.web.servlet.mvc):Http请求处理的适配器
  • SimpleServletHandlerAdapter (org.springframework.web.servlet.handler):实现了Httpservlet类采用的适配器。
  • HandlerFunctionAdapter (org.springframework.web.servlet.function.support)
  • CompositeHandlerAdapter (org.springframework.boot.actuate.autoconfigure.web.servlet)
  • AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
  • SimpleControllerHandlerAdapter (org.springframework.web.servlet.mvc):继承Controller方式所有使用的适配器
  • RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc):注解的方式的(@Controller的方式)采用的适配器

3.3 spring mvc中适配器模式

SpringMVC,这套框架可以帮助我们把前端的请求访问到后台对应的controller的方法上,然后再把处理结果返回给后端,它的底层其实就用到了适配器模式。SpringMVC中的适配器模式主要用于执行目标Controller中的请求处理方法。在它的底层处理中,DispatcherServlet作为用户,HandlerAdapter作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller作为需要适配的类。

为什么要在 Spring MVC 中使用适配器模式?那么SpringMVC是怎么处理的呢?

Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,那样每增加一个类型的Controller就需要使用增加一个if else判断instance of,这违反了设计模式的开闭原则 —— 对扩展开放,对修改关闭。

我们来简单看一下源码,首先是适配器接口HandlerAdapter,

public interface HandlerAdapter {
    boolean supports(Object var1);

    ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    long getLastModified(HttpServletRequest var1, Object var2);
}

该接口的适配器类对应着 Controller,每自定义一个Controller需要定义一个实现HandlerAdapter的适配器。举个例子,有一个适配器类HttpRequestHandlerAdapter ,该类就是实现了HandlerAdapter接口,这是它的源码:

public class HttpRequestHandlerAdapter implements HandlerAdapter {

   @Override
   public boolean supports(Object handler) {
      return (handler instanceof HttpRequestHandler);
   }

   @Override
   public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {

      ((HttpRequestHandler) handler).handleRequest(request, response);
      return null;
   }

   @Override
   public long getLastModified(HttpServletRequest request, Object handler) {
      if (handler instanceof LastModified) {
         return ((LastModified) handler).getLastModified(request);
      }
      return -1L;
   }

}

当Spring容器启动后,会将所有定义好的适配器对象存放在一个List集合中,当一个请求来临时,DispatcherServlet会通过handler的类型找到对应适配器,并将该适配器对象返回给用户,然后就可以统一通过适配器的handle方法来调用Controller中的用于处理请求的方法。

public class DispatcherServlet extends FrameworkServlet {

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	
		//.........................
		
		//找到匹配当前请求对应的适配器
		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;
				}
		}

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

        try {
        	// Actually invoke the handler.
        	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        }finally {
        	if (asyncManager.isConcurrentHandlingStarted()) {
        		return;
        	}
        }
	}
	// 遍历集合,找到合适的匹配器
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
				return ha;
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}
}

这样一来,所有的controller就都统一交给HandlerAdapter处理,免去了大量的 if-else 语句判断,同时增加controller类型只需增加一个适配器即可,不需要修改到Servlet的逻辑,符合开闭原则。

3.4 适配器模式总结

适配器模式主要优点

  1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  2. 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  3. 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
  4. 一个对象适配器可以把多个不同的适配者适配到同一个目标;
  5. 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

类适配器模式的缺点如下:

  1. 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
  2. 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
  3. 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

对象适配器模式的缺点如下:

  • 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

适用场景

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

博文参考

SpringMVC源码分析系列(精简)

SpringMVC源码解析

带你一步一步手撕Spring MVC源码加手绘流程图

SpringMVC源码解析