SpringMVC中的HandlerMapping和HandlerAdapter详解

 更新时间:2023年08月31日 08:56:10   作者:止步前行  
这篇文章主要介绍了SpringMVC中的HandlerMapping和HandlerAdapter详解,在Spring MVC中,HandlerMapping(处理器映射器)用于确定请求处理器对象,请求处理器可以是任何对象,只要它们使用了@Controller注解或注解@RequestMapping,需要的朋友可以参考下

一、引言

本人在阅读 SpringMVC 源码过程中,一直对 HandlerMapping 、 HandlerAdapter 有疑惑,一直不理解。心里知道这里用的是适配器模式,本人对适配器模式还是知晓的,但这两个东西就是不理解。最近突然知道了一个知识点,瞬间豁然开朗,至于是哪个知识点,下面慢慢说。

下面这张图是 SpringMVC 的工作流程图,随便一搜,应该一大把,这里只做熟悉用,不会细说。(PS:之前跳槽面试,就有一道笔试题让我画SpringMVC的工作流程。。。。)

在这里插入图片描述

对上图做一下简单总结:

1、请求首先进入 DispatcherServlet , 由 DispatcherServlet 从 HandlerMappings 中匹配对应的 Handler ,此时只是获取到了对应的 Handler ,然后拿着这个 Handler 去寻找对应的适配器,即: HandlerAdapter ;

2、拿到对应 HandlerAdapter 时,这时候开始调用对应的 Handler 方法,即执行我们的 Controller 来处理业务逻辑了, 执行完成之后返回一个 ModeAndView ;

3、 HandlerAdapter 执行完之后,返回一个 ModeAndView ,把它交给我们的视图解析器 ViewResolver ,通过视图名称查找出对应的视图然后返回;

4、最后,渲染视图 返回渲染后的视图。

二、SpringMVC中定义Controller的方式

在介绍 HandlerMapping 、 HandlerAdapter 之前,先来说一下 SpringMVC 中定义 Handler 的方式,本人就是对这个知识点不熟悉,导致对这两个对象一直不明白。

先说一下最最最最……常用定义 Handler 的方式,使用 @RequestMapping 注解,下面这段代码不用介绍吧:

@Controller
public class IndexController {
    @RequestMapping("/index")
    @ResponseBody
    public String sayHello(){
        System.out.println("hello ...");
        return "hello";
    }
}

那大家有没有用过下面的两种方式来声明一个 Handler 呢??

实现 org.springframework.web.servlet.mvc.Controller 控制器接口,此接口只有一个方法 handleRequest() ,用于请求的处理,返回 ModelAndView 。 这个接口从第一版 SpringMVC 就存在了,所以这个接口是非常古老的接口~~~也是 Spring MVC 最早期的实现 Handler 的方式

// 关注一下这个包
import org.springframework.web.servlet.mvc.Controller;
@Component("/home")
public class HomeController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, 
    				HttpServletResponse response) throws Exception {
        System.out.println("home ...");
        return null;
    }
	// 这地方考虑个问题:怎么样实现类似@ResponseBody的功能呢?
	// 就是想实现直接向body里写数据,而不是返回一个页面。
	// 如果想直接在处理器/控制器里使用response向客户端写回数据,
	// 可以通过返回null来告诉	DispatcherServlet我们已经写出响应了,
	// 不需要它进行视图解析。像下面这样
	@Override
    public ModelAndView handleRequest(HttpServletRequest request,
    				 HttpServletResponse response) throws Exception {
        System.out.println("home ...");
        response.getWriter().write("home controller from body");
        return null; // 返回null告诉视图渲染  直接把body里面的内容输出浏览器即可
    }
}

实现 org.springframework.web.HttpRequestHandler 接口, HttpRequestHandler 用于处理 Http requests ,其类似于一个简单的 Servlet ,只有一个 handlerRequest() 方法,其处理逻辑随子类的实现不同而不同。

// 关注一下这个包
import org.springframework.web.HttpRequestHandler;
@Component("/login")
public class LoginController implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, 
    		HttpServletResponse response) 
    		throws ServletException, IOException {
        System.out.println("login...");
        response.getWriter().write("login ...");
    }
}

再来看一下 servlet 的使用,是不是很相似。

@WebServlet("/myServlet")
public class MyServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, 
    		HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
    }
}

其实上面这两种方式第一种使用 @RequestMapping 注解一样,都能定义为一个 Handler ,拦截到对应的请求,并且做出响应。这地方就要牵扯出 HandlerMapping 了。

三、何为HandlerMapping、HandlerAdapter?

从上面的分析,我们知道, Handler 的定义有上面三种(也有可能还有其他方式,比如Servlet),这地方就要引出下面这两个 HandlerMapping:BeanNameUrlHandlerMapping 、 RequestMappingHandlerMapping ,当然还有其他 HandlerMapping ,下面的断点图也能说明这一点。

这里先说明一下,用注解 @RequestMapping 定义的 Handler ,用的是 RequestMappingHandlerMapping ,上面的其他两种,用的是 BeanNameUrlHandlerMapping ,静态资源的请求,用的是 SimpleUrlHandlerMapping 。

这地方我们可以从 Spring 的角度考虑,Spring 容器在启动的时候,会去扫描所有的组件,并把它们实例化。当 Spring 容器发现一个方法用 @RequestMapping 注解标注的时候,就用 RequestMappingHandlerMapping 这个类去实例化,当发现一个类实现了 org.springframework.web.servlet.mvc.Controller 这个接口的时候,就用 BeanNameUrlHandlerMapping 去实例化,然后将所有请求放在一个Map里,用请求路径(比如:/index)和对应的 Handler 做映射处理,这样是不是更好理解。

HandlerMapping的作用:主要是根据 request 请求匹配/映射上能够处理当前 request 的 Handler .

下面来看一下如何根据 request 来获取 HandlerMapping

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;
	}

下面是对 /index 请求的断点调试图,我们从图中可以看出, this.handlerMappings 里面有4个类,有一个为重复的。

循环这个List,判断这个 /index 请求是由哪个 Handler 来处理(即查找 HandlerMapping 的过程)。

通过循环 HandlerMapping 来获取 HandlerExecutionChain ,再次强调,因为 spring 当中存在的 Handler 有多种形式,我们处理 request 需要通过 HandlerExecutionChain 来反射执行 Handler 当中的方法,所以不同的 Handler 需要 new 不同的 HandlerExecutionChain ,那么问题来了 HandlerExecutionChain 不知道你的 Handler 是什么类型(因为 HandlerExecutionChain 里只定义了一个 Object handler 属性,它不知道你的 Handler 是什么类型的),但是 HandlerMapping 知道,所以 HandlerExecutionChain 的实例化必须依赖 HandlerMapping 。

好,讲到这终于明白 HandlerMapping 的干嘛的了,至于如何根据 /index 去找对应的 Handler 和 HandlerExecutionChain ,这里就不做介绍啦。

在这里插入图片描述

那上面几个 HandlerMapping 是怎么来的呢? Spring 容器在初始化的过程中,会调用到 initStrategies 中的 initHandlerMappings(context)、initHandlerAdapters(context); 这两个方法。我们在源码包的 DispatcherServlet.properties 文件下会看见, 它定义了图片里的这些属性。 第一个属性,就是我们刚看见的 HandlerMappings , 也就是说 HandlerMappings 是 SpringMVC 事先定义好的, Spring容器 会帮我们创建。至于第二个属性,也就是 HandlerAdapter 。

在这里插入图片描述

介绍完 HandlerMapping 之后,下面就要来介绍 HandlerAdapter 了。

HandlerAdapter的作用:

因为 Spring MVC 中的 Handler 可以有多种实现形式,但是 Servlet 需要的处理方法的结构却是固定的,都是以 request 和 response 作为方法入参,那么如何让固定参数的 Servlet 处理方法调用灵活的 Handler 来进行处理呢?这就需要 HandlerAdapter 来做适配。

为什么需要HandlerAdapter?

前面说过不同的请求会获取到不同的 Handler ,那么不同的 Handler 它是怎么实现处理不同的请求的呢?我的第一反应是抽象出一个接口,定义一个公共接口,然后让每个 Handler 实现这个接口,我想的没问题吧,但 Spring 不是这么做的,为什么呢?

再次强调: Spring MVC 的 Handler ( Controller接口,HttpRequestHandler,@RequestMapping、Servlet )有多种表现形式,不同的 Handler ,处理请求的方式是不一样的,注解 @RequestMapping 方式使用的是用方法处理请求,而实现 Controller 接口和 HttpRequestHandler 接口方式使用的是一个类,而适配器模式就能模糊掉具体的实现,从而就能提供统一访问接口,所以这地方就要使用适配器了。

这样做的好处有两个 (1)、处理器程序,也就是 Handler ,允许的是任意的Object,只要返回封装好的 HandlerExecutionChain ,具体的 Handler 不用管;(2)、集成第三方请求处理器的时候,本处代码也无需修改,加个适配器就行(PS:这地方可以参考文章最后的模拟程序)

HandlerMapping 的源码也说明了这一点。 HandlerMapping 接口里面只有一个 getHandler() 方法,而且返回类型是 HandlerExecutionChain ,用 HandlerExecutionChain 里面定义了一个 Object 类型的 handler 属性,并对 handler 进行了封装,在每个请求里加入了拦截器链。然后将这个 HandlerExecutionChain 里面的 handler 传给了 HandlerAdapter 。

这地方我们可以换个角度,就是万一处理请求的每个方法不一样怎么办?支持扩展的话,是不是就需要适配器模式了

说了这么多,是不是终于知道为什么需要 HandlerAdapter 了。

在得到 Handler 之后,就是下面的这行代码,我们来看一下 getHandlerAdapter() 方法

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
protected HandlerAdapter getHandlerAdapter(Object handler) 
				throws ServletException {
	if (this.handlerAdapters != null) {
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(handler)) {
				return adapter;
			}
		}
	}
}

从代码中能看到,从一个 this.handlerAdapters 属性里面遍历了我们的适配器。这个 handlerAdapters 哪来的呢? 跟上面的 this.HandlerMappings 一样,在 SpringMVC 的配置文件里面配置的,也就是上图中的第二个属性。

在这里插入图片描述

实现 org.springframework.web.servlet.mvc.Controller 接口形式的处理器,对应的 HandlerMapping 是 BeanNameUrlHandlerMapping ,对应的 HandlerAdapter 是 HttpRequestHandlerAdapter

在这里插入图片描述

实现 org.springframework.web.HttpRequestHandler 接口形式的处理器,对应的 HandlerMapping 也是 BeanNameUrlHandlerMapping ,对应的 HandlerAdapter 也是 HttpRequestHandlerAdapter 。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

最后看一下三个适配器中的 supports() 和 handle() 方法

SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter 适配 org.springframework.web.servlet.mvc.Controller 这种 Handler 。

源码非常之简单,它是一个非常古老的适配器,几乎已弃用状态。

因为它直接处理的就是源生的 HttpServletRequest 和 HttpServletResponse ,所以它和 Servlet容器 是强绑定的。

无数据自动封装、校验等一系列高级功能,所以实际应用中此种方式很少被使用。

// 适配`org.springframework.web.servlet.mvc.Controller`这种Handler
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}
	// 最终执行逻辑的还是Handler啊~~~~
	@Override
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return ((Controller) handler).handleRequest(request, response);
	}
}

HttpRequestHandlerAdapter

HttpRequestHandlerAdapter 适配 org.springframework.web.HttpRequestHandler 这种 Handler 。

它比 Controller 方式还源生。 它和上面的唯一不同是: return null 。

那是因为 HttpRequestHandler#handleRequest() 它没有返回值,这就需要全靠开发者自己写 response ,而 Controller 最起码来说还有 Model和View 自动渲染的能力。

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;
	}

RequestMappingHandlerAdapter

RequestMappingHandlerAdapter 主要是支持到了 org.springframework.web.method.HandlerMethod 这种 handler ,显然这种处理器也是我们最最最最为常用的,它已经把 HandlerMethod 的实现精确到了使用 @RequestMapping 注解标注的方法。

这个类,我们要查看它的父类 AbstractHandlerMethodAdapter 。

public class AbstractHandlerMethodAdapter {
	// 只处理HandlerMethod 类型的处理器。抽象方法supportsInternal默认返回true
	// 是留出的钩子可以给你自己扩展的
	@Override
	public final boolean supports(Object handler) {
		return (handler instanceof HandlerMethod 
			&& supportsInternal((HandlerMethod) handler));
	}
	@Override
	public final ModelAndView handle(HttpServletRequest request, 
					HttpServletResponse response, Object handler)
			throws Exception {
		// 抽象方法交给子类handleInternal去实现
		return handleInternal(request, response, (HandlerMethod) handler);
	}
}

看完之后,再来读一下 DispatcherServlet#doDispatch() 方法的分发流程,看看 DispatcherServlet 是如何使用 HandlerMapping和HandlerAdapter 。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) 
							throws Exception {
		...
		//1、根据URL(当然不一定非得是URL)匹配到一个处理器
		mappedHandler = getHandler(processedRequest);
		if (mappedHandler == null) {
			// 若匹配不到Handler处理器,就404了
			noHandlerFound(processedRequest, response);
			return;
		}
		//2、从HandlerExecutionChain里拿出Handler(注意是Object类型哦~ )然后找到属于它的适配器
		HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
		...
		//3、执行作用在此Handler上的所有拦截器的Pre方法
		if (!mappedHandler.applyPreHandle(processedRequest, response)) {
			return;
		}
		//4、真正执行handle方法(也就是你自己书写的逻辑方法),得到一个ModelAndView
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
		//5、视图渲染
		applyDefaultViewName(processedRequest, mv);
		//6、执行拦截器的post方法(可见它是视图渲染完成了才会执行的哦~)
		mappedHandler.applyPostHandle(processedRequest, response, mv);
		...
		//7、执行拦截器的afterCompletion方法(不管抛出与否)
	}

从执行步骤中可以看到: HandlerAdapter 对于执行流程的通用性起到了非常重要的作用,它能把任何一个 Handler(注意是Object类型) 都适配成一个 HandlerAdapter ,从而可以做统一的流程处理,这也是为何 DispatcherServlet 它能作为其它 web处理框架 的分发器的原因,因为它没有耦合具体的处理器,你完全可以自己去实现。

四、模拟HandlerMapping/HandlerAdapter的适配器模式

如果上面的讲法,你还是不懂,下面就用适配器模式模拟一下,这两个类的具体调用情况,应该会一目了然。下面是依赖关系的类图。其中 Controller 代表的就是 HandlerMapping 。具体代码,可以在文章最后下载。

在这里插入图片描述

//多种Controller实现  
public interface Controller {
}
// 注意这里每个实现,都用了不同的方法名, 如果都用一样的话,就可以放到接口中了
class HttpController implements Controller {
	public void doHttpHandler() {
		System.out.println("http...");
	}
}
class SimpleController implements Controller {
	public void doSimplerHandler() {
		System.out.println("simple...");
	}
}
class AnnotationController implements Controller {
	public void doAnnotationHandler() {
		System.out.println("annotation...");
	}
}
// 定义一个Adapter接口 
public interface HandlerAdapter {
	public boolean supports(Object handler);
	public void handle(Object handler);
}
// 多种适配器类
class SimpleHandlerAdapter implements HandlerAdapter {
	public void handle(Object handler) {
		((SimpleController) handler).doSimplerHandler();
	}
	public boolean supports(Object handler) {
		return (handler instanceof SimpleController);
	}
}
class HttpHandlerAdapter implements HandlerAdapter {
	public void handle(Object handler) {
		((HttpController) handler).doHttpHandler();
	}
	public boolean supports(Object handler) {
		return (handler instanceof HttpController);
	}
}
class AnnotationHandlerAdapter implements HandlerAdapter {
	public void handle(Object handler) {
		((AnnotationController) handler).doAnnotationHandler();
	}
	public boolean supports(Object handler) {
		return (handler instanceof AnnotationController);
	}
}
public class DispatchServlet {
	public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();
	public DispatchServlet() {
		handlerAdapters.add(new AnnotationHandlerAdapter());
		handlerAdapters.add(new HttpHandlerAdapter());
		handlerAdapters.add(new SimpleHandlerAdapter());
	}
	public void doDispatch() {
		// 此处模拟SpringMVC从request取handler的对象,
		// 适配器可以获取到希望的Controller
		 HttpController controller = new HttpController();
		// AnnotationController controller = new AnnotationController();
		//SimpleController controller = new SimpleController();
		// 得到对应适配器
		HandlerAdapter adapter = getHandler(controller);
		// 通过适配器执行对应的controller对应方法
		adapter.handle(controller);
	}
	public HandlerAdapter getHandler(Controller controller) {
		//遍历:根据得到的controller(handler), 返回对应适配器
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(controller)) {
				return adapter;
			}
		}
		return null;
	}
	public static void main(String[] args) {
		new DispatchServlet().doDispatch(); // http...
	}
}

注意: Controller 接口的每个实现类,都用了不同的方法名, 这样的话就需要用到适配器模式了,如果都用一样的话,就可以放到接口中了,这样是不是可以理解 SpringMVC 中此处的 HandlerAdapter 了

五、小结

还是做个小结吧。 SpringMVC 的 Handler 有多种实现方式(Controller,HttpRequestHandler,Servlet等),例如继承Controller接口的形式,基于注解@Controller控制器方式的,HttpRequestHandler方式的。

由于实现方式不一样,调用方式就不确定。

  • 继承 Controller 方式所使用的HandlerMapping:BeanNameUrlHandlerMapping,
  • 继承 Controller 方式所使用的适配器:SimpleControllerHandlerAdapter 、
  • 注解方式@Controller的HandlerMapping器:RequestMappingHandlerMapping
  • 注解方式@Controller适配器:RequestMappingHandlerAdapter、

这是一一对应的。

如果说的不对的,反馈一下给我啊,谢谢……

到此这篇关于SpringMVC中的HandlerMapping和HandlerAdapter详解的文章就介绍到这了,更多相关HandlerMapping和HandlerAdapter内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java的logback自定义日志脱敏组件详解

    Java的logback自定义日志脱敏组件详解

    这篇文章主要介绍了Java的logback自定义日志脱敏组件详解,一个项目在书写了很多打印日志的代码,但是后面有了脱敏需求,如果我们去手动改动代码,会花费大量时间,如果引入本组件,完成配置即可轻松完成脱敏,需要的朋友可以参考下
    2023-11-11
  • java微信小程序步数encryptedData和开放数据解密的实现

    java微信小程序步数encryptedData和开放数据解密的实现

    这篇文章主要介绍了java微信小程序步数encryptedData和开放数据解密的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Java Stream API 使代码更出色的操作完全攻略

    Java Stream API 使代码更出色的操作完全攻略

    这篇文章主要介绍了Java Stream API 使代码更出色的操作完全攻略,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • Java8 使用CompletableFuture 构建异步应用方式

    Java8 使用CompletableFuture 构建异步应用方式

    这篇文章主要介绍了Java8 使用CompletableFuture 构建异步应用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Cookie在Java中的使用

    Cookie在Java中的使用

    Cookie又称“小甜饼”,类型为“小型文本文件”,指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。由用户客户端计算机暂时或永久保存的信息。本文将讲解Cookie在Java中的使用,感兴趣的朋友可以了解下
    2021-05-05
  • springboot切面添加日志功能实例详解

    springboot切面添加日志功能实例详解

    在本篇文章里小编给大家整理的是关于springboot 切面添加日志功能的相关知识点内容,有需要的朋友们可以参考下。
    2019-09-09
  • Java服务器端跨域问题解决方案

    Java服务器端跨域问题解决方案

    这篇文章主要介绍了java服务器端跨域问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • MyBatis标签之Select resultType和resultMap详解

    MyBatis标签之Select resultType和resultMap详解

    这篇文章主要介绍了MyBatis标签之Select resultType和resultMap,在MyBatis中有一个ResultMap标签,它是为了映射select标签查询出来的结果集,下面使用一个简单的例子,来介绍 resultMap 的使用方法,需要的朋友可以参考下
    2022-09-09
  • Java基础Map集合详析

    Java基础Map集合详析

    这篇文章主要介绍了Java基础Map集合详析,主要通过介绍Map集合的常用方法、Map的获取方法的一些相关资料展开内容,需要的小伙伴可以参考一下
    2022-04-04
  • Java图片与二进制相互转换实现示例讲解

    Java图片与二进制相互转换实现示例讲解

    这篇文章主要介绍了Java图片与二进制相互转换实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-03-03

最新评论