前面的章节中我们讲过springmvc中handler,handerMapping和handlerApdater的作用和关系,那么本章中,我们主要看一下springmvc中自带的一些HandlerApdater的实现类,以及如何自定义一个adapter来调用controller中的方法的。
我们知道在DispatcherServlet中,发起请求后,会先通过url到handlerMapping中获取handler,然后再拿着这个handler去所有的handlerAdapter中通过isSupport方法找到属于自己的adapter,那后通过这个apdater指定调用handler中的那个方法来处理请求。
下面先通过一张图片,来看一下HandlerAdapter有哪些实现类,如下图:
下面会直接通过源码和实例两部分,类对每个handlerAdapter的实现类进行说明。如果对于adapter还不提明白,请看前面的文章。
- SimpleControllerHandlerAdapter
从源码的handle方法可知,只要你的Controller实现了Controller接口,并重写接口的handleRequest(request,reponse)方法,那么便会被这个Adapter调用。不知道Adatper的作用,请看我前面文章的介绍。public class SimpleControllerHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof Controller); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 只要你的controller实现了Controller接口,重写的方法(handleRequest)就可以处理请求 return ((Controller) handler).handleRequest(request, response); } ...省略部分代码... }
例子:
spring-mvc.xml
BeanNameURLHandlerMappingController.java<bean id="/hello" class="com.lhb.controller.BeanNameURLHandlerMappingController"/>
/** * 用来处理请求的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; } }
hello.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> <span>${message}</span> </body> </html>
验证:浏览器输入localhost:8088//hello 进入到controller的handleRequest方法为成功.
总结:这种方式一个controller只能映射一个url的处理 - SimpleServletHandlerAdapter
根据源码的注释,这个adatper默认是不会被激活的,如果想使用,需要我们把它注入到容器中才行,也就是在spring-mvc.xml中通过bean标签声明一下。使用了这个adapter后,当我们的controller实现了Servlet接口后,那么当浏览器发送请求后,便可以通过这个adatper去调用controller中的service方法去处理请求了。/** * <p>This adapter is not activated by default; it needs to be defined as a * bean in the DispatcherServlet context. It will automatically apply to * mapped handler beans that implement the Servlet interface then. */ public class SimpleServletHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof Servlet); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((Servlet) handler).service(request, response); return null; } ...省略部分... }
实例:
spring-mvc.xml<!--使用这个adapter必须要声明一下。因为默认是不自动激活这个adpater的--> <bean class="org.springframework.web.servlet.handler.SimpleServletHandlerAdapter"/> <!--上面apdater将会调用这个handler来处理请求,请求为/mySimpleServlet,将跟这个controller映射,这是因为这个写法符合BeanNameUrlHandlerMapping的规则,会自动将id变为url,将class作为handler了就,再不明白看前面相关的文章--> <bean id="/mySimpleServlet" class="com.lhb.controller.SimpleServletHandlerController"/>
SimpleServletHandlerController.java
public class SimpleServletHandlerController implements Servlet{ ... 部分忽略... @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { res.getWriter().write("i am simple servlet handler controller"); res.flushBuffer(); } ... 部分忽略... }
验证:
浏览器输入:http://localhost:8088/mySimpleServlet 打印出 i am simple servlet handler controller 成功 -
HttpRequestHandlerAdapter.java
/** * * */ 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; } ...部分省略... }
通过源码可知,其原理跟上面的一样,只要我们的controller(handler)实现了HttpReqeustHandler接口,那么就会自动使用这个adapter来调用controller中的handlerRequest方法来处理请求
实例:
spring-mvc.xml<!--id的值中必须以/开头,这样就可以自动被BeanNameURLHandlerMapping处理,将id最为请求,class作为处理请求的handler类,adpater最终会调用这个handler中的handleRequest方法处理请求--> <bean id="/myhttpRequestHandler" class="com.lhb.controller.HttpRequestAdapterController" />
HttpRequestAdapterController.java
public class HttpRequestAdapterController implements HttpRequestHandler{ @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write("i am HttpRequestAdapterController" ); response.flushBuffer(); } }
验证:
浏览器输入:http://localhost:8088/myhttpRequestHandler 打印出i am HttpRequestAdapterController 则成功 -
AnnotationMethodHandlerAdapter
/** * @deprecated as of Spring 3.2, in favor of * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter RequestMappingHandlerAdapter} */ @Deprecated public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter, Ordered, BeanFactoryAware { }
从源码注释中可以看到,此adapter已经被废弃,不推荐使用了,而是同过RequestMappingHandlerAdapter来代替,所以这里就不多做介绍了。让我们直接看RequestMappingHandlerAdapter。
-
RequestMappingHandlerAdapter->AbstractHandlerMethodAdapter
当我们使用@Controller来注解一个类,让它成为处理请求的handler后,那么当使用@RequestMapping来进行url和方法映射后,当发起请求时,都是由RequestMappingHandlerAdapter这个来实现请求对应方法的调用,以及调用结果的处理等操作。源码如下:
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered { @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 { return handleInternal(request, response, (HandlerMethod) handler); } ...省略部分... }
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean { @Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); //调用controller中的处理请求的方法 if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { //调用controller中的处理请求的方法 mav = invokeHandlerMethod(request, response, handlerMethod); } } else { //调用controller中的处理请求的方法 mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; } }
上面两块代码只是把如何根据请求调用controller里面方法的原码放了上去,从AbstractHandlerMethodAdapter 源码可以看出,根据多态的特性,handle方法中最终调用的是子类RequestMappingHandlerAdapter 中的handleInternal来处理请求的。
实例:@Controller public class RequestMappingHandlerMappingController { @RequestMapping("/hello") @ResponseBody public String helloAndView(){ return "hello"; } }
验证:
浏览器输入: http://localhost:8088/hello 后,页面显示出hello
到这里有可能会有人有疑问,前面的adatper后者handler需要在spring-mvc.xml中通过bean标签声明,为什么这里只是写了这么一点代码?这是因为,当在spring-mvc.xml中使用<mvc:annotation-driven/>时,便会自动把RequestMappingHandlerAdapter和RequestMappingHandlerMapping注入到容器中,然后就会自动把含有RequestMapping注解方法与url映射好,发起请求时,通过RequestMappingHandlerAdapter来调用对应的处理请求的方法即可。多以就不需要在speing-mvc.xml中声明了。 -
MyHandlerAdapter
为什么最后讲这个adapter,因为这个我么自己定义的adapter,并不是springmvc默认自带的.他是让我们来举一反三的,会用别人的,还需要学会自己做一个自己的。话不多说,线上例子,在解释
实例:
MyHandlerAdapter.java/** * 除了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.java
public class BeanNameURLHandlerMappingController implements Controller { /** * 自定义的MyHandlerAdapter将调用这个方法来处理 */ public ModelAndView myHandleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView model = new ModelAndView("hello"); model.addObject("message", "Hello World!"); return model; } }
hello.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> 你好 </body> </html>
spring-mvc.xml<!--自定义的MyHandlerAdapter将调用我们这个controller的方法来处理/myHanler这个请求--> <bean id="/myHanler" class="com.lhb.controller.BeanNameURLHandlerMappingController"/> <!--这里的id必须为这个固定的值,因为springmvc源码的DispatcherServlet会在容器中找这个名字的adapter--> <bean id="handlerAdapter" class="com.lhb.MyHandlerAdapter"/>
web.xml
<servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <init-param> <!--自定义adapter必须将这个属性设置为false,至于为什么下面将放源码解释--> <param-name>detectAllHandlerAdapters</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> <multipart-config> <location>/tmp</location> <max-file-size>5242880</max-file-size> <max-request-size>20971520</max-request-size> <file-size-threshold>0</file-size-threshold> </multipart-config> </servlet> <servlet-mapping> <servlet-name>spring-mvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
验证:
在浏览器输入http://localhost:8088/myHanler,跳转到hello.jsp页面为成功
现在来解释下,在使用自定义adapter时,必须在web.xml中将detectAllHandlerAdapters属性设置为false,并且在spring-mvc.xml中为什么声明我们自定义的adatper时,id必须写为handlerAdapter,看下面DispatcherServlet的原码:public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter"; private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; //是否检测所有的adapter,如果为true便会将容器中所有的adatper拿出来 if (this.detectAllHandlerAdapters) { Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); } // 如果为false,那么就只会在容器中查找那个名为handlerAdapter的adpater else { HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } }
从源码看出,如果只想使用我们自定义的这个adapter的话,那么就必须通过web.xml现将detectAllHandlerAdapters设置为false。然后便会从容器找我们声明的名字为handlerAdapter的那个adapter了。