前言:
这里为什么要单独介绍一下handler,handlermapping,adapter呢?我们知道springmvc主要是来接收请求,处理请求的。
那么怎么知道哪个请求应该使用哪个controller来处理,是通过什么策略来进行匹配的的,最后又是怎么知道调用controller里面的那个方法的,这些其实就是handler,handlermapping,adapter这三者搞出来的。
在前面的【springmvc DispatcherServlet】文章中,我们有提到过,DispatcherServlet的init方法会从容器中去找我们声明的handlerMapping和adapter相关的javabean,如果没有,就是用默认的,如下:
三者的关系
对于其他的handlermapping,会有各自的不同的识别和绑定url和handler的策略和约定。这里只要知道handlermapping的 作用即可。在使用中,你可以通过在xml中声明不同的handlermapping的类,让他们把我们handler和请求映射起来。也就 是说我们可以指定使用不同的handlermapping或者自定义handlermapping
红框部分就决定了我们的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相关的类和接口。如下:
另外还有spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet\mvc\method\annotation 包下的
RequestMappingHandlerMapping等.
最后我们就通过例子来说明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?
也就是说,使用了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:
为什么会默认使用了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 ");
}
从上面代码看出, 因为我们的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中加上
关于为什么
程序启动时,在读取spring-mvc.xml中的
比如我们在spring-mvc.xml中写了
下面是例子的代码:
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是不行的)。