HandlerAdapter

2023-11-20

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

类结构如下:

1、SimpleServletHandlerAdapter实际就是执行HttpServlet的service方法
springMVC源码分析–SimpleServletHandlerAdapter(二)

2、SimpleControllerHandlerAdapter实际就是执行Controller的handleRequest方法
springMVC源码分析–SimpleControllerHandlerAdapter(三)

3、HttpRequestHandlerAdapter实际就是执行HttpRequestHandler的handleRequest方法 springMVC源码分析–HttpRequestHandlerAdapter(四)

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");  
                }  
            }  
        }  

2.根据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默认的适配器没有他,默认的是前面的三种。

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

感觉写的像屎一样

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

HandlerAdapter 的相关文章

随机推荐

  • C语言程序——字符串常量的输出

    文章目录 前言 一 字符串常量的输出 二 程序实例 1 程序代码 2 运行结果 3 结果分析 三 拓展应用 总结 前言 C语言没有提供字符串变量 但是字符串可以作为一个整体进行输出 一 字符串常量的输出 字符串是比较特殊的数据 在C语言中可
  • 【PIPE】流水线设计中的基本模块

    大概分成以下几节 1 概述及协议 2 valid forward valid超前 3 bubble collapse 消除气爆 4 input output skid 不知中文怎么说 5 pipe halt 流水停顿 6 idle pres
  • Git上克隆代码并运行

    新手使用git 开启创建者模式 在本地需要放置的文件夹处右键选择 Git Bash Here 1 克隆项目 git clone xxxxx git 复制的url 2 在下拉代码的前端文件夹安装依赖 npm install 需要已经安装nod
  • 【H5】 svg画扇形饼图

    H5 svg画扇形饼图 效果图如下 封装代码如下 代码内有详细注解哦
  • Android 获取网络速度

    一 效果图 一 通过wifimanager来获取WiFi的当前速度状态 WifiManager wm WifiManager NetworkSignalDetectionActivity this getApplicationContext
  • 第二次用烤箱做了面包

    可松做成了法棍 味道还不错 img http dl iteye com upload attachment 314370 296dbfa1 491e 3df9 93c9 77d709a25b38 jpg img
  • get和post区别

    1 GET请求在URL中传送的参数是有长度限制的 而POST没有 2 GET相对于POST来说不安全 因为参数直接暴露在URL上 所以不能用来传递敏感信息 而POST数据不会显示在URL中 是放在Request body中 3 对参数的数据
  • 有关 sscanf 和 sprintf 的用法

    sscanf 的用法 用法 int sscanf const char str const char format 功能 从字符串读取格式化输入 返回值 如果成功 该函数返回成功匹配和赋值的个数 如果到达文件末尾或发生读错误 则返回 EOF
  • C语言单向循环链表的建立

    1 头文件 include
  • 【数学建模】线性规划模型基本原理与案例分享

    1 1 线性规划问题 在人们的生产实践中 经常会遇到如何利用现有资源来安排生产 以取得最大经济效益的问题 此类问题构成了运筹学的一个重要分支 数学规划 而线性规划 Linear Programming 简记LP 则是数学规划的一个重要分支
  • 国产CPU对比

    关于国产CPU 龙芯 飞腾 鲲鹏 海光 申威 兆芯 CPU 是计算机系统的核心和大脑 n CPU 即中央处理器是计算机的运算和控制核心 其功能主要是解释计算机指令以及处理计算机软件中的数据 CISC实际上是以增加处理器本身复杂度作为代价 去
  • Jenkins系列:3、wsl/ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序

    Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 文章目录 Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 1 前言 2 wsl
  • 数组去重合并

    let arrA id 1 name name1 id 2 name name2 let arrB id 1 name name3 id 3 name name4 function concatArr arrA arrB 只需要拿到A
  • ANDROID版本号和版本名称的重要性介绍

    转载请注明出处http blog csdn net y150481863 article details 41249159 来自 http blog csdn net y150481863 当我们在刚开始学习ANDROID的时候 可能不会过
  • DVWA安装配置教程

    原文传送门 http www cnblogs com yaochc p 5049832 html DVWA 安装教程 1 直接下载WampServer 免去了需要安装apache php mysql的服务器软件的痛苦 一体集成 相当于安装了
  • CSRF漏洞详细说明

    CSRF漏洞详细说明 通常情况下 有三种方法被广泛用来防御CSRF攻击 验证token 验证HTTP请求的Referer 还有验证XMLHttpRequests里的自定义header 鉴于种种原因 这三种方法都不是那么完美 各有利弊 二 C
  • H.265/HEVC编码结构

    H 265 HEVC编码结构 为了增强各种应用下操作的灵活性以及数据损失的鲁棒性 H 265 HEVC在编解码的设计上添加了多种新的语法结构 相较于以往的视频编码标准 如H 264 AVC 这种新的语法架构使得H 265 HEVC在压缩效率
  • linux虚拟机可以ping通,但是无法socket连接

    场景 两台windows各开一台Linux虚拟机 通过路由器组网 分配桥接地址 出现电脑之间ping不通 socket不通怎么办 答案 1 关掉windows防火墙 任何阻止联网的行为统统取消 2 关掉linux防火墙 对 etc seli
  • 狂飙!GPT-4最新20+个应用案例集锦,附视频

    编者按 自OpenAI于3月15日重磅推出GPT 4 一石激起千层浪 全球开发者 创业者们迅速尝试了各种形形色色的场景应用 来体验它的极限 游戏 编程 客户关系 营销 财务 家庭生活 饮食 文学艺术创作等等不一而足 笔者从中筛选了23款基于
  • HandlerAdapter

    HandleAdapter HandlerAdapter的功能实际就是执行我们的具体的Controller Servlet或者HttpRequestHandler中的方法 类结构如下 1 SimpleServletHandlerAdapte