SpringMVC中的HandlerAdapter解析

 更新时间:2023年10月12日 09:47:51   作者:菜鸡旭旭  
这篇文章主要介绍了SpringMVC中的HandlerAdapter解析,HandlerAdapter是一个关键的组件,用于将请求与处理程序方法进行适配和调度,它充当了控制器和处理程序之间的桥梁,负责将请求的参数和处理程序方法进行匹配,并将结果返回给前端,需要的朋友可以参考下

HandleAdapter

HandlerAdapter的功能实际就是执行我们的具体的Controller、Servlet或者HttpRequestHandler中的方法。

类结构如下:

1、SimpleServletHandlerAdapter实际就是执行HttpServlet的service方法

2、SimpleControllerHandlerAdapter实际就是执行Controller的handleRequest方法

3、HttpRequestHandlerAdapter实际就是执行HttpRequestHandler的handleRequest方法

4、RequestMappingHandlerAdapter实际就是执行@RequestMapping注解的方法。

5、AnnotationMethodHandlerAdapter已结被废弃,就不做过多介绍

该接口有3个方法

    public interface HandlerAdapter {  
        boolean supports(Object handler);  
        ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;  
        long getLastModified(HttpServletRequest request, Object handler);  
    }  

HandlerAdapter的执行操作,其执行过程在DispatcherServlet的doDispatch中,执行流程如下:

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  
            ........  
            try {  
                try {  
                    //获取合适的HandlerAdapter实现类  
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  
                ........  
                    if (isGet || "HEAD".equals(method)) {  
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());  
                    }  
                ........  
                    //执行真正的请求操作  
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  
            ........  
        }  

HandlerAdapter处理3步

1.DispatcherServlte会根据配置文件信息注册HandlerAdapter。

如果在配置文件中没有配置,那么DispatcherServlte会获取HandlerAdapter的默认配置, 如果是读取默认配置的话,DispatcherServlte会读取DispatcherServlte.properties文件, 该文件中配置了三种HandlerAdapter:HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter。 DispatcherServlte会将这三个HandlerAdapter对象存储到它的handlerAdapters这个集合属性中,这样就完成了HandlerAdapter的注册。

2.DispatcherServlte会根据handlerMapping传过来的controller与已经注册好了的HandlerAdapter一一匹配, 看哪一种HandlerAdapter是支持该controller类型的,如果找到了其中一种HandlerAdapter是支持传过来的controller类型, 那么该HandlerAdapter会调用自己的handle方法, handle方法运用java的反射机制执行controller的具体方法来获得ModelAndView, 例如SimpleControllerHandlerAdapter是支持实现了controller接口的控制器,如果自己写的控制器实现了controller接口,那么SimpleControllerHandlerAdapter就会去执行自己写控制器中的具体方法来完成请求。

分析handlerAdapter注册

//初始化handlerAdapters 放在一个链表中
    private void initHandlerAdapters(ApplicationContext context) {  
            this.handlerAdapters = null;  
            if (this.detectAllHandlerAdapters) {  
                // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.  
                Map<String, HandlerAdapter> matchingBeans =  
                        BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);  
                if (!matchingBeans.isEmpty()) {  
                    this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());  
                    // We keep HandlerAdapters in sorted order.  
                    OrderComparator.sort(this.handlerAdapters);  
                }  
            }  
            else {  
                try {  
                    HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);  
                    this.handlerAdapters = Collections.singletonList(ha);  
                }  
                catch (NoSuchBeanDefinitionException ex) {  
                    // Ignore, we'll add a default HandlerAdapter later.  
                }  
            }  
            // Ensure we have at least some HandlerAdapters, by registering  
            // default HandlerAdapters if no other adapters are found.  
            if (this.handlerAdapters == null) {  
                this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);  
                if (logger.isDebugEnabled()) {  
                    logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");  
                }  
            }  
        }  

3.根据handlerMapping传过来的Handler对象与DispatcherServlet集合属性handlerAdapter中的HandlerAdapter一一匹配,如果有支持Handler对象的HandlerAdapter,那么HandlerAdapter就会调用自己的handle方法处理请求。

rotected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {  
        for (HandlerAdapter ha : this.handlerAdapters) {  
            if (logger.isTraceEnabled()) {  
                logger.trace("Testing handler adapter [" + ha + "]");  
            }  
            if (ha.supports(handler)) {  
                return ha;  
            }  
        }  
        throw new ServletException("No adapter for handler [" + handler +  
                "]: Does your handler implement a supported interface like Controller?");  
    }  

在运行的过程中发现SimpleControllerHandlerAdapter是支持Controller类型的控制器的。

SimpleControllerHandlerAdapter

    public class SimpleControllerHandlerAdapter implements HandlerAdapter {  
        public boolean supports(Object handler) {  
            return (handler instanceof Controller);  
        }  
        public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)  
                throws Exception {  
            return ((Controller) handler).handleRequest(request, response);  
        }  
        public long getLastModified(HttpServletRequest request, Object handler) {  
            if (handler instanceof LastModified) {  
                return ((LastModified) handler).getLastModified(request);  
            }  
            return -1L;  
        }  
    }  

Controller源码,Controller接口只有一个handleRequest方法

    public interface Controller {  
        ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; 
    }  

再看看实现了Controller接口的AbstractController类

    public abstract class AbstractController extends WebContentGenerator implements Controller {  
        private boolean synchronizeOnSession = false;  
        /** 
         * Set if controller execution should be synchronized on the session, 
         * to serialize parallel invocations from the same client. 
         * <p>More specifically, the execution of the <code>handleRequestInternal</code> 
         * method will get synchronized if this flag is "true". The best available 
         * session mutex will be used for the synchronization; ideally, this will 
         * be a mutex exposed by HttpSessionMutexListener. 
         * <p>The session mutex is guaranteed to be the same object during 
         * the entire lifetime of the session, available under the key defined 
         * by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a 
         * safe reference to synchronize on for locking on the current session. 
         * <p>In many cases, the HttpSession reference itself is a safe mutex 
         * as well, since it will always be the same object reference for the 
         * same active logical session. However, this is not guaranteed across 
         * different servlet containers; the only 100% safe way is a session mutex. 
         * @see org.springframework.web.servlet.mvc.AbstractController#handleRequestInternal 
         * @see org.springframework.web.util.HttpSessionMutexListener 
         * @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession) 
         */  
        public final void setSynchronizeOnSession(boolean synchronizeOnSession) {  
            this.synchronizeOnSession = synchronizeOnSession;  
        }  
        /** 
         * Return whether controller execution should be synchronized on the session. 
         */  
        public final boolean isSynchronizeOnSession() {  
            return this.synchronizeOnSession;  
        }  
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)  
                throws Exception {  
            // Delegate to WebContentGenerator for checking and preparing.  
            checkAndPrepare(request, response, this instanceof LastModified);  
            // Execute handleRequestInternal in synchronized block if required.  
            if (this.synchronizeOnSession) {  
                HttpSession session = request.getSession(false);  
                if (session != null) {  
                    Object mutex = WebUtils.getSessionMutex(session);  
                    synchronized (mutex) {  
                        return handleRequestInternal(request, response);  
                    }  
                }  
            }  
            return handleRequestInternal(request, response);  
        }  
        /** 
         * Template method. Subclasses must implement this. 
         * The contract is the same as for <code>handleRequest</code>. 
         * @see #handleRequest 
         */  
        protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)  
            throws Exception;  
    }  

再看一下继承了AbstractController的MultiActionController,MultiActionController中有对AbstractController的handleRequestInternal的实现

    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)  
                throws Exception {  
            try {  
             //找寻该请求对象的方法名   推测是反射   
                String methodName = this.methodNameResolver.getHandlerMethodName(request);  
                //根据方法名返回视图
                return invokeNamedMethod(methodName, request, response);  
            }  
            catch (NoSuchRequestHandlingMethodException ex) {  
                return handleNoSuchRequestHandlingMethod(ex, request, response);  
            }  
        }  

可以看出在MultiActionController的handleRequestInternal方法中分为两步,第一步是找寻执行该请求的方法名,第二步是调用invokeNamedMethod方法。

invokeNamedMethod方法源码

    protected final ModelAndView invokeNamedMethod(  
                String methodName, HttpServletRequest request, HttpServletResponse response) throws Exception {  
        //获取方法对象  来自于handlerMethodMap
            Method method = this.handlerMethodMap.get(methodName);  
            if (method == null) {  
            //没有该请求方法名
                throw new NoSuchRequestHandlingMethodException(methodName, getClass());  
            }  
            try {  
             //参数列表
                Class[] paramTypes = method.getParameterTypes();  
                List<Object> params = new ArrayList<Object>(4);  
                params.add(request);  
                params.add(response);  
                if (paramTypes.length >= 3 && paramTypes[2].equals(HttpSession.class)) {  
                    HttpSession session = request.getSession(false);  
                    if (session == null) {  
                        throw new HttpSessionRequiredException(  
                                "Pre-existing session required for handler method '" + methodName + "'");  
                    }  
                    //session加入到params
                    params.add(session);  
                }  
                // If last parameter isn't of HttpSession type, it's a command.  
                if (paramTypes.length >= 3 &&  
                        !paramTypes[paramTypes.length - 1].equals(HttpSession.class)) {  
                    Object command = newCommandObject(paramTypes[paramTypes.length - 1]);  
                    params.add(command);  
                    //绑定
                    bind(request, command);  
                }  
                            //执行该方法对象的invoke  
                Object returnValue = method.invoke(this.delegate, params.toArray(new Object[params.size()]));  
                //判断返回值类型
                return massageReturnValueIfNecessary(returnValue);  
            }  
            catch (InvocationTargetException ex) {  
                // The handler method threw an exception.  
                return handleException(request, response, ex.getTargetException());  
            }  
            catch (Exception ex) {  
                // The binding process threw an exception.  
                return handleException(request, response, ex);  
            }  
        }  

根据方法名在MultiActionController的方法集合属性handlerMethodMap中寻找对应的方法对象,然后执行该方法对象,

执行该方法对象后获得一个object的返回值,通过massageReturnValueIfNecessary方法判断这个返回值的类型,

如果这个值的返回类型是ModelAndView类型,就返回ModelAndView。到此我们找到响应请求的方法并执行获得了返回值。

虽然总体走完了。 但是有两个地方还没有说,

1如何根据用户发来的url请求来确定url中哪一段是执行该请求的方法名;

2.确定方法名后是怎么找到该方法的。

handlerMethodMap咋来的

MultiActionController中有一个构造函数, registerHandlerMethods(this.delegate);方法就是对我们所写的controller中的方法的注册。

    public MultiActionController() {  
        this.delegate = this;  
        registerHandlerMethods(this.delegate);  
        // We'll accept no handler methods found here - a delegate might be set later on.  
    }  

registerHandlerMethods方法

    private void registerHandlerMethods(Object delegate) {  
        this.handlerMethodMap.clear();  
        this.lastModifiedMethodMap.clear();  
        this.exceptionHandlerMap.clear();  
        // Look at all methods in the subclass, trying to find  
        // methods that are validators according to our criteria  
        Method[] methods = delegate.getClass().getMethods();  
        for (Method method : methods) {  
            // We're looking for methods with given parameters.  
            if (isExceptionHandlerMethod(method)) {  
                registerExceptionHandlerMethod(method);  
            }  
            else if (isHandlerMethod(method)) {  
                registerHandlerMethod(method);  
                registerLastModifiedMethodIfExists(delegate, method);  
            }  
        }  
    }  

ParameterMethodNameResolver这个类的作用就是根据url链接中带的参数来确定执行该url的方法名是什么。在ioc容器初始ParameterMethodNameResolver的时候,容器会将“m”这个参数赋值给ParameterMethodNameResolver的属性paramName,然后ParameterMethodNameResolver会根据url中m后面跟的参数来获取方法名

public String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException {  
        String methodName = null;  
        // Check parameter names where the very existence of each parameter  
        // means that a method of the same name should be invoked, if any.  
        if (this.methodParamNames != null) {  
            for (String candidate : this.methodParamNames) {  
                if (WebUtils.hasSubmitParameter(request, candidate)) {  
                    methodName = candidate;  
                    if (logger.isDebugEnabled()) {  
                        logger.debug("Determined handler method '" + methodName +  
                                "' based on existence of explicit request parameter of same name");  
                    }  
                    break;  
                }  
            }  
        }  
        // Check parameter whose value identifies the method to invoke, if any.  
        if (methodName == null && this.paramName != null) {  
            methodName = request.getParameter(this.paramName);  
            if (methodName != null) {  
                if (logger.isDebugEnabled()) {  
                    logger.debug("Determined handler method '" + methodName +  
                            "' based on value of request parameter '" + this.paramName + "'");  
                }  
            }  
        }  
        if (methodName != null && this.logicalMappings != null) {  
            // Resolve logical name into real method name, if appropriate.  
            String originalName = methodName;  
            methodName = this.logicalMappings.getProperty(methodName, methodName);  
            if (logger.isDebugEnabled()) {  
                logger.debug("Resolved method name '" + originalName + "' to handler method '" + methodName + "'");  
            }  
        }  
        if (methodName != null && !StringUtils.hasText(methodName)) {  
            if (logger.isDebugEnabled()) {  
                logger.debug("Method name '" + methodName + "' is empty: treating it as no method name found");  
            }  
            methodName = null;  
        }  
        if (methodName == null) {  
            if (this.defaultMethodName != null) {  
                // No specific method resolved: use default method.  
                methodName = this.defaultMethodName;  
                if (logger.isDebugEnabled()) {  
                    logger.debug("Falling back to default handler method '" + this.defaultMethodName + "'");  
                }  
            }  
            else {  
                // If resolution failed completely, throw an exception.  
                throw new NoSuchRequestHandlingMethodException(request);  
            }  
        }  
        return methodName;  
        }

当找到了方法名后,就会去MultiActionController属性handlerMethodMap中根据方法名找方法对象。再执行这个方法就好了。

HandlerAdapter,大家都叫它适配处理器,就是适配不同的处理器,将他们封装起来调用同一个接口方法, 这样DispatcherServlet就只需要调用接口方法,而不需要在DispatcherServlet判断调用哪一个具体的HandlerAdapter的实现类了。

当时看到自己项目里面的所有的处理器都是实现了MultiActionController的Controller, 再去看MultiActionController的源码时,发现MultiActionController实现了Controller接口, Controller接口只有一个handleRequest方法, 我想DispatcherServlet直接用Controller的handleRequest方法执行具体请求就行了,何必还要用HandlerAdapter将Controller封装起来, 再在HandlerAdapter的handle方法里执行Controller的handleRequest方法呢,这不是多此一举?

只怪自己目光短浅,由于用的是配置的方式来做项目的, 而且平时自己写的Controller只继承了MultiActionController, 以为Controller接口就是所有的处理器的接口,眼里就只有Controller了。

今天再来看源码,发现处理器根本就不只有Controller这一种。 还有HttpRequestHandler,Servlet等处理器。

下面来介绍一下几种适配器对应的处理器以及这些处理器的作用

1. AnnotationMethodHandlerAdapter主要是适配注解类处理器,注解类处理器就是我们经常使用的@Controller的这类处理器

2. HttpRequestHandlerAdapter主要是适配静态资源处理器,静态资源处理器就是实现了HttpRequestHandler接口的处理器,这类处理器的作用是处理通过SpringMVC来访问的静态资源的请求。

3.SimpleControllerHandlerAdapter是Controller处理适配器,适配实现了Controller接口或Controller接口子类的处理器,比如我们经常自己写的Controller来继承MultiActionController,那么自己写的这些Controller就会由SimpleControllerHandlerAdapter来适配

4.SimpleServletHandlerAdapter是Servlet处理适配器,适配实现了Servlet接口或Servlet的子类的处理器,我们不仅可以在web.xml里面配置Servlet,其实也可以用SpringMVC来配置Servlet,不过这个适配器很少用到,而且SpringMVC默认的适配器没有他,默认的是前面的三种。

适配处理器当然用到了适配器模式。

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

相关文章

  • Spring boot基于JPA访问MySQL数据库的实现

    Spring boot基于JPA访问MySQL数据库的实现

    本文主要介绍了Spring boot基于JPA访问MySQL数据库的实现,Spring boot结合Jpa 能够简化创建 JPA 数据访问层和跨存储的持久层功能,用户的持久层Dao接口只需要继承定义好的接口,感兴趣的可以了解一下
    2021-06-06
  • 基于Java语言的递归运算例题详解

    基于Java语言的递归运算例题详解

    一个方法在执行过程中调用自身, 就称为 "递归"。本文将通过几个例题带大家深入了解一下Java语言中的递归运算,感兴趣的可以了解一下
    2022-08-08
  • SpringBoot结合dev-tool实现IDEA项目热部署的流程步骤

    SpringBoot结合dev-tool实现IDEA项目热部署的流程步骤

    这篇文章主要给大家介绍了SpringBoot结合dev-tool实现IDEA项目热部署的流程步骤,文章通过图文介绍的非常详细,对大家的学习有一定的帮助,需要的朋友可以参考下
    2023-10-10
  • struts2实现多文件上传的示例代码

    struts2实现多文件上传的示例代码

    本篇文章主要介绍了struts2实现多文件上传的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • 使用springboot的jar包能够以service方式启动

    使用springboot的jar包能够以service方式启动

    这篇文章主要介绍了使用springboot的jar包能够以service方式启动,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Mybatis动态SQL之IF语句详解

    Mybatis动态SQL之IF语句详解

    这篇文章主要给大家介绍了关于Mybatis动态SQL之IF语句的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • Java面试最容易被刷的重难点之锁的使用策略

    Java面试最容易被刷的重难点之锁的使用策略

    锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂。因为锁(以及其它更高级的线程同步机制)是由synchronized同步块的方式实现的,所以我们还不能完全摆脱synchronized关键字
    2021-10-10
  • SpringBoot+MybatisPlus+Mysql+JSP实战

    SpringBoot+MybatisPlus+Mysql+JSP实战

    这篇文章主要介绍了SpringBoot+MybatisPlus+Mysql+JSP实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • Spring boot使用多线程过程步骤解析

    Spring boot使用多线程过程步骤解析

    这篇文章主要介绍了Spring boot使用多线程过程步骤解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • java开发命名规范总结

    java开发命名规范总结

    包名的书写规范 (Package)推荐使用公司或机构的顶级域名为包名的前缀,目的是保证各公司/机构内所使用的包名的唯一性。包名全部为小写字母,且具有实际的区分意义
    2013-10-10

最新评论