SpringMVC中HandlerMapping和HandlerAdapter详解(适配器模式)

一、引言

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

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

对上图做一下简单总结:

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

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

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

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

二、SpringMVC中定义Controller的方式

在介绍HandlerMappingHandlerAdapter之前,先来说一下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:BeanNameUrlHandlerMappingRequestMappingHandlerMapping,当然还有其他HandlerMapping,下面的断点图也能说明这一点。

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

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

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

下面来看一下如何根据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去找对应的HandlerHandlerExecutionChain ,这里就不做介绍啦。
在这里插入图片描述
那上面几个HandlerMapping是怎么来的呢?Spring容器在初始化的过程中,会调用到initStrategies中的 initHandlerMappings(context)、initHandlerAdapters(context);这两个方法。我们在源码包的DispatcherServlet.properties文件下会看见, 它定义了图片里的这些属性。 第一个属性,就是我们刚看见的HandlerMappings, 也就是说 HandlerMappingsSpringMVC事先定义好的,Spring容器会帮我们创建。至于第二个属性,也就是HandlerAdapter
在这里插入图片描述
介绍完HandlerMapping之后,下面就要来介绍HandlerAdapter了。

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

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

再次强调:Spring MVCHandlerController接口,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接口形式的处理器,对应的HandlerMappingBeanNameUrlHandlerMapping,对应的HandlerAdapterHttpRequestHandlerAdapter
在这里插入图片描述

实现org.springframework.web.HttpRequestHandler接口形式的处理器,对应的HandlerMapping也是 BeanNameUrlHandlerMapping,对应的HandlerAdapter 也是 HttpRequestHandlerAdapter在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
最后看一下三个适配器中的supports()handle()方法

SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter适配org.springframework.web.servlet.mvc.Controller这种Handler。源码非常之简单,它是一个非常古老的适配器,几乎已弃用状态。因为它直接处理的就是源生的HttpServletRequestHttpServletResponse,所以它和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

五、小结

还是做个小结吧。
SpringMVCHandler有多种实现方式(Controller,HttpRequestHandler,Servlet等),例如继承Controller接口的形式,基于注解@Controller控制器方式的,HttpRequestHandler方式的。由于实现方式不一样,调用方式就不确定。

,上面的其他两种,用的是``,这地方要说明一下,静态资源的请求,用的是SimpleUrlHandlerMapping`==。

继承 Controller 方式所使用的HandlerMappingBeanNameUrlHandlerMapping
继承 Controller 方式所使用的适配器:HttpRequestHandlerAdapter
注解方式@ControllerHandlerMapping器:RequestMappingHandlerMapping
注解方式@Controller适配器:RequestMappingHandlerAdapter

这是一一对应的。

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

代码下载:

https://github.com/Hofanking/spring-boot-demo

发布了123 篇原创文章 · 获赞 230 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/zxd1435513775/article/details/103000992