背景:不得不提的 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
方法中,如何获取 handler
、handlerAdapter
,就是我们要关注的重点:
相关的代码:
①:获取处理器(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;
可以看到,获取的具体逻辑,实际上交给了 handlerMappings
。handlerMappings
是接口 HandlerMapping
的集合,该接口的实现类有很多:
HandlerMapping
接口的继承关系:
至此,我们发现了一个很重要的宝藏接口类 HandlerMapping
,这里就藏着分发 request
到 handler
的秘密。
通过其所在位置,还可以发现另外一个兄弟:
具体怎么使用的,就很简单了,就是遍历 handlerMappings
集合,调用 getHandler
方法,命中返回即可,以下是 debug
截图:
其中,RequestMappingHandlerMapping
中,就存在我们熟悉的 controller
的信息。
分析到这里,可以理解为 DispatcherServlet
是全局一个单实例,所有的请求,被 DispatcherServlet.doDispatcher
方法分发,分发给到对应的处理器(controller方法
)。
这里有两条信息需要分析:
①:HandlerMapping
接口中,根据 request
找到 handler
是如何实现的。
②:private List<HandlerMapping> handlerMappings
的初始化。
HandlerMapping 获取 handler 的细节
RequestMappingHandlerMapping
的 getHandler
方法,实际调用的其父类 AbstractHandlerMapping
的 getHandler
。
查看类继承结构: 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
,然后在根据 lookupPath
和 request
找到 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
匹配 lookupPath
找 HandlerMethod
,源码这德行,就一层套一层。
简单看下 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 MVC 解析之 DispatcherServlet
# SpringMVC 系列源码:DispatcherServlet