SpringMvc是如何管理Controller

背景:不得不提的 servlet

介绍下两者的关系,mvc 要解决的问题

Servlet 作为 Java Web 开发的基础组件,用于处理 http 请求和响应,但在实际使用中,不够便捷,于是,spring 就重新设计一套实现,方便开发者更专注于业务代码开发的 mvc 组件,也就是 springmvc。

Servlet 的使用样例:

java 复制代码
// WebServlet注解表示这是一个Servlet,并映射到地址/:
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        
        //这里是用户请求的总入口,根据用户的请求路径,可以分别调用对应的方法
        /**
        * req/a -> method1
        * req/b -> method2
        * req/c -> method3
        */
        
        // 设置响应类型:
        resp.setContentType("text/html");
        // 获取输出流:
        PrintWriter pw = resp.getWriter();
        // 写入响应:
        pw.write("<h1>Hello, world!</h1>");
        // 最后不要忘记flush强制输出:
        pw.flush();
    }
}

SpringMvc 的使用样例:

java 复制代码
@RestController
@Slf4j
@RequestMapping("/myReq")
public class MyReqController {

    @GetMapping("/name")
    public String getName(String name) {
        return "接受的参数:" + name;
    }

}

用户发起的请求 localhost:8080/myReq/name?name=abc,就可以直接被上述代码执行。

mvc 将路径判断封装起来,让开发者不再关注非业务逻辑,只关注具体方法的实现。 那这一切是怎么实现的?

SpringMvc 的核心类 DispatcherServlet

mvc 之所以让开发流程更便捷,主要是通过 DispatcherServlet 类来统一处理请求,将 req 请求转发给对应的 handler 处理。也就是在对 request 进行路由分发到 handler

要找到 mvc 的核心入口,可以通过 debug 方式,从请求的堆栈中往上翻找到 doDispatch 方法

以下是 doDispatch 的文档说明

java 复制代码
	/**
	 * Process the actual dispatching to the handler.
	 * 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.
	 * All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
	 */
	 //翻译下就是:将请求分发到具体的handler。通过应用Servlet的HandlerMappings按优先级获取处理程序(handler)。从HandlerAdapters中找到对应的HandlerAdapter,也就是支持处理程序类的第一个适配器。所有HTTP方法都由此方法处理。这取决于HandlerAdapters或handlers。(机翻)
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//...
	}

先解释下 handler 这个高频出现的词,在 mvc 中,其实就是指 controller 中某个具体的方法,通常也被称为处理器。

原文的文档说明,描述的比较清晰,说白了就是 doDispatch 就是实际上分发请求给到对应方法(handler)的程序,HandlerMappings 维护了一个有优先级的处理器集合。HandlerAdapter 就是处理器的适配器,封装了不同类型的 handler ,已保证 mvc 中一致的处理流程(适配器存在的意义,就是适配后,让应用可以按固有标准执行)。

所以,doDispatch 方法中,如何获取 handlerhandlerAdapter,就是我们要关注的重点:

相关的代码:

①:获取处理器(HandlerExecutionChain(handler)) HandlerExecutionChain mappedHandler = this.getHandler(processedRequest);

官方说明:Determine handler for the current request. 就是根据当前的请求,拿到对应的处理器,说人话就是,根据请求的 path 路径,找到对应 controller 中的方法。

②:获取处理器适配器(handlerAdapter) HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

官方说明:Determine handler adapter for the current request. 意思是,获取对应处理器的适配器。适配器负责将处理器的执行结果适配为框架的统一处理结果,以便进行后续的处理和响应。

获取处理器(HandlerExecutionChain(handler))

获取处理器 handler,实际获取到的对象是 HandlerExecutionChain,即处理器的执行链。执行链中不仅包括了目标 handler ,也包括各类拦截器:List<HandlerInterceptor> interceptorList

HandlerExecutionChain 的类图可以发现,HandlerExecutionChain 主要就是对目标 handler 添加了各类拦截器:

Object handler,就是 controller 中的具体的方法 method

继续看,getHandler 的全类路径:org.springframework.web.servlet.DispatcherServlet#getHandler ,源码如下:

java 复制代码
	// Determine handler for the current request.
	// HandlerExecutionChain mappedHandler,除了目标handler,还包括各类拦截器(List<HandlerInterceptor> interceptorList)
	mappedHandler = getHandler(processedRequest);
	
	/**
	 * 从所有HandlerMappings中,根据排序,找到可处理器当前请求的HandlerExecutionChain 
	 */
	@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			//this.handlerMappings,是HandlerMapping接口的实现类集合,包括各类处理器的实现,其中RequestMappingHandlerMapping是我们需要重点关注的实现。
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

    //这里的值是什么时候设置进来的?需要从初始化入手,这里暂时不关注
	/** List of HandlerMappings used by this servlet. */  
	@Nullable  
	private List<HandlerMapping> handlerMappings;

可以看到,获取的具体逻辑,实际上交给了 handlerMappingshandlerMappings 是接口 HandlerMapping 的集合,该接口的实现类有很多:

HandlerMapping 接口的继承关系:

至此,我们发现了一个很重要的宝藏接口类 HandlerMapping,这里就藏着分发 requesthandler 的秘密。

通过其所在位置,还可以发现另外一个兄弟:

具体怎么使用的,就很简单了,就是遍历 handlerMappings 集合,调用 getHandler 方法,命中返回即可,以下是 debug 截图:

其中,RequestMappingHandlerMapping 中,就存在我们熟悉的 controller 的信息。

分析到这里,可以理解为 DispatcherServlet 是全局一个单实例,所有的请求,被 DispatcherServlet.doDispatcher 方法分发,分发给到对应的处理器(controller方法)。

这里有两条信息需要分析:

①:HandlerMapping 接口中,根据 request 找到 handler 是如何实现的。

②:private List<HandlerMapping> handlerMappings 的初始化。

HandlerMapping 获取 handler 的细节

RequestMappingHandlerMappinggetHandler 方法,实际调用的其父类 AbstractHandlerMappinggetHandler

查看类继承结构: RequestMappingHandlerMapping 有个抽象父类 AbstractHandlerMapping :

抽象父类的 getHandler 方法:

java 复制代码
	/**
	 * 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
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//根据request获取handler,@see #getHandlerInternal
		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 = obtainApplicationContext().getBean(handlerName);
		}

		//封装handler,返回 executionChain
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		
		.....略

		return executionChain;
	}

	//getHandlerInternal是个抽象方法,实现在子类中
	@Nullable  
	protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

好家伙,抽象父类又调用了子类方法,方法 getHandlerInternal 三个子类的实现:

我们重点看第一个:AbstractHandlerMethodMapping.getHandlerInternal

java 复制代码
	// Handler method lookup

	/**
	 * Look up a handler method for the given request.
	 */
	@Override
	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		this.mappingRegistry.acquireReadLock();
		try {
			//根据path,找handlerMethod,这不就是我们梦寐以求的那段寻找处理器的代码么
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}

源码有点绕,但实际上就做了两件事,先从 request 中提取出请求的路径 lookupPath,然后在根据 lookupPathrequest 找到 HandlerMethod

关键处的一两行:

  • String lookupPath = getUrlPathHelper().getLookupPathForRequest(request)
  • HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);

第一行是 url 解析工具,这里暂不关系,感兴趣的可以搜索此类:org.springframework.web.util.UrlPathHelper

我们重点看第二行的方法,lookupHandlerMethod 相关源码:

java 复制代码
	/**
	 * Look up the best-matching handler method for the current request.
	 * 为当前请求找到最匹配的handler
	 * If multiple matches are found, the best match is selected.
	 */
	@Nullable
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		//初始化匹配器集合,可能存在多个
		List<Match> matches = new ArrayList<>();
		
		//以下两段if,主要是将mappingRegistry中的映射信息,初始化到matches中
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		if (directPathMatches != null) {
			addMatchingMappings(directPathMatches, matches, request);
		}
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}

		//匹配器不为空,
		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);
			//排序后,第一个就是最佳匹配的match
			Match bestMatch = matches.get(0);
			//有第二个,判断下第一个和第二个匹配器优先级是否相同,
			if (matches.size() > 1) {
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					String uri = request.getRequestURI();
					throw new IllegalStateException(
							"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
				}
			}
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
			handleMatch(bestMatch.mapping, lookupPath, request);
			//返回最佳的handler
			return bestMatch.handlerMethod;
		}
		else {
			//没找到匹配器
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}

	//我这里管理的path->handler的映射信息
	private final MappingRegistry mappingRegistry = new MappingRegistry();

这次我们又在源码发现一个宝藏内部类 MappingRegistry,其全路径 org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry

这段代码的功臣,就是通过 MappingRegistry 匹配 lookupPathHandlerMethod,源码这德行,就一层套一层。

简单看下 MappingRegistry 的类图:

MappingRegistry 就是 AbstractHandlerMethodMapping 最后一站了,这里维护了所有的映射关系。现在就剩下一个需要理清楚的地方,就是 MappingRegistry 的初始化。

内部类 MappingRegistry 的初始化,也就是所在主类 AbstractHandlerMethodMapping 的初始化;AbstractHandlerMethodMapping 的初始化,也就是接口 HandlerMapping 的初始化的一个分支,它们是一衣带水,要弄清楚 dispatcherServelt 如何为请求 request 找到自己专属的 handler,就需要了解 HandlerMapping 接口的初始化。

HandlerMapping 的初始化

HandlerMapping 的初始化,我们目前关注的是 AbstractHandlerMethodMapping 的初始化以及其内部类 MappingRegistry 的初始化。

未完待续...(埋坑)

获取处理器适配器(handlerAdapter)

前面拿到 handler 后,在处理前,还需要一步,就是根据将 hanler 包装为 handlerAdapter,再由 handlerAdapter 去执行 invoke 逻辑。源码如图:

java 复制代码
	// Determine handler adapter for the current request.
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
	
	// Actually invoke the handler.
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

我们先分析下 getHandlerAdapter 方法都干了什么事。点进去方法一看,好家伙,就直接遍历的所有的视频器,找到支持当前 handler 便返回,找不到就报错(这个适配器我是非拿不可了!):

java 复制代码
	/**
	 * 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。再次放出这个图:

HandlerAdapter 接口的类图,也是相当简单明了,并且,supports + handle 组合,是不是很熟悉?

其实,这就是我们常用的拦截器(原来你在这里)。

有了 handler 的获取经验,我们就自然明白,HandlerAdapter 接口的初始化,就藏着我们要了解的秘密。于是就有了后面的标题:HandlerAdapter 的初始化。

看初始化前,先简单过下 HandlerAdapter 接口中常用的实现类 RequestMappingHandlerAdapter,借此熟悉下 Adapter 的用途。

下图是 HandlerAdapter 接口一个重要的实现类 RequestMappingHandlerAdapter 的 debug 信息,可以更加直观了解下适配器做了些什么。

适配器中,有以下内容(列了部分常见的解析器、处理器):

  • 自定义参数解析器:List<HandlerMethodArgumentResolver> customArgumentResolvers

  • 预置的参数解析器:HandlerMethodArgumentResolverComposite argumentResolvers

  • 返回值处理器 HandlerMethodReturnValueHandlerComposite returnValueHandlers

HandlerAdapter 的初始化

初始化埋坑,哈哈...

真正的执行(前中后)

这块的逻辑就很简单,在源码中,就是先执行前置拦截器,然后目标方法,最后调用后置拦截器。

源码如下:

java 复制代码
	//执行前置的拦截器
	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;
	}

	// Actually invoke the handler.这里就是真正执行目标方法的地方,mappedHandler.getHandler()就是获取待反射执行的方法
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

	//执行后置的拦截器
	mappedHandler.applyPostHandle(processedRequest, response, mv);

源码中,实际执行的方法是 mappedHandler.getHandler(),继续查看,是反射的方式调用:

至此,整个流程已分析完毕。

总结

看到这里,我们就可以简单总结下:

发现在 DispatcherServlet 类中,两次获取相关 handler 方法中,都是交给了内置的属性来判断,怎么形容呢,感觉 DispatcherServlet 更像是一个交警指挥 request 的执行,你走这里,他去那里,去找自己的目的地,指挥着 request交通路线

正向流程看完,我之前脑海中存在的疑问,其实也就有了答案。写文章前,我有以下几个问题,现在看,它们都渐渐清晰,并有了属于自己的位置。

  • Controller 何时被初始化为 mappedHandler(spring 启动那套)

  • handlerMethod 是谁?

  • HandlerMappingIntrospector?

  • HandlerInterceptor 和 filter

  • interceptor 初始化

  • filter 初始化

参考文章:

scdn # Spring MVC 是管理 controller 对象的容器

Spring 基础 - SpringMVC 请求流程和案例

廖雪峰-servlet入门

廖雪峰-mvc

# Servlet规范之Filter工作原理

# Spring MVC 解析之 DispatcherServlet

# SpringMVC 系列源码:DispatcherServlet

# SpringMVC 源码分析之 DispatcherServlet

# SpringMVC 中的参数还能这么传递?涨姿势了!

# SpringMVC 初始化流程分析

相关推荐
芯冰乐29 分钟前
综合时如何计算net delay?
后端·fpga开发
用户673559885613 小时前
数据驱动,实时监控显威力 —— 淘宝商品详情API助力商家精准营销
后端·api·fastapi
lucifer3113 小时前
线程池与最佳实践
java·后端
用户673559885613 小时前
天猫店铺商品列表API返回值中的商品视频与图文详情
前端·javascript·后端
程序员大金4 小时前
基于SSM+Vue+MySQL的酒店管理系统
前端·vue.js·后端·mysql·spring·tomcat·mybatis
程序员大金4 小时前
基于SpringBoot的旅游管理系统
java·vue.js·spring boot·后端·mysql·spring·旅游
Pandaconda4 小时前
【计算机网络 - 基础问题】每日 3 题(十)
开发语言·经验分享·笔记·后端·计算机网络·面试·职场和发展
程序员大金5 小时前
基于SpringBoot+Vue+MySQL的养老院管理系统
java·vue.js·spring boot·vscode·后端·mysql·vim
customer085 小时前
【开源免费】基于SpringBoot+Vue.JS网上购物商城(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
Ylucius5 小时前
JavaScript 与 Java 的继承有何区别?-----原型继承,单继承有何联系?
java·开发语言·前端·javascript·后端·学习