Loading

SpringMVC中的Handler、HandlerMapping、HandlerAdapter到底是啥

这东西,虽然说和我们的开发没啥关系,尤其是当你用SpringBoot进行开发时,这些接口离你越来越远了。讲实话,要不是这学期扫一眼学校的课件,我都不知道有这东西,这东西本来就是对使用框架进行开发的开发者隐藏的。人家好不容易隐藏起来,你却要我们学起来,没事儿干了吧。

下图是网上流传的总览图,来自这篇文章:SpringMVC框架理解

img

下面通过阅读源码,来学习这些接口都是干啥的

DispathcerServlet

不管是哪个Web框架,基于什么语言,都会提供一个在整个系统最前端接受用户请求的东西,我们暂且称它“前端调度器”,它会解析用户请求请求,调度你编写的用于接收请求的组件。这样,你可以根据不同的请求编写不同的组件,在SpringMVC里,DispathcerServlet就是前端调度器,Controller就是你编写的处理请求的组件。

SpringMVC也是基于JavaWeb的那套ServletAPI的,所以,它使用一个Servlet用来接收所有请求,它就像一个桥,一头是ServletAPI,一头是SpringMVC,把Servlet世界里的话翻译成框架中的通用语言。

既然是Servlet,那我们就看它的doService方法呗:

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);
    // ... 省略一些代码 ... 


    try {
        doDispatch(request, response);
    }

    // ... 再省略一些代码 ...
}

调用了doDispatch来执行调度。doDispatch里的代码太多了,我做了精简之后还是很多,所以我在代码中写上注释:

@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    HttpServletRequest processedRequest = request;
    // 记住它的类型,是一个Handler执行链
    HandlerExecutionChain mappedHandler = null;

    ModelAndView mv = null;
    Exception dispatchException = null;

    // ... 省略代码 ...
    try {

        // processedRequest是经过一定处理的请求对象
        // 这里是根据请求,获取一个能够处理该请求的Handler对象(实际是一个Handler执行链)
        // Handler用于对请求进行处理
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }

        // 根据Handler对象获取一个HandlerAdapter对象
        // 具体为啥要这一层,我们稍后会说
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        // 在实际执行对请求的处理之前,先调用Handler的预处理方法`preHandle`
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        // 实际调用Handler,这里调用的是Handler的适配器对象,HandlerAdapter,它会返回一个ModelAndView
        mv = ha.handle(processedRequest, response, 

        // 请求处理完毕,这里调用Handler的`postHandle`方法
        mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    // 记录异常
    catch (Exception ex) {
        dispatchException = ex;
    }
    catch (Throwable err) {
        dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
    // 对上面一同折腾得到的处理结果进行处理,返回给前端
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

}

目前来看,DispatcherServlet对一个请求调度的过程是:

  1. 根据请求获取能够处理的Handler对象
  2. 根据Handler对象获取它的适配器对象HandlerAdapter
  3. 调用HandlerpreHandle,进行请求预处理
  4. 调用HandlerAdapterhandle,实际处理请求,返回ModelAndView
  5. 调用HandlerpostHandle,善后
  6. 上面的过程中如果出现异常,记录
  7. 最后,根据请求对象,响应对象,Handler对象,ModelAndView对象,异常对象的状态,向前端返回一个结果

从这些流程里,我们能够知道,Handler才是实际处理请求的对象,而且根据它的类型HandlerExecutionChain,我们能猜测,实际处理请求的可能不止是一个处理器,而是一串处理器。HandlerAdapter是对该对象的再次包装,而且使用适配器模式,应该是为了兼容某些不兼容的接口

下面我们继续深入

getHandler如何实现

下面是getHandler的代码,我们可以看出,它遍历了类中的一个叫handlerMappings的可迭代对象,这证明,DispathcerServlet维护了一系列用于映射请求到HandlerHandlerMapping对象。

而且,getHandler的任务就是从这些HandlerMapping中找到一个能处理当前请求的并返回,否则返回null。

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

HandlerMapping是啥?

public interface HandlerMapping {

    // ... 省略掉一些无关方法 ...

	@Nullable
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

getHandler方法返回一个用于参数中的请求对象的Handler和任何的Interceptors,选择的依据可能是请求URL、session状态或任何实现类选择的其它因素。

返回的HandlerExecutionChain包含一个Object类型的handler对象,而不是任何一种接口类型,这是为了处理器可以不受任何约束。举个例子,一个HandlerAdapter可以被写成允许使用其它框架的Handler对象。

HandlerExecutionChain如何实现

通过上面的分析,我们已经知道了HandlerExecutionChain中包含了实际的Handler对象,它用于处理请求,这个对象不与任何框架的处理器对象耦合,然后还包含了一系列用于该请求的HandlerInterceptor

private final Object handler;

private final List<HandlerInterceptor> interceptorList = new ArrayList<>();

我们知道SpringMVC的HandlerInterceptor对象用于拦截一个请求,它可以在一个请求执行前后做一些处理。这是我们经常使用的类。不知道也没关系,现在你知道它的作用了。

我们可以看到,HandlerExecutionChain提供了一些方法,applyPreHandle用于调用所有interceptor的preHandle方法,如果该方法返回false,证明该Interceptor不希望请求继续传递给链中的下一个处理器,该请求应该被拦截,到此为止。所以它判断了返回值,并且当返回false时停止执行。其它的applyPostHandletriggerAfterCompletion也都大同小异。这是职责链设计模式。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        if (!interceptor.preHandle(request, response, this.handler)) {
            triggerAfterCompletion(request, response, null);
            return false;
        }
        this.interceptorIndex = i;
    }
    return true;
}

/**
    * Apply postHandle methods of registered interceptors.
    */
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
        throws Exception { /* ... */ }

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) { /* ... */ }

回到doDispatch

现在,doDispatch中下面的高亮部分的代码,是不是不用注释也能懂了:

+mappedHandler = getHandler(processedRequest);

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

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

mv = ha.handle(processedRequest, response, 

+mappedHandler.applyPostHandle(processedRequest, response, mv);

目前,我们知道了HandlerMapping是用于将请求映射到一个HandlerExecutionChain的,而HandlerExecutionChain主要执行该请求的所有Interceptor,用于对请求进行拦截和善后,并且它保存了实际的Handler对象,该对象是Object类型,不和任何框架中的处理器类耦合,这给了其它框架整合到SpringMVC的能力

HandlerAdapter

现在我们的版图只剩下HandlerAdapter一块了,我虽文思泉涌,但奈何我妈喊我吃饭。看到这,不妨你也休息一下。

既然SpringMVC选择了Object类型的Handler对象来兼容其它框架,那么必定要有一个适配器对象来将这个Handler适配到SpringMVC中,如果不经过任何处理,就指望着其它框架中的处理器与SpringMVC中的接口完全兼容是不可能的。所以这里要用到适配器模式。

getHandlerAdapter方法

也不难看出,和HandlerMapping一样,DispatcherServlet中也维护了HandlerAdapter的一个可迭代结构——handlerAdapters

getHandlerAdapter方法遍历这个适配器链,调用它们的supports方法,传入Object类型的handler,看看适配器是否适配这个处理器。它找到第一个适配这个处理器的适配器并返回,否则就抛出异常。

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方法用于判断传入的Handler是否被当前适配器所适配,handle方法用于实际的处理请求,它一边调用自己的Handler对象的API,一边尽力去适配SpringMVC的API,比如返回Spring中的ModelAndView。它是任何Handler(兼容Spring或不兼容Spring的)与Spring框架间的桥梁。

public interface HandlerAdapter {

	boolean supports(Object handler);

	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    // ... 省略一个已废弃方法 ...
}

support方法

比如你有一个自己开发的框架——FlyMVC,你的框架有自己的处理器接口FlyHandler,该处理器通过HandlerMapping被添加到HandlerExecutionChain中。但它并不兼容Spring框架,为了让它与Spring协同工作,你需要编写一个HandlerAdapter,并且,它的supports方法可能如下:

public boolean supports(Object handler) {
    return handler instanceof FlyHandler;
}

SpringMVC中,谁扮演Handler?

毫无疑问是Controller

如果你在新版Idea中,在HandlerAdapter类上会看到这个,你点一下就可以看到所有实现类:

img

点开我们就能看到有一个SimpleControllerHandlerAdapter,它肯定是用于SpringMVC的Controller的HandlerAdapter。它的实现简单至极:

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

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

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

		return ((Controller) handler).handleRequest(request, response);
	}

    // ... 省略一个无关方法 ...
}

如果你实现了Controller接口来编写一个请求处理器,那么这个HandlerAdapter会被应用。同时,因为Controller本就是SpringMVC中的组件,它的handleRequest方法返回的本就是ModelAndView,所以,它的handle方法很简单,并看不出做了什么不兼容接口之间的转换,毕竟都是SpringMVC里的组件。

没实现Controller接口的Controller怎么处理的?

我们知道,在Spring中编写Controller并不非要实现接口,而且Spring官方宣扬的最小侵入性也鼓励我们尽量不要用接口的方式,这会让我们编写的组件和Spring框架耦合。

我们可以这样编写一个Controller

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

这个Controller并没有实现任何接口,它是怎么被DispatcherServlet调度的?这里,我盲猜是Spring会将它转换成某个Controller接口的实现类,然后继续按照上面的流程,使用SimpleControllerHandlerAdapter来调度它。

我们在这里打上断点,然后运行程序,访问/hello

img

断点没有任何反应,请求直接通过,这说明不实现接口来编写的Controller并不是按我们之前想的那样工作的,完全是两个流程,根本没用到SimpleControllerHandlerAdapter

下面我们把断点打到DispatcherServlet中:

img

这次我们看到,handler是一个HandlerMethod对象的实例,它的HandlerAdapterRequestMappingHandlerAdapter

img

HandlerMethod

基于非接口方式编写的Controller,每一个方法处理一个请求,所以,HandlerMethod代表的就是一个请求方法。

RequestMappingHandlerAdapter

它用于处理标注了@RequestMappingHandlerMethod对象,因此得名。

它扩展自AbstractHandlerMethodAdaptersupports方法也是在父类中完成的:

// AbstractHandlerMethodAdapter.supports()
@Override
public final boolean supports(Object handler) {
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

该方法只判断了handler是否是HandlerMethod,而且,它考虑到它的子类有可能在能够处理的方法上有其它限制,所以它还调用了子类的supportsInternal方法,子类可以通过该方法来判断是否支持处理对应的HandlerMethod

AbstractHandlerMethodAdapter中的handle方法也极其简单,直接调用子类的handleInternal方法,这属于模板设计模式:

// AbstractHandlerMethodAdapter.handle()
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

    return handleInternal(request, response, (HandlerMethod) handler);
}

下面来看看RequestMappingHandlerAdatper.handleInternal怎么写的:

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    checkRequest(request);

    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No HttpSession available -> no mutex necessary
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    }
    else {
        // No synchronization on session demanded at all...
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    // ... 省略 ...

    return mav;
}

有点长,总结起来就是根据不同情况,做不同的处理,再使用invokeHandlerMethod来调用具体的HandlerMethod,最后返回ModelAndView

为什么这里不直接调用HandlerMethod,而是包装了一层方法呢?要知道,你编写的那些Controller方法可不一定返回ModelAndView,将你那些方法返回的东西按情况合理的转换成ModelAndView,就是该invokeHandlerMethod方法存在的理由。

具体的代码我就不看了,我觉得到这就足够满足我的好奇心了。

总结

看下面这张图,自己总结一遍,注意,从视图解析器部分开始,都不是本篇文章讨论的范围。

img

我的总结

  1. DispatcherServlet接收前端请求,找到一个用于处理该请求的HandlerMapping
  2. HandlerMapping返回一个HandlerExecutionChain,它其中包含了一堆Interceptor和一个任意类型的Handler对象
  3. 执行Interceptor的preHandle方法
  4. 为了将任意类型的Handler适配到SpringMVC,所以需要为每种Handler提供一个HandlerAdapter
  5. 执行所有HandlerAdapter的supports方法,找到支持该种Handler的适配器
  6. 执行找到的HandlerAdapter的handle方法,处理请求,返回ModelAndView
  7. 执行Interceptor的postHandle方法
  8. 如果一切正常,结束,返回
  9. 如果发生异常,记录异常,返回
posted @ 2022-07-03 10:29  yudoge  阅读(801)  评论(0编辑  收藏  举报