springmvc篇:【一篇看懂DispatcherServlet中的handler、handlermapping和adapter】

前言:

       这里为什么要单独介绍一下handler,handlermapping,adapter呢?我们知道springmvc主要是来接收请求,处理请求的。
那么怎么知道哪个请求应该使用哪个controller来处理,是通过什么策略来进行匹配的的,最后又是怎么知道调用controller里面的那个方法的,这些其实就是handler,handlermapping,adapter这三者搞出来的。

在前面的【springmvc DispatcherServlet】文章中,我们有提到过,DispatcherServlet的init方法会从容器中去找我们声明的handlerMapping和adapter相关的javabean,如果没有,就是用默认的,如下:

springmvc篇:【一篇看懂DispatcherServlet中的handler、handlermapping和adapter】_第1张图片

三者的关系

  • 处理请求的类就成为handler,例如springmvc中我们的controller就属于handler。当然要成为handler也不一定非要是controller。只要满足handler的要求,他就可以成为handler。
  • 有了handler后,怎么又怎么能知道我们spring-mvc.xml中配合的bean或者使用java配置的方式声明的bean,那些属于handler?,每个handler又能处理那个请求呢?这就是handlermapping的作用。每个handlermapping类,实际上是提供了一种策略或者是约定,只要在xml中或者其他方式配置的bean,满足他的约定,他就可以自动把我们下你给要处理的url和handler联系起来。用BeanNameUrlHandlerMapping来举个例子,当DispatcherServlet中初始化的时候,会从容器中handlermapping,如果找不到就会使用默认的BeanNameUrlHandlerMapping,BeanNameUrlHandlerMapping的策略就是,如果你在spring-mvc.xml中声明的bean,id以/开头,例如下面的样子: 
         
    那么BeanNameUrlHandlerMapping就认为你后面的这个class就属于一个handler,并且id的值/user就是作为映射的url,也就是说将来请求的如果是/user,那么便会使用BeanNameURLHandlerMappingController来处理这个请求

                             springmvc篇:【一篇看懂DispatcherServlet中的handler、handlermapping和adapter】_第2张图片
           对于其他的handlermapping,会有各自的不同的识别和绑定url和handler的策略和约定。这里只要知道handlermapping的             作用即可。在使用中,你可以通过在xml中声明不同的handlermapping的类,让他们把我们handler和请求映射起来。也就             是说我们可以指定使用不同的handlermapping或者自定义handlermapping

  • 通过上面二者,url和handler就映射绑定成功了,但是要用handler里的什么方法来处理这个请求呢?也就是实际调用handler中处理请求的方法的猪脚就是adapter了,也就是说,你的handler仅仅满足handlermapping的规则和策略进行了url映射还不行,因为处理请求的最终用的是handler里面的方法。那么到底应该用什么方法?是自己随便写方法?还是必须重写一个规定的方法呢?这就取决于你的handler属于那种adapter了。怎么知道我们写的handler属于那种adapter呢。下面以SimpleControllerHandlerAdapter为例来解释一下,先看代码:      springmvc篇:【一篇看懂DispatcherServlet中的handler、handlermapping和adapter】_第3张图片


    红框部分就决定了我们的handler属于哪个adapter,如果你的handler实现了Controller接口,那么你就属于  SimpleControllerHandlerAdapter了,属于这个adapter,那么我们的handler就必须有handlerRequest这样一个方法(handle方法中调用的),以后用来处理请求(其实这个方法就是Controller接口中的方法,因为实现一个接口,肯定就必须重写这个方法)。这也就保证SimpleControllerHandlerAdapter的handle方法中调用不会出错。说白了就是面向接口编程,你的handler如果属于我这个接口(supports为true),然后就用这个方法来处理请求。因此只要我们写的controller重写了这个handleRequest方法,就可以在这个方法中处理请求了。具体实现,下面会有例子。

 

关系都挑明了,下面来看看springmvc包中默认都带了那些handlemapping和adapter,然后我们再通过自定义一些handler,通过例子来详细说明一下。

springmvc默认的提供的handlermapping和adatper,一般都放在spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet\handler包下面。下面通过uml来展示这个包下的handlermapping相关的类和接口。如下:

  • handlermapping:

                                      springmvc篇:【一篇看懂DispatcherServlet中的handler、handlermapping和adapter】_第4张图片

       另外还有spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet\mvc\method\annotation 包下的   
       RequestMappingHandlerMapping等.

  • adapter
    SimpleServletHandlerAdapter 实现HandlerAdapter接口

最后我们就通过例子来说明handler,和默认的handlermapping,adapter三者的用法。

1.BeanNameUrlHandlerMapping
   首先,什么时候会使用这个handlermapping,看下面源码中的说明
 

/**
	 * Initialize the HandlerMappings used by this class.
	 * 

If no HandlerMapping beans are defined in the BeanFactory for this namespace, * we default to BeanNameUrlHandlerMapping. */ private void initHandlerMappings(ApplicationContext context) {}

也就是说如果在spring容器中找不到HandlerMapping,就会默认使用BeanNameUrlHandlerMapping。

其次,这个类有什么作用?这个类会从spring容器中(就是xml中或者java配置类声明的bean)查找beanname以/开头的bean(什么是beanname?name就是beanname,如果没有name属性,id就是beanname),如果找到了,就会以这个beanname(/hello)为映射的key,如果以后浏览器发送的请求(/hello)正好与这个key相同,那么就用这个key对应的handler来处理这个请求(至于用哪个方法来处理,就要看adapter了)。

也就是说,使用了BeanNameUrlHandlerMapping后,springmvc会默认认为,用户想告诉它,xml中只要按照这个格式写的bean,那么这个bean就是用来处理请求的,处理那个请求?就是处理这个bean的beanname中那个字符串对应的请求。

例子一:

要使用BeanNameUrlHandlerMapping,那么我们在xml中不要声明其他的HandlerMapping,否则不会生效。

spring-mvc.xml


BeanNameURLHandlerMappingController.java

/**
* 自定义的用来处理请求的handler
*/
public class BeanNameURLHandlerMappingController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 接受请求后,跳转到hello.jsp页面,然后在页面打印出Hello World!
		ModelAndView model = new ModelAndView("hello");
		model.addObject("message", "Hello World!");
		return model;
	}
}

只需这多么两步,我们在自己写的handler中接收到/hello的请求进行处理了。

也许你可能会问,前面不是说了么,通过handlermapping只能绑定某个请求由那个handler来处理,但是具体用handler中的哪个方法来处理,必须要有adapter才行么,为什么这里没看到,你就说只需要上面的两步就可以处理请求了,你还没指定处理请求的方法呢,怎么就保证请求一定会进入到handleRequest这个方法进行处理。那么下面来解开真像,有请第三者SimpleControllerHandlerAdapter,同上问题,它是什么时候被加载的呢?我们并没有生命过这个类呀?其实这个Adapter还有其他的几个Adapter都是在DispatcherServlet.properties中指定的,然后在DispatchServlet的static静态的块中就实例化放到容器中了,在initHandlerAdapters方法中直接拿了出来,代码如下:

private void initHandlerAdapters(ApplicationContext context) {
	this.handlerAdapters = null;
    // 如果要加载所有的adapter
	if (this.detectAllHandlerAdapters) {
        // 这里便把dispatchservlet.properites中的adapter都加载了,包括了我们说的
        // SimpleControllerHandlerAdapter
		Map matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, 
                                                       HandlerAdapter.class, true, false);
	}
    // 如果不想加载默认properites中的adapter,而是想加载自己定义的adapter,那么就在
    // xml中定义名字为handlerAdapter的bean
	else {
        //从容器中取出名为handlerAdapter的adapter,也即是说我们可以自定义adapter来使用
		HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, 
                                             HandlerAdapter.class);
		this.handlerAdapters = Collections.singletonList(ha);	
	}
    有部分代码此处省略了。。。
}

从代码中可以看出这里会有三个adapter:

  • HttpRequestHandlerAdapter
  • SimpleControllerHandlerAdapter
  • AnnotationMethodHandlerAdapter

为什么会默认使用了SimpleControllerHandlerAdapter呢,最简单的方式就是看名字,名字中有个Controller,就是说你的handler如果实现了Controller这个接口,那么就会用这个Adapter ^_^,再从看下面DispatcherServlet中获取adapter的代码和SimpleControllerHandlerAdapter的代码来看看为什么我们的例子中最后用的是SimpleControllerHandlerAdapter:

/**
* DispatcherServlet接收到请求,获取到handler后,便会通过handler来获取对应的adapter,
* 因为最后要通过adapter才能真正实现调用handler中的那个方法处理请求
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	// 遍历所有adapter,决定使用哪个apdapter,至于怎么决定,就在support方法
    for (HandlerAdapter ha : this.handlerAdapters) {
        // 如果这个handler符合当前adapter的规则,那么就用这个adapter来调用handler中的方法来处理 
        // 请求
		if (ha.supports(handler)) {
			return ha;
		}
	}
	throw new ServletException("No adapter for handler ");
}

                     springmvc篇:【一篇看懂DispatcherServlet中的handler、handlermapping和adapter】_第5张图片

从上面代码看出, 因为我们的handler(BeanNameURLHandlerMappingController)实现了Controller接口,所以handler instanceof Controller变会是true。所以就会选择SimpleControllerHandlerAdapter来指定调用handler中的那个方法来处理请求。
调用handler中的哪个方法呢?在这个apdater的hand方法中指定的是handlRequest方法来处理(正好是实现Controller接口重写的方法)。这里知道了本质后,让我们再来举一反三,我们可不可以自定义个一个ControllerHandlerAdapter呢?然后让这个adapter调用handler中的其他方法来处理请求,而不是用重写的Controller接口来处理请求。

第一步:修改web.xml,将detectAllHandlerAdapters属性设置为false,这样才能使用我们自己定义的adapter

web.xml

    spring-mvc
        org.springframework.web.servlet.DispatcherServlet
        .... 其他的配置省略
        // 设置DispatcherServlet类中的detectAllHandlerAdapters属性为false
        // 只有设置到false才能使用自己定义的adapter
        
          detectAllHandlerAdapters
          false
        
		1
		.... 其他的配置省略

第二步:自定义一个adapter,一定要实现HandlerAdapter接口

/**
 * 除了handle方法,其他两个方法从SimpleControllerHandlerAdapter复制过来就行
 *
 */
public class MyHandlerAdapter implements HandlerAdapter {

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

	@Override
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// 这里指定调用我们自己handler中的一个普通的方法来处理请求,而不是用接口中的方法了。
	    // 但是不提倡这样写,耦合太深,这里只是为了觉例子而且,实际不建议这样写
		return ((BeanNameURLHandlerMappingController) handler).myHandleRequest(request, response);
	}

	@Override
	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}
}

第三步:改造上面的BeanNameURLHandlerMappingController类,添加一个让我们自定义的adapter调用的方法

public class BeanNameURLHandlerMappingController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView model = new ModelAndView("hello");
		model.addObject("message", "Hello World!");
		return model;
	}
	
	/**
	 * 自定义的apdater将调用这个方法来处理请求
	 */
	public ModelAndView myHandleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView model = new ModelAndView("hello");
		model.addObject("message", "Hello World!");
		return model;
	}

	
}

第四步:在spring-mvc.xml中声明这个adapter



通过上面四步后,我们会看到,当发起/hello请求后,DispatcherServlet中便会使用我们自己定义adapter来调用handler(BeanNameURLHandlerMappingController)中的myHandleRequest方法来处理请求了。

如果你明白了举一反三的这个例子中,那么相信handler、handlermapping和adpter你就应该懂得差不多了。

 

下面我将继续写其他的例子
例子二 SimpleUrlHandlerMapping的使用:
看过上面BeanNameURLHandlerMapping的介绍后,大家有没有这么一个感受,就是spring-mvc.xml中声明handler的时候,如下:
 
如果使用BeanNameURLHandlerMapping生成url和handler的映射,如果想多个请求都映射到同一个handler中处理,那么就需要声明多次,这样就有点太啰嗦了。所以这就引入了SimpleUrlHandlerMapping,使用它来替代默认的BeanNameURLHandlerMapping,所以需要在xml中声明一个,有了它,我们只要定义一个handler,然后通过做在声明的SimpleUrlHandlerMapping的bean
中将多个不同的url指向这个handler即可,相关代码如下:

public class SimpleUrlHandlerMappingHandler  implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ModelAndView model = new ModelAndView("hello");
		model.addObject("message", "Hello World!");
		return model;
	}
}

spring-mvc.xml

    
	
		
			
				myHandler
				myHandler
				myHandler
			
		
	

	

通过上面的配置后,我们请求/welcome,/welcome2还有/*/welcome都会调用SimpleControllerHandlerAdapter中的myHandleRequest方法来处理请求了。那么用的是哪个adpater呢,用的还是SimpleControllerHandlerAdapter,因为我们的handler还是实现了Controller接口。只要实现了Controller接口,一定就会是SimpleControllerHandlerAdapter

例子三 RequestMappingHandlerMapping:

要想让RequestMappingHandlerMapping起作用,要先在spring-mvc.xml中加上,然后在DispatcherServlet的initHandlerMappings方法中便会看到RequestMappingHandlerMapping这个类了。有了这个类,也就是说你只要按照他的规则进行编写,那么它就可以识别出你想要那个url映射到那个handler来处理请求了。图片如下:
springmvc篇:【一篇看懂DispatcherServlet中的handler、handlermapping和adapter】_第6张图片


关于为什么会让RequestMappingHandlerMapping生效,这里稍作说明,详细的请参考【】专栏详解

程序启动时,在读取spring-mvc.xml中的的标签时,会根据标签的名字去找到对应解析器类,然后这个解析器类中便会实现特定的一些业务,比如向容器中注册一些对象,初始化一些数据等。
比如我们在spring-mvc.xml中写了,那么程序启动时,在读取spring-mvc.xml文件中的内容时,读到标签时,便会使用已经注册好的AnnotationDrivenBeanDefinitionParser这个类,去生成RequestMappingHandlerMapping放到容器中,另外还会注册其他的一些类。


下面是例子的代码:
RequestMappingHandlerMappingController .java

@Controller
public class RequestMappingHandlerMappingController {
	
	@RequestMapping("/hello")
	@ResponseBody
	public String helloAndView(){
		/*ModelAndView model = new ModelAndView("hello");
		model.addObject("message", "Hello World!");*/
		return "hello";
	}

}

spring-mvc.xml

	

	 

然后浏览器发送/hello请求,便可以得到响应了。

考虑一个问题,为什么这个RequestMappingHandlerMappingController 会被符合RequestMappingHandlerMapping的规则,变成一个handler呢,看下面他的一个代码:

/**
	 * {@inheritDoc}
	 * Expects a handler to have a type-level @{@link Controller} annotation.
	 */
	@Override
	protected boolean isHandler(Class beanType) {
		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
				AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
	}

正如注释上说的,如果你容器中的对象上含有一个@Controller注解,那么便可以把你这个类当作是一个符合RequestMappingHandlerMapping规定的handler,便可以处理浏览器发来的请求(所以如果是在xml中定义javabean是不行的)。

你可能感兴趣的:(SpringMVC)