Spring MVC源码分析

一、SpringMVC概述

1.1 SpringMVC请求处理流程

在阅读源码之前先进行SpringMVC源码环境的搭建SpringMVC环境搭建

里面也对Controller控制器的几种实现方式,进行了简单说明,便于我们后续的理解。

下面先给出一张流程图,方便我们进行梳理。

DispatcherServlet:DispatcherServlet是SpringMVC中的前端控制器,负责接收request并将request转发给对应的处理组件。

HandlerMapping:HanlerMapping是SpringMVC中完成url到Controller映射的组件。

Handler:Handler处理器,其实就是我们的Controller,Controller是SpringMVC中负责处理request的组件。

ModelAndView:ModelAndView是封装结果视图的组件。

ViewResolver:ViewResolver视图解析器,解析ModelAndView对象,并返回对应的视图View给客户端。

处理流程:

1.首先,请求进入DispatcherServlet,由DispatcherServlet从HandlerMapping中提取对应的Handler。

2.此时,只是获取到了对应的Handler,然后得去寻找对应的适配器,即:HandlerAdapter。

3.拿到对应的HandlerAdapter后,开始调用对应的Handler处理业务逻辑,执行完成之后返回一个ModelAndView。

4.这时候交给我们的ViewResolver,通过视图名称查找对应的视图,然后返回。

5.最后,渲染视图,返回渲染后的视图,响应给客户端。

1.2 SpringMVC工作机制

在容器初始化时会建立所有url和controller的对应关系,保存到Map<url,controller>中。

tomcat启动时会通知spring初始化容器(加载bean的定义信息和初始化所有单例bean),然后springmvc会遍历容器中的bean,获取每一个controller中的所有方法访问的url,然后将url和Controller保存到一个Map中;

这样就可以根据request快速定位到Controller,因为最终处理request的是Controller中的方法,Map中只保留了url和Controller中的对应关系,所以要根据request的url进一步确认Controller中的method。

这一步工作的原理就是拼接Controller的url(Controller上@RequestMapping的值)和方法的url(method上@RequestMapping的值),与request的url进行匹配,找到匹配的那个方法;  

确定处理请求的method后,接下来的任务就是参数绑定,把request中参数绑定到方法的形式参数上,这一步是整个请求处理过程中最复杂的一个步骤。

SpringMVC提供了两种request参数与方法形参的绑定方法:

1.通过注解进行绑定 @RequestParam

使用注解进行绑定,我们只要在方法参数前面声明@RequestParam("userName"),就可以将request中参数a的值绑定到方法的该参数上。

2.通过参数名称进行绑定

使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法。

SpringMVC解决这个问题的方法是用asm框架读取字节码文件,来获取方法的参数名称。

asm框架是一个字节码操作框架,关于asm更多介绍可以参考它的官网。个人建议,使用注解来完成参数绑定,这样就可以省去asm框架的读取字节码的操作。

二、SpringMVC源码分析

2.1 实现Controller接口源码流程

2.1.1 流程图

processOn

我们根据工作机制中三部分来分析SpringMVC的源码

1. ApplicationContext初始化时建立所有Url和Controller类的对应关系(用Map保存)。

2. 根据请求Url找到对应的Controller,并从Controller中找到处理请求的方法。

3. Request参数绑定到方法的形参,执行方法处理请求,并返回结果视图。

2.1 建立Map<url, controller>的关系

我们首先看第一个步骤,也就是建立Map<url,controller>关系的部分。第一部分的入口类为ApplicationObjectSupport的setApplicationContext方法。

setApplicationContext方法中核心部分就是初始化容器initApplicationContext(context),子类AbstractDetectingUrlHandlerMapping实现了该方法,所以我们直接看子类中的初始化容器方法。

	@Override
	public void initApplicationContext() throws ApplicationContextException {
		super.initApplicationContext();
		// 检测出handler
		detectHandlers();
	}
	// 建立当前ApplicationContext中的所有Controller和Url的对应关系
	protected void detectHandlers() throws BeansException {
		ApplicationContext applicationContext = obtainApplicationContext();
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for URL mappings in application context: " + applicationContext);
		}
		// 获取ApplicationContext容器中所有Bean的Name
		String[] beanNames = (this.detectHandlersInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
				applicationContext.getBeanNamesForType(Object.class));

		// Take any bean name that we can determine URLs for.
		// 遍历beanNames,并找到这些Bean对应的Url
		for (String beanName : beanNames) {
			// 找bean上的所有Url(Controller上的Url + 方法上的Url),该方法由对应的子类实现
			String[] urls = determineUrlsForHandler(beanName);
			if (!ObjectUtils.isEmpty(urls)) {
				// URL paths found: Let's consider it a handler.
				// 注册handler(重点)
				// 保存url和beanName的对应关系
				// put it to Map<urls,beanName>,该方法在父类AbstractUrlHandlerMapping中实现
				registerHandler(urls, beanName);
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
				}
			}
		}
	}
	// 获取controller中所有方法的url,由子类实现,典型的模板模式
	protected abstract String[] determineUrlsForHandler(String beanName);

determineUrlsForHandler(String beanName)方法的作用是获取每个controller中的url,不同的子类有不同的实现,这是一个典型的模板设计模式。

BeanNameUrlHandlerMapping是AbstractDetectingUrlHandlerMapping的子类,处理beanName形式的url映射。

我们这里以BeanNameUrlHandlerMapping来进行分析。我们看BeanNameUrlHandlerMapping是如何查beanName上所有映射的url。

 BeanNameUrlHandlerMapping#determineUrlsForHandler

	// 获取Controller中所有的url
	@Override
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<>();
		// 判断beanName,哪些是以 "/" 开头
		if (beanName.startsWith("/")) {
			urls.add(beanName);
		}
		String[] aliases = obtainApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) {
				urls.add(alias);
			}
		}
		return StringUtils.toStringArray(urls);
	}

AbstractUrlHandlerMapping#registerHandler

	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) {
			// 转为beanName
			String handlerName = (String) handler;
			ApplicationContext applicationContext = obtainApplicationContext();
			if (applicationContext.isSingleton(handlerName)) {
				// 根据beanName获取bean,对应到我们的controller类
				resolvedHandler = applicationContext.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 {
				// 最终put到map集合中,Map<url,controller>
				this.handlerMap.put(urlPath, resolvedHandler);
				if (logger.isInfoEnabled()) {
					logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
				}
			}
		}
	}

到这里HandlerMapping组件就已经建立所有url和controller的对应关系

2.2 根据访问url找到对应controller中处理请求的方法

下面我们开始分析第二个步骤,第二个步骤是由请求触发的,所以入口为DispatcherServlet。

DispatcherServlet的核心方法为doService(),doService()中的核心逻辑由doDispatch()实现,我们查看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变量
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				// 1.检查是否是文件上传的请求
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// 2.取得处理当前请求的controller,这里也称为handler处理器,第一个步骤的意义就在这里体现了
				//   这里并不是直接返回controller,而是返回的HandlerExecutionChain请求处理器链对象,该对象封装了handler和interceptors
				mappedHandler = getHandler(processedRequest);
				// 如果handler为空,则返回404
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				// 推断适配器,不同的controller类型,交给不同的适配器去处理
				// 如果是一个bean,mappedHandler.getHandler()返回的是一个对象
				// 如果是一个method,mappedHandler.getHandler()返回的是一个方法
				// 3.获取处理request的处理器适配器handlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
				// 到这里,spring才确定我要怎么反射调用

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					// 处理last-modified请求头
					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;
					}
				}

				// 4.拦截器的预处理方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				// 通过适配器,处理请求(可以理解为,反射调用方法)(重点)
				// 5.实际的处理器处理请求,返回结果视图对象
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

				// 结果视图对象的处理
				applyDefaultViewName(processedRequest, mv);

				// 6.拦截器的后处理方法
				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);
				}
			}
		}
	}

第2步:getHandler(processedRequest)方法实际上就是从HandlerMapping中找到url和controller的对应关系。这也就是第一个步骤:建立Map<url,Controller>的意义。

我们知道,最终处理request的是controller中的方法,我们现在只是知道了controller,还要进一步确认controller中处理request的方法。

由于下面的步骤和第三个步骤关系更加紧密,直接转到第三个步骤。

2.3 反射调用处理请求的方法,返回结果视图

上面的方法中,第2步其实就是从第一个步骤中的Map<urls,beanName>中取得Controller,然后经过拦截器的预处理方法,到最核心的部分--第5步调用controller的方法处理请求。

在第2步中我们可以知道处理request的Controller,第5步就是要根据url确定Controller中处理请求的方法,然后通过反射获取该方法上的注解和参数,解析方法和参数上的注解,最后反射调用方法获取ModelAndView结果视图。

因为上面采用注解url形式说明的,所以我们这里继续以注解处理器适配器来说明。第5步调用的就是AnnotationMethodHandlerAdapter的handle().handle()中的核心逻辑由invokeHandlerMethod(request, response, handler)实现。

三、源码Debug分析

3.1 SpringMVC初始化流程

3.2 DispatcherServlet入口

3.3 DispatcherServlet流程

我们分析@Controller、@RequestMapping注解形式的

Gitee-Spring-MVC-SourceCode

流程图

 

参考博客参考博客视频教程视频教程

  • 16
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring MVC源码包括多个组件和类。其中,Tomcat在启动时会通知Spring初始化容器,加载bean的定义信息并初始化所有单例bean。然后,Spring MVC会遍历容器中的bean,获取每个controller中方法访问的URL,并将URL和Controller保存到一个Map中。这一过程是由HandlerMapping组件完成的,它是Spring MVC中负责URL到Controller映射的组件。此外,在Spring MVC源码中还有一个抽象类FrameworkServlet,它重写了初始化方法initServletBean(),可以在控制台或日志中打印初始化Servlet的名称以及初始化所需的时间。 以上是关于Spring MVC源码的一些重要信息,这些组件和类协同工作,实现了Spring MVC框架的核心功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Spring MVC源码分析](https://blog.csdn.net/qq_38826019/article/details/117877511)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [SpringMVC源码解析](https://blog.csdn.net/qq_35512802/article/details/120659719)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值