Spring MVC 源码分析

news2024/12/24 3:30:49

Spring MVC 源码分析

  • 1. 回顾Servlet
    • 1.1. 什么是Servlet
    • 1.2. Servlet工作模式
    • 1.3. Servlet的工作原理
    • 1.4. 源码分析
      • 1.4.1. Servlet接口
      • 1.4.2. GenericServlet抽象类
      • 1.4.3. HttpServlet抽象类
    • 1.5. Servlet的局限性
  • 2. Spring MVC简介
    • 2.1. 什么是MVC
    • 2.2. 什么是Spring MVC?
    • 2.3. Spring MVC核心组件
    • 2.4. Spring MVC工作流程
  • 3. Spring MVC源码分析
    • 3.1. FrameworkServlet抽象类
    • 3.2. DispatcherServlet类
    • 3.3. 获取HandlerExecutionChain对象
    • 3.4. 根据Handler获取HandlerAdapter
    • 3.5. 调用拦截器的preHandle方法
    • 3.6. 通过HandlerAdapter调用Handler实际处理请求,获取ModelAndView对象
    • 3.7. 调用拦截器的postHandle方法
    • 3.8. 处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端
      • 3.8.1. 位置
      • 3.8.2. processDispatchResult方法源码
      • 3.8.3. 如果有异常,进行全局异常处理
      • 3.8.4. 如果有异常,进行全局异常处理
      • 3.8.5. 调用拦截器的 afterCompletion 方法

1. 回顾Servlet

1.1. 什么是Servlet

Servlet is an API that provides many interfaces and classes including documentation.

Servlet is an interface that must be implemented for creating any Servlet.

Servlet is a class that extends the capabilities of the servers and responds to the incoming requests. It can respond to any requests.

其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类。

1.2. Servlet工作模式

  1. 客户端发送请求至服务器;
  2. 服务器启动并调用Servlet,Servlet根据客户端请求生成响应内容并将其传给服务器;
  3. 服务器将响应返回客户端;

1.3. Servlet的工作原理

  1. Servlet接口定义了Servlet与servlet容器之间的契约。这个契约是:Servlet容器将Servlet类载入内存,并产生Servlet实例和调用它具体的方法。但是要注意的是,在一个应用程序中,每种Servlet类型只能有一个实例。
  2. 用户请求致使Servlet容器调用Servlet的service()方法,并传入一个ServletRequest对象和一个ServletResponse对象。ServletRequest对象和ServletResponse对象都是由Servlet容器(例如TomCat)封装好的,并不需要程序员去实现,程序员可以直接使用这两个对象。
  3. ServletRequest中封装了当前的Http请求,因此,开发人员不必解析和操作原始的Http数据。ServletResponse表示当前用户的Http响应,程序员只需直接操作ServletResponse对象就能把响应轻松的发回给用户。
  4. 对于每一个应用程序,Servlet容器还会创建一个ServletContext对象。这个对象中封装了上下文(应用程序)的环境详情。每个应用程序只有一个ServletContext。每个Servlet对象也都有一个封装Servlet配置的ServletConfig对象。

1.4. 源码分析

1.4.1. Servlet接口

最原始的Servlet接口有三个主要方法:init、service和destroy;

/**
 * Defines methods that all servlets must implement.
 *
 * @author 	Various
 *
 * @see 	GenericServlet
 * @see 	javax.servlet.http.HttpServlet
 *
 */

public interface Servlet {

    /**
     * Called by the servlet container to indicate to a servlet that the 
     * servlet is being placed into service.
     */

    public void init(ServletConfig config) throws ServletException;

    /**
     * Returns a {@link ServletConfig} object, which contains
     * initialization and startup parameters for this servlet.
     */

    public ServletConfig getServletConfig();
    
    /**
     * Called by the servlet container to allow the servlet to respond to 
     * a request.
     */

    public void service(ServletRequest req, ServletResponse res)
	throws ServletException, IOException;

    /**
     * Returns information about the servlet, such
     * as author, version, and copyright.
     */

    public String getServletInfo();
    
    /**
     * Called by the servlet container to indicate to a servlet that the
     * servlet is being taken out of service. 
     */

    public void destroy();
}

1.4.2. GenericServlet抽象类

  1. GenericServlet defines a generic, protocol-independent servlet.

  2. 在这里插入图片描述

  3. GenericServlet抽象类包含以下主要方法,可以看出主要还是围绕着init、service和destroy;

    public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {
       
      /**
        * Called by the servlet container to indicate to a servlet that the
        * servlet is being taken out of service.  See {@link Servlet#destroy}.
        */
    
       public void destroy() { }
    
       /**
        * Called by the servlet container to indicate to a servlet that the
        * servlet is being placed into service.  See {@link Servlet#init}.
        */
    
       public void init(ServletConfig config) throws ServletException {
    		this.config = config;
    		this.init();
       }
    
       /**
        * A convenience method which can be overridden so that there's no need
        * to call <code>super.init(config)</code>.
        */
       
       public void init() throws ServletException { }
       
       /**
        * Called by the servlet container to allow the servlet to respond to
        * a request.  See {@link Servlet#service}.
        */
    
       public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
       
       /**
        * Returns the name of this servlet instance.
        */
    
       public String getServletName() {
           ServletConfig sc = getServletConfig();
           if (sc == null) {
               throw new IllegalStateException(
                   lStrings.getString("err.servlet_config_not_initialized"));
           }
    
           return sc.getServletName();
       }
    }
    
    

1.4.3. HttpServlet抽象类

  1. HttpServlet provides an abstract class to be subclassed to create an HTTP servlet suitable for a Web site.

  2. 在这里插入图片描述

  3. HttpServlet的主要内容如下,主要是提供了service方法,使得继承该类的方法无需再override。定义了doGet、doPost等一系列方法,但并未给出具体实现。

    public abstract class HttpServlet extends GenericServlet implements java.io.Serializable {
    
       /**
        * Called by the server (via the <code>service</code> method) to
        * allow a servlet to handle a GET request. 
        */
       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         String protocol = req.getProtocol();
         String msg = lStrings.getString("http.method_get_not_supported");
         if (protocol.endsWith("1.1")) {
             resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
         } else {
             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
         }
       }
    
       protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         NoBodyResponse response = new NoBodyResponse(resp);
         doGet(req, response);
         response.setContentLength();
       }
       
       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         String protocol = req.getProtocol();
         String msg = lStrings.getString("http.method_post_not_supported");
         if (protocol.endsWith("1.1")) {
             resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
         } else {
             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
         }
       }
    
       /**
        *
        * Receives standard HTTP requests from the public
        * <code>service</code> method and dispatches
        * them to the <code>do</code><i>XXX</i> methods defined in 
        * this class. This method is an HTTP-specific version of the 
        * {@link javax.servlet.Servlet#service} method. There's no
        * need to override this method.
        */
    
       protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         String method = req.getMethod();
    
         if (method.equals(METHOD_GET)) {
             long lastModified = getLastModified(req);
             if (lastModified == -1) {
           // servlet doesn't support if-modified-since, no reason
           // to go through further expensive logic
           doGet(req, resp);
             } else {
           long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
           if (ifModifiedSince < (lastModified / 1000 * 1000)) {
               // If the servlet mod time is later, call doGet()
                           // Round down to the nearest second for a proper compare
                           // A ifModifiedSince of -1 will always be less
               maybeSetLastModified(resp, lastModified);
               doGet(req, resp);
           } else {
               resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
           }
             }
    
         } else if (method.equals(METHOD_HEAD)) {
             long lastModified = getLastModified(req);
             maybeSetLastModified(resp, lastModified);
             doHead(req, resp);
    
         } else if (method.equals(METHOD_POST)) {
             doPost(req, resp);
    
         } else if (method.equals(METHOD_PUT)) {
             doPut(req, resp);	
    
         } else if (method.equals(METHOD_DELETE)) {
             doDelete(req, resp);
    
         } else if (method.equals(METHOD_OPTIONS)) {
             doOptions(req,resp);
    
         } else if (method.equals(METHOD_TRACE)) {
             doTrace(req,resp);
    
         } else {
             //
             // Note that this means NO servlet supports whatever
             // method was requested, anywhere on this server.
             //
    
             String errMsg = lStrings.getString("http.method_not_implemented");
             Object[] errArgs = new Object[1];
             errArgs[0] = method;
             errMsg = MessageFormat.format(errMsg, errArgs);
    
             resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
         }
       }
     
       /**
        * Dispatches client requests to the protected
        * <code>service</code> method. There's no need to
        * override this method.
        */
       public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
         HttpServletRequest	request;
         HttpServletResponse	response;
    
         try {
             request = (HttpServletRequest) req;
             response = (HttpServletResponse) res;
         } catch (ClassCastException e) {
             throw new ServletException("non-HTTP request or response");
         }
         service(request, response);
       }
    }
    

1.5. Servlet的局限性

Servlet如果需要给客户端返回数据,比如一个HTML文件,需要一行一行的把HTML语句给用Writer输出,早期简单的网页还能应付得住,但是随着互联网的不断发展,网站的内容和功能越来越强大,一个普通的HTML文件可能就达到好几百行,如果在采用使用Servlet去一行一行的输出HTML代码的话,将会非常的繁琐并且浪费大量的时间,且在当时,出现了PHP这种可以内嵌到HTML文件的动态语言,使得制作动态网页变得异常的简单和轻松,因此大量的程序员转上了PHP语言的道路,JAVA的份额急剧减小,当时JAVA的开发者Sun公司为了解决这个问题,也开发出了自己的动态网页生成技术,使得同样可以在HTML文件里内嵌JAVA代码,这就是现在的JSP技术。

2. Spring MVC简介

2.1. 什么是MVC

  1. MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向耦合。

  2. Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。

    View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。

    Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作。

  3. 流程:

    1. 用户通过视图层发送请求到服务器,在服务器中请求被Controller接收;
    2. Controller调用相应的Model层处理请求,处理完毕后结果返回到Controller;
    3. Controller再根据请求处理的结果找到对应的View视图,渲染数据后最终响应给浏览器;

在这里插入图片描述

  1. MVC不是一种设计模式,而是一种架构模式。

    1. 架构模式:一个架构模式描述软件系统里的基本的结构组织或纲要;架构模式提供一些事先定义好的子系统,指定他们的责任,并给出把他们组织在一起的法则和指南;
    2. 设计模式:一个设计模式提供一种提炼子系统或软件系统中的组织或者他们之间的关系的纲要设计;设计模式描述普遍存在的在相互通信的组件中重复出现的结构,这种结构解决在一定的背景中的具有一般性的设计问题;

2.2. 什么是Spring MVC?

  1. Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架。

  2. Spring MVC的特点:

    1. 轻量级,简单易学;
    2. 高效 , 基于请求响应的MVC框架;
    3. 与Spring兼容性好,无缝结合;
    4. 约定优于配置;
    5. 功能强大:RESTful、数据验证、格式化、本地化、主题等;
    6. 简洁灵活;
  3. Spring的web框架围绕DispatcherServlet设计。DispatcherServlet的作用是将请求分发到不同的处理器。

2.3. Spring MVC核心组件

  1. DispatcherServlet:负责调度其他组件的执行,可以降低不同组件之间的耦合性,是整个Spring MVC的核心模块;
  2. Handler:处理器,完成具体的业务逻辑,相当于Servlet;
  3. HandlerMapping:DispatcherServlet是通过 HandlerMapping把请求映射到不同的Handler;
  4. HandlerInterceptor:处理器拦截器,是一个接口,如果我们需要进行一些拦截处理,可以通过实现该接口完成;
  5. HandlerExecutionChain:处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果有额外拦截处理,可以添加拦截器进行设置);
  6. HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作包括表单的数据验证、数据类型转换、把表单数据封装到POJO等,这些一系列的操作都是由HandlerAdapter完成,DispatcherServlet通过HandlerAdapter执行不同的Handler;
  7. ModelAndView:封装了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet;
  8. ViewResolver:视图解析器,DispatcherServlet通过它把逻辑视图解析为物理视图,最终把渲染的结果响应给客户端;

2.4. Spring MVC工作流程

在这里插入图片描述
在这里插入图片描述

  1. 客户端向服务器发送请求,请求被 SpringMVC 前端控制器 DispatcherServlet 捕获;
  2. DispatcherServlet 根据该 URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以 HandlerExecutionChain 的形式返回;
  3. DispatcherServlet 根据获得的 Handler,选择一个合适的 HandlerAdapter;
  4. 如果成功获得 HandlerAdapter,此时将开始执行拦截器的 preHandler方法;
  5. 提取 Request 中的数据,填充 handle入参,开始执行 handle 方法,处理请求,在填充 Handler 的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作:
    1. HttpMessageConveter:将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的类型信息;
    2. 数据转换:对请求消息进行数据转换。如 String 转换成 Integer、Double 等;
    3. 数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等;
    4. 数据验证:验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 Error 中;
  6. handle方法执行完毕后,返回一个ModelAndView对象给DispatcherServlet;
  7. 此时将开始执行拦截器的 postHandle 方法【逆向】;
  8. DispatcherServlet把获取的ModelAndView对象传给ViewResolver视图解析器,来渲染视图;
  9. DispatcherServlet把渲染后的视图返回给客户端;
  10. 渲染视图完毕执行拦截器的 afterCompletion 方法【逆向】;

3. Spring MVC源码分析

3.1. FrameworkServlet抽象类

  1. FrameworkServlet is the base servlet for Spring’s web framework. Provides integration with a Spring application context, in a JavaBean-based overall solution.

  2. 在这里插入图片描述

  3. HttpServlrtBean抽象类:Simple extension of HttpServlet which treats its config parameters (init-param entries within the servlet tag in web.xml) as bean properties.

  4. 看一下FrameworkServlet抽象类的主要方法,发现把doXXX方法都统一到doService抽象方法上了;

    /**
     * Override the parent class implementation in order to intercept PATCH requests.
     */
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
    		throws ServletException, IOException {
    
    	HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    	if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
    		processRequest(request, response);
    	}
    	else {
    		super.service(request, response);
    	}
    }
    
    /**
     * Delegate GET requests to processRequest/doService.
     */
    @Override
    protected final void doGet(HttpServletRequest request, HttpServletResponse response)
    		throws ServletException, IOException {
    
    	processRequest(request, response);
    }
    
    /**
     * Delegate POST requests to {@link #processRequest}.
     */
    @Override
    protected final void doPost(HttpServletRequest request, HttpServletResponse response)
    		throws ServletException, IOException {
    
    	processRequest(request, response);
    }
    
    /**
     * Process this request, publishing an event regardless of the outcome.
     * <p>The actual event handling is performed by the abstract
     * {@link #doService} template method.
     */
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    		throws ServletException, IOException {
    
    	long startTime = System.currentTimeMillis();
    	Throwable failureCause = null;
    
    	LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    	LocaleContext localeContext = buildLocaleContext(request);
    
    	RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    	ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    
    	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    	asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    
    	initContextHolders(request, localeContext, requestAttributes);
    
    	try {
    		doService(request, response);
    	}
    	catch (ServletException | IOException ex) {
    		failureCause = ex;
    		throw ex;
    	}
    	catch (Throwable ex) {
    		failureCause = ex;
    		throw new NestedServletException("Request processing failed", ex);
    	}
    
    	finally {
    		resetContextHolders(request, previousLocaleContext, previousAttributes);
    		if (requestAttributes != null) {
    			requestAttributes.requestCompleted();
    		}
    		logResult(request, response, failureCause, asyncManager);
    		publishRequestHandledEvent(request, response, startTime, failureCause);
    	}
    }
    
    /**
     * Subclasses must implement this method to do the work of request handling,
     * receiving a centralized callback for GET, POST, PUT and DELETE.
     */
    protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
    		throws Exception;
    

3.2. DispatcherServlet类

  1. Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception handling facilities.

  2. 在这里插入图片描述

  3. DispatcherServlet类override了FrameworkServlet的doService方法,并将最终的处理流程放在了doDispatch方法里,该方法很值得研究,写了一些注释,接下来会详细分析带有标号的注释对应的代码。

    /**
     * Process the actual dispatching to the handler.
     */
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	HttpServletRequest processedRequest = request;
    	HandlerExecutionChain mappedHandler = null;
    	boolean multipartRequestParsed = false;
       
       //获取异步处理管理器,servlet3.0后支持异步处理,可以在子线程中响应用户请求
    	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
    	try {
    		ModelAndView mv = null;
    		Exception dispatchException = null;
    
    		try {
           
           // 解析multipart类型的请求,上传文件用的就是multipart类型的请求方式
    			processedRequest = checkMultipart(request);
           // 用来标记是否是multipart类型的请求
    			multipartRequestParsed = (processedRequest != request);
    
    			// 1. 获取HandlerExecutionChain对象
    			mappedHandler = getHandler(processedRequest);
           // 如果没有获取到,就抛异常
    			if (mappedHandler == null) {
    				noHandlerFound(processedRequest, response);
    				return;
    			}
    
           // 2. 根据Handler获取HandlerAdapter
    			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
           // 3. 调用拦截器的preHandle方法,若返回false,处理结束
    			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    				return;
    			}
    
           // 4. 通过HandlerAdapter调用Handler实际处理请求,获取ModelAndView对象
    			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
           // 判断异步请求不是已经开始了,开始了就返回了
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				return;
    			}
    
           // 如果mv对象中没有视图且DispatcherServlet配置了默认的视图,则给mv安排一个默认的视图
    			applyDefaultViewName(processedRequest, mv);
           // 5. 调用拦截器的postHandle方法
    			mappedHandler.applyPostHandle(processedRequest, response, mv);
    		}
    		catch (Exception ex) {
    			dispatchException = ex;
    		}
    		catch (Throwable err) {
    			dispatchException = new NestedServletException("Handler dispatch failed", err);
    		}
         // 6. 处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端
    		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    	}
    	catch (Exception ex) {
         // 7. 调用拦截器的afterCompletion方法
    		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    	}
    	catch (Throwable err) {
         // 7. 调用拦截器的afterCompletion方法
    		triggerAfterCompletion(processedRequest, response, mappedHandler,
    				new NestedServletException("Handler processing failed", err));
    	}
    	finally {
         // 对于异步处理的情况,调用异步处理的拦截器AsyncHandlerInterceptor的afterConcurrentHandlingStarted方法
    		if (asyncManager.isConcurrentHandlingStarted()) {
    			if (mappedHandler != null) {
    				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    			}
    		}
    		else {
    			//对于multipart的请求,清理资源,比如文件上传的请求,上传的过程中文件会被保存到临时文件中,这里就会对这些文件继续清理
    			if (multipartRequestParsed) {
    				cleanupMultipart(processedRequest);
    			}
    		}
    	}
    }
    

3.3. 获取HandlerExecutionChain对象

  1. // 1. 获取HandlerExecutionChain对象
    mappedHandler = getHandler(processedRequest);
    
  2. 来看一下getHandler方法:

    /**
     * Return the HandlerExecutionChain for this request.
     */
    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    	if (this.handlerMappings != null) {
    		for (HandlerMapping mapping : this.handlerMappings) {
    			HandlerExecutionChain handler = mapping.getHandler(request);
    			if (handler != null) {
    				return handler;
    			}
    		}
    	}
    	return null;
    }
    
  3. 遍历了handlerMappings,分别调用它们的getHandler方法,找到对应的HandlerExecutionChain;

  4. HandlerExecutionChain类中主要包含以下三个属性:

    // 请求处理器,通常就是我们自定义的 controller 对象及方法
    private final Object handler;
    
    // 当前请求匹配到的拦截器列表
    private List<HandlerInterceptor> interceptorList;
    
    // 拦截器索引,用来记录执行到第几个拦截器了
    private int interceptorIndex;
    
  5. 在AbstractHandlerMapping抽象类中,getHandler方法首先调用了getHandlerInternal方法,通过寻找、读取配置文件,找到request对应的handler,然后调用了getHandlerExecutionChain方法,通过路径匹配,找到对应的interceptors;

3.4. 根据Handler获取HandlerAdapter

  1.     // 2. 根据Handler获取HandlerAdapter
    	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
  2. 来看一下getHandlerAdapter方法:

    /**
     * Return the HandlerAdapter for this handler object.
     */
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    	if (this.handlerAdapters != null) {
    		for (HandlerAdapter adapter : this.handlerAdapters) {
    			if (adapter.supports(handler)) {
    				return adapter;
    			}
    		}
    	}
    	throw new ServletException("No adapter for handler [" + handler +
    			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }
    
  3. 遍历handlerAdapters,找到能够处理当前 Handler 的HandlerAdapter,如果没找到会报错;

3.5. 调用拦截器的preHandle方法

  1.    // 3. 调用拦截器的preHandle方法,若返回false,处理结束
         				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
         					return;
         				}
    
  2. 来看下applyPreHandle方法:

    /**
     * Apply preHandle methods of registered interceptors.
     */
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    	HandlerInterceptor[] interceptors = getInterceptors();
    	if (!ObjectUtils.isEmpty(interceptors)) {
    		for (int i = 0; i < interceptors.length; i++) {
    			HandlerInterceptor interceptor = interceptors[i];
    			if (!interceptor.preHandle(request, response, this.handler)) {
    				triggerAfterCompletion(request, response, null);
    				return false;
    			}
    			this.interceptorIndex = i;
    		}
    	}
    	return true;
    }
    
  3. 该方法首先循环调用拦截器的preHandle方法,如果某个拦截器的preHandle方法返回 false,则调用triggerAfterCompletion方法,且记录了拦截器的执行位置;

  4. 来看下triggerAfterCompletion方法:

    /**
     * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
     * Will just invoke afterCompletion for all interceptors whose preHandle invocation
     * has successfully completed and returned true.
     */
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
    		throws Exception {
    
    	HandlerInterceptor[] interceptors = getInterceptors();
    	if (!ObjectUtils.isEmpty(interceptors)) {
    		for (int i = this.interceptorIndex; i >= 0; i--) {
    			HandlerInterceptor interceptor = interceptors[i];
    			try {
    				interceptor.afterCompletion(request, response, this.handler, ex);
    			}
    			catch (Throwable ex2) {
    				logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
    			}
    		}
    	}
    }
    
  5. 该方法反向依次调用那些 preHandle 方法返回 ture 的拦截器的 afterCompletion 方法;

3.6. 通过HandlerAdapter调用Handler实际处理请求,获取ModelAndView对象

  1.    // 4. 通过HandlerAdapter调用Handler实际处理请求,获取ModelAndView对象
         				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
  2. 来看下handle方法,handle方法会走到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod:

    /**
     * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
     * if view resolution is required.
     */
    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    	ServletWebRequest webRequest = new ServletWebRequest(request, response);
    	try {
    		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    
    		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    		if (this.argumentResolvers != null) {
    			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    		}
    		if (this.returnValueHandlers != null) {
    			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    		}
    		invocableMethod.setDataBinderFactory(binderFactory);
    		invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    
    		ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    		modelFactory.initModel(webRequest, mavContainer, invocableMethod);
    		mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
    
    		AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    		asyncWebRequest.setTimeout(this.asyncRequestTimeout);
    
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    		asyncManager.setTaskExecutor(this.taskExecutor);
    		asyncManager.setAsyncWebRequest(asyncWebRequest);
    		asyncManager.registerCallableInterceptors(this.callableInterceptors);
    		asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
    
    		if (asyncManager.hasConcurrentResult()) {
    			Object result = asyncManager.getConcurrentResult();
    			mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
    			asyncManager.clearConcurrentResult();
    			LogFormatUtils.traceDebug(logger, traceOn -> {
    				String formatted = LogFormatUtils.formatValue(result, !traceOn);
    				return "Resume with async result [" + formatted + "]";
    			});
    			invocableMethod = invocableMethod.wrapConcurrentResult(result);
    		}
    
    		invocableMethod.invokeAndHandle(webRequest, mavContainer);
    		if (asyncManager.isConcurrentHandlingStarted()) {
    			return null;
    		}
    
    		return getModelAndView(mavContainer, modelFactory, webRequest);
    	}
    	finally {
    		webRequest.requestCompleted();
    	}
    }
    
  3. 上述方法主要做了三件事:

    1. 组装目标方法需要的参数;

      org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
      
      1. 在该方法中,使用HandlerMethodArgumentResolver进行解析请求,得到参数;

      2. HandlerMethodArgumentResolver有很多实现类,归纳如下:

        实现类对应的控制器参数说明
        PathVariableMapMethodArgumentResolver@PathVariable 标注参数从 url 中提取参数的值
        RequestHeaderMethodArgumentResolver@RequestHeader 标注参数从 http 头中提取参数值
        RequestParamMethodArgumentResolver@RequestParam 标注参数从http 请求参数中获取值
        RequestResponseBodyMethodProcessor@RequestBody 标注参数提取 body 数据,转换为参数类型
        ServletResponseMethodArgumentResolverServletResponse、OutputStream、Writer 这 3 种类型的参数这几种类型用来控制 http 请求的响应输出流
        HttpEntityMethodProcessorHttpEntityHttpEntity 类型的参数HttpEntity 中包含了 http 请求头和 body 的所有信息
        ExpressionValueMethodArgumentResolver@Value 标注的参数spel 表达式,从 spring 容器中获取值
        MapMethodProcessor参数为 Map 或者子类型-
        ModelMethodProcessor参数为 org.springframework.ui.Model 或子类型-
        ModelAttributeMethodProcessor@ModelAttribute 标注的参数-
    2. 通过反射调用处理请求的目标方法,获取方法的返回值;

      org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
      
    3. 对方法的返回值进行处理;

      org.springframework.web.method.support.HandlerMethodReturnValueHandler
      
      1. 该接口有两个方法:

        	/**
        	 * Whether the given {@linkplain MethodParameter method return type} is
        	 * supported by this handler
        	 */
        	boolean supportsReturnType(MethodParameter returnType);
        
        	/**
        	 * Handle the given return value by adding attributes to the model and
        	 * setting a view or setting the
        	 * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
        	 * to indicate the response has been handled directly.
        	 */
        	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
        
      2. 该接口具有很多实现类:在这里插入图片描述

      3. 归纳如下:

        实现类说明
        ViewNameMethodReturnValueHandler返回值为视图名称时的解析器
        MapMethodProcessor返回值为 Map 的解析器
        StreamingResponseBodyReturnValueHandler返回值为 ResponseEntity 类型时的解析器
        DeferredResultMethodReturnValueHandler返回值为 DeferredResult 类型时的解析器,表示异步请求
        CallableMethodReturnValueHandler返回值为 Callable 类型时的解析器,表示异步请求
        ModelMethodProcessor返回值为 Model 类型时的解析器
        ModelAndViewMethodReturnValueHandler返回值为 ModelAndView 类型时的解析器
        RequestResponseBodyMethodProcessor方法上标注有@ResponseBody 注解时返回值的解析器
        HttpEntityMethodProcessor返回值为 HttpEntity 类型但是非 RequestEntity 类型时的解析器
      4. 以其中的RequestResponseBodyMethodProcessor为例,来看下源码:

        	@Override
        	public boolean supportsReturnType(MethodParameter returnType) {
        		// 判断类上或者目标方法上是否有@ResponseBody注解
        		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
        				returnType.hasMethodAnnotation(ResponseBody.class));
        	}
        
        	@Override
        	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
            // 1:标注为请求已处理,因为当前handleReturnValue方法会直接将结果输出到客户端,所以后续就不需要再进行视图渲染了,表示请求已经被处理了
            mavContainer.setRequestHandled(true);
        		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
        
            // 2:将结果输出到客户端
        		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
        	}
        
      5. 注意,并不是所有的实现类的handleReturnValue方法都将结果直接返回给客户端了,有些是需要渲染的;

3.7. 调用拦截器的postHandle方法

  1.    // 5. 调用拦截器的postHandle方法
         				mappedHandler.applyPostHandle(processedRequest, response, mv);
    
  2. 来看下applyPostHandle方法:

    /**
     * Apply postHandle methods of registered interceptors.
     */
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
    		throws Exception {
    
    	HandlerInterceptor[] interceptors = getInterceptors();
    	if (!ObjectUtils.isEmpty(interceptors)) {
    		for (int i = interceptors.length - 1; i >= 0; i--) {
    			HandlerInterceptor interceptor = interceptors[i];
    			interceptor.postHandle(request, response, this.handler, mv);
    		}
    	}
    }
    
  3. 逆序调用拦截器的postHandle方法;

3.8. 处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端

3.8.1. 位置

  1. // 6. 处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端
    		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    

3.8.2. processDispatchResult方法源码

	/**
	 * Handle the result of handler selection and handler invocation, which is
	 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
	 */
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;

		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

3.8.3. 如果有异常,进行全局异常处理

  1. mv = processHandlerException(request, response, handler, exception);
    
  2. 看一下processHandlerException方法:

    /**
     * Determine an error ModelAndView via the registered HandlerExceptionResolvers.
     */
    @Nullable
    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
    		@Nullable Object handler, Exception ex) throws Exception {
    
    	ModelAndView exMv = null;
    	if (this.handlerExceptionResolvers != null) {
    		for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
    			exMv = resolver.resolveException(request, response, handler, ex);
    			if (exMv != null) {
    				break;
    			}
    		}
    	}
    	if (exMv != null) {
    		WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
    		return exMv;
    	}
    
    	throw ex;
    }
    
  3. 该方法主要是遍历HandlerExceptionResolver的resolveException方法来处理异常;

3.8.4. 如果有异常,进行全局异常处理

  1. render(mv, request, response);
    
  2. 看一下render方法:

    /**
     * Render the given ModelAndView.
     */
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    	View view;
    	String viewName = mv.getViewName();
    	if (viewName != null) {
    		// We need to resolve the view name.
    		view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    	}
    	else {
    		// No need to lookup: the ModelAndView object contains the actual View object.
    		view = mv.getView();
    	}
    	view.render(mv.getModelInternal(), request, response);
    }
    
    
  3. 该方法主要做了两件事:

    1. 调用resolveViewName方法解析视图名称得到 View;

      1. 最终是在org.springframework.web.servlet.view.UrlBasedViewResolver#buildView中拼接了prefix和suffix;
    2. 调用render方法渲染视图;

      1. 看一下render方法,发现最终走到org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel方法中,以其实现类override的方法org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel为例:

        protected void renderMergedOutputModel(
                Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
         
            // 将model中的数据遍历后放在request中(request.setAttribute(name,value))
            exposeModelAsRequestAttributes(model, request);
         
            // 获取跳转的页面的路径
            String dispatcherPath = prepareForRendering(request, response);
         
            // 调用getRequestDispatcher得到RequestDispatcher对象
            RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
         
            //实现页面跳转
            if (useInclude(request, response)) {
                rd.include(request, response);
            }else {
                rd.forward(request, response);
            }
        }
        

3.8.5. 调用拦截器的 afterCompletion 方法

  1. if (mappedHandler != null) {
     			mappedHandler.triggerAfterCompletion(request, response, null);
     		}
    
  2. 来看一下这部分源码:

    /**
     * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
     * Will just invoke afterCompletion for all interceptors whose preHandle invocation
     * has successfully completed and returned true.
     */
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
    		throws Exception {
    
    	HandlerInterceptor[] interceptors = getInterceptors();
    	if (!ObjectUtils.isEmpty(interceptors)) {
    		for (int i = this.interceptorIndex; i >= 0; i--) {
    			HandlerInterceptor interceptor = interceptors[i];
    			try {
    				interceptor.afterCompletion(request, response, this.handler, ex);
    			}
    			catch (Throwable ex2) {
    				logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
    			}
    		}
    	}
    }
    
  3. 主要是反向调用拦截器的afterCompletion方法;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/59128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深度学习】详解 BEiT

目录 摘要 一、引言 二、方法 2.1 图像表示 2.1.1 图像 patch 2.1.2 视觉 token 2.2 主干网络&#xff1a;图像 Transformer 2.3 预训练 BEiT&#xff1a;掩码图像建模 2.4 从变分自动编码器的角度来看 2.5 预训练设置 2.6 在下游视觉任务微调 BEiT 三、实验 3.…

谁还说我没表情包用?马上用Python采集上万张个表情包

前言 今天来表演一手 采集全网表情包图片 虽然我现在的wx表情包已经996个了&#xff0c;但是我还在存表情包哈哈&#xff0c;多了就继续删 现在跟人聊天&#xff0c;不发个表情包&#xff0c;我都觉得不对劲&#xff0c;怪难受的 索性今天就来&#xff0c;给你们分享一下&a…

Vue3:分析elementplus表格第一列序号hover变多选框实现思路

灵感来自Vue el-table 表格第一列序号与复选框hover切换 源码是通过Vue2elementui去实现的&#xff0c;本篇是通过Vue3elementplus实现&#xff0c;所以在代码上面有些许不同&#xff0c;但函数名一致 实现思路&#xff1a; ①通过表头是多选框&#xff0c;我们可以判定这一…

9.1、面向对象编程

文章目录面向对象编程简介面向对象编程面向对象编程的三大特性对象和类封装练习继承什么是继承重写父类方法多继承私有属性和私有方法多态项目案例&#xff1a;栈和队列的封装栈的封装队列的封装python是面向对象的编程语言 面向对象编程简介 “面向过程”(Procedure Oriente…

Java并发编程—synchronized

文章目录synchronized 的底层实现原理监视器锁对象的锁的获取过程如下&#xff1a;monitorexit&#xff1a;加synchronized锁前后对比synchronized的作用synchronized的三种主要用法synchronized为什么是 非公平锁&#xff1f;————————————————————————…

大数据项目 --- 电商数仓(一)

这个项目实在数据采集基础使用的,需要提前复习之前学的东西,否则的话就是很难继续学习.详见博客数据项目一 ---数据采集项目.大数据项目 --- 数据采集项目_YllasdW的博客-CSDN博客大数据第一个项目笔记整理https://blog.csdn.net/m0_47489229/article/details/127477626 目录 …

Android 基于物理特性动画 —— 弹簧动画

在安卓开发中我们可以通过动画添加视觉提示&#xff0c;向用户通知应用中的动态。当界面状态发生改变时&#xff08;例如有新内容加载或有新操作可用时&#xff09;&#xff0c;动画尤其有用。动画还为应用增加了优美的外观&#xff0c;使其拥有更高品质的外观和风格。 首先来…

Java并发编程—并发和并行、线程上下文

文章目录并发和并行并发和并行的区别上下文切换相关问题为什么循环次数少的情况下&#xff0c;单线程快&#xff1f;什么时候需要用多线程&#xff1f;线程上下文切换消耗的时长&#xff1f;用什么测试的线程上下文&#xff1f;面试回答下面的工具会加分&#xff1a;如何减少上…

DQL简介

学习笔记之DQL 数据查询语言&#xff0c;用来查询数据库中表的记录。 查询关键字&#xff1a;select 基本查询 1查询多个字段 select 字段1&#xff0c;字段2&#xff0c;字段3.. from 表名; select * from 表名;2设置别名 select 字段1 [别名1],字段2[别名2]... from 表名;3…

233搞懂HMM(隐马尔可夫)

文章目录2条性质3个参数3个问题维特比算法参考资料有向图模型&#xff0c;主要用于时序数据建模&#xff0c;在语音识别&#xff0c;自然语言处理等领域&#xff0c;以及在知识图谱命名实体识别中的序列标注&#xff0c;有广泛应用。 HMM模型由两部分组成&#xff0c; 观测变量…

spring boot 应用mybatis

Mybatis入门: Mybatis入门_做测试的喵酱的博客-CSDN博客 目录 一、spring boot 应用mybatis 核心 二、举例&#xff1a; 2.1 背景 2.2 项目结构&#xff1a; 2.3 依赖包 pom 2.4 项目配置文件application.yml 2.5 实例层entity 2.6 mybatis的mapper层 2.7 spring boot…

Android—过渡按钮的简单实现

Android—过渡按钮的简单实现前言准备工作登录页面(activity_main.xml)登录成功页面(activity_new.xml)主要代码给登录按钮设置监听事件(MainActivity.xml)点击登录按钮出现加载动画(TransitionButton.java)当isSuccessful判断为true时(MainActivity.xml)加载动画结束时切入跳转…

C语言笔记-16-Linux基础-文件元数据

C语言笔记-16-Linux基础-文件元数据 文章目录C语言笔记-16-Linux基础-文件元数据前言一、概述二、ln 硬连接三、软连接四、stat 获取元数据总结前言 自学笔记&#xff0c;没有历史知识铺垫&#xff08;省略百度部分&#xff09;C语言笔记-16-Linux基础-文件元数据 一、概述 文…

Kaggle泰坦尼克号-决策树Top 3%-0基础代码详解

Titanic Disaster Kaggle&#xff0c;里的经典入门题目&#xff0c;因为在学决策树所以找了一个实例学习了一下&#xff0c;完全萌新零基础&#xff0c;所以基本每一句都做了注释。 原文链接&#xff1a;Titanic: Simple Decision Tree model score(Top 3%) | Kaggle 目录 1.…

SolidWorks如何绘制环形波纹垫片

环形波纹垫片主要用于螺纹式固定件或者防滑螺纹固定中,那这个环形垫片一般怎么用SolidWorks制作呢?首先我们观察到这样的垫片是上下此起彼伏的波纹状,厚度一般1MM左右,制作起来还是有点小难度,但是通过仔细观察,我们可以看到它的大概走向,如图 也就是特征就是这边凹下去…

获取Android签名MD5的方式

形而上者谓之道&#xff0c;形而下者谓之器 我们在申请百度云/腾讯云等第三方的各种服务时&#xff0c;经常会遇到需要提供包名和签名MD5的情况。这里特地总结一下&#xff1a; 1. 获取MD5的一般方式 1.1 有签名文件(.keystore)的情况下: keytool -list -v -keystore XXX.key…

【沐风老师】3DMAX一键生成圣诞树建模插件使用教程

圣诞节快到了&#xff0c;给大家分享一款3DMax一键生成圣诞树模型插件&#xff1a; 3DMAX一键生成圣诞树建模插件&#xff0c;可以生成定制和随机两种3D圣诞树模型&#xff0c;并自动赋予材质和贴图。 【安装方法】 方法一&#xff1a;解压后直接拖动插件脚本文件到3dMax窗口…

使用springboot实现jsonp|jsonp的实现|JSONP的实现使用springboot

1、服务端&#xff1a; 1.1、项目目录&#xff1a; 1.2、pom文件&#xff1a; <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instan…

从一到无穷大 #3 对象存储.浅谈

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录引言Windows Azure StorageNosql&#xff1a;TiKV为例总结引言 天才的开源精神对于普…

67-94-hive-函数-开窗函数-常用函数-udf自定义函数

67-hive-函数&#xff1a; UDF:一进一出&#xff0c;普通函数 UDAF:多进一出&#xff0c;聚合函数 UDTF&#xff1a;一进多出&#xff0c;炸裂函数 一、多指的是输入数据的行数。一行变多行&#xff0c;多行变一行。 函数 #查看系统自带的函数 hive> show functions; …