Spring MVC 第一篇 - Controller 的方法是如何被调用的?

目录

 

前言

以 Post 请求为例探究执行流程        ​

Spring MVC 的执行流程

@RequestMapping 标记的处理器方法的调用流程       ​

如何通过 HandlerMapping 获取处理器执行链?

为什么需要 HandlerAdapter ?

适配器调用处理器方法的流程

总结

回答开头提到的几个问题


前言

        本文需要一些关于 Servlet 的前置知识,可以先大概看一下这个连接的内容关于 Servlet 你需要知道哪些?看完之后应该知道 Servlet 的工作流程,也知道当我们实现 HttpServlet 接口的时候 doGet,doPost 怎么被调用到的。了解了 Servlet 相关的知识之后,就可以来探索 Controller 到底是怎么执行的了,一般我们只在一个类上加上 @RestController,在方法上添加 @RequestMapping("/") 就可以很简单的实现一个访问接口,也不用像 Servlet 那样配置 web.xml 文件,极大的简化的开发,可是它也让我们变得更傻,即会用但是不知道为什么他就能工作了,本文就是来探究 Controller 的方法到底是怎么执行的。

以 Post 请求为例探究执行流程        

        通过上面流程图可以看出来调用 Controller 的方法的执行流程,这个流程怎么来的呢?就是我在 Controller 的方法那里打了一个断点,然后根据调用栈跟踪出来的,有兴趣的话大家都可以去跟踪一下,不然除了写这块代码的本人,其他人根本也不知道怎么去看吧,emmm。

        可是流程虽然出来了,但是其实也是基本根本看不懂, 但是如果我前面提到的关于 Servlet 的那篇文章,相信大家在 DispatcherServlet.doDispatch 这一步之前都是可以理解的,DispatcherServlet 间接继承了 HttpServlet,所以 post 请求到达 Servlet 容器之后会一步一步走到这里。但是还有很多其它问题等着要去解决。 

  1. FrameworkServlet 是一个抽象类,谁继承了它,它对应的实例又是从哪里来的?
  2. RequestMapping 配置的是访问接口方法的路径,怎么通过该路径找到对应的接口方法呢?
  3. Servlet 获取参数是通过 getParameter 等方法自己手动去获取,Controller 是怎么获取到参数值的,Controller 接口方法的参数名称可以任意取吗?
  4. Controller 和 Servlet 有什么关系?

Spring MVC 的执行流程

        Spring MVC 中的几个重要的组件 

  1. DispatcherServlet:也叫前端控制器,它是 Spring MVC 中唯一的一个 Servlet,用于 Servlet 容器发送过来的请求,并进行处理解析,最终返回给客户端结果。
  2. Handler:又名 Controller, 它用来接收用户的请求数据,调用业务方法处理请求,也叫后端处理器。
  3. HandlerMapping:一般叫它处理器映射器,通过它可以解析请求找到对应的 Handler。
  4. HandlerAdapter:处理器适配器,通过它来调用后端处理器 Handler | Controller 的方法。
  5. HandlerMethodArgumentResolver: 用于解析参数,通过业务方法的属性名获取到属性值。
  6. ViewResolver: 视图解析器,将逻辑视图名称解析为真正的物理视图。
  7. View: 最终要展现给客户端的视图。

        Spring MVC 的大概执行流程: 

         上图是 Spring MVC 的总体执行流程,但是本文只是为了探索 Controller 中的接口方法到底是如何执行的,所以我们只需要考虑前 6 步就可以了,后面的不在本文的讨论范畴。 

@RequestMapping 标记的处理器方法的调用流程       

         本文开篇介绍了 Post 方式调用方法的执行流程,虽然是以 post 为例,但是 GET 等其它方式最终也会调用到 DispatcherServlet.doDispatcher 方法,所以说 DispatcherServlet 至关重要,一般情况下使用了 Spring MVC 之后就不会再自己去写 Servlet 了,所以说这个 Servlet 很可能是你整个项目中唯一的一个 Servlet,客户端所有的请求都会打到它身上,然后它再来分配工作给其它组件,并最终给客户端返回结果。

        上述的流程涉及五个很重要的组件,分别为 DispatcherServlet, HandlerMapping,HandlerAdapter,HandlerMethodArgumentResolver,Handler。首先 DispatcherServlet 已经说了很多,它就像是一个大脑,控制着整个流程的所有操作,下面分别再通过查看代码的方式看一下其它几个组件到底怎么工作的。

如何通过 HandlerMapping 获取处理器执行链?

        首先看一下如何根据请求获取到对应的后端处理器 Handler。

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

    // HandlerMapping 的 getHandler 方法
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		Object handler = getHandlerInternal(request);

		//。。。。 省略部分代码

        // 此时已经获取到 handler 实例,调用该方法可以获取到拦截器信息,组合形成执行链
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

        // 省略部分代码

		return executionChain;
	}

    // @RequestMapping 对应的映射器类型为 RequestMappingHandlerMapping,因为该映射器类没有getHandlerInternal 方法,所以调用父类 RequestMappingInfoHandlerMapping.getHandlerInternal
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
		try {
            // 继续调用父类的方法 AbstractHandlerMethodMapping.getHandlerInternal
			return super.getHandlerInternal(request);
		}
		finally {
			ProducesRequestCondition.clearMediaTypesAttribute(request);
		}
	}

    //AbstractHandlerMethodMapping.getHandlerInternal, 该方法获取到 HandlerMethod 类型的 Handler
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        // 获取请求的路径, 通过前面讲的 request.getContextPath(),request.getRequestURI() 等方法获取路径信息
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		request.setAttribute(LOOKUP_PATH, lookupPath);
		this.mappingRegistry.acquireReadLock();
		try {
            // 寻找跟当前请求路径匹配的 Handler
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}

    //AbstractHandlerMethodMapping.lookupHandlerMethod
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
        // 获取匹配的 Match 类型的对象
		List<T> directPathMatches =          this.mappingRegistry.getMappingsByUrl(lookupPath);

		// ...... 省略部分代码,最终返回的是 Match.handlerMethod 对象,也就是我们需要的 handler
	}

    public List<T> getMappingsByUrl(String urlPath) {
        // urlLookup 是一个 Map 类型的变量,通过 urlPath 直接返回 Match 对象实例
		return this.urlLookup.get(urlPath);
	}

    

        从上面的代码分析我们应该已经知道,要获取对应请求的后端处理器 Handler,首先需要获取到请求路径信息,这个直接可以从 Request 对象中获取到。然后 AbstractHandlerMethodMapping 维护了一个 MappingRegistry 类型的变量,该变量又维护了 MultiValueMap<String, T> urlLookup 类型的变量,通过请求路径信息可以直接得到 Match 类型的对象,而 handler 就被保存到返回的 Match 对象中,如此便得到了请求对应的 Handler。可见 urlLookup 是至关重要的,它什么时候保存了请求路径和 Handler 的对应关系呢?这个下篇文章再分析,本文还是主要把目光放在请求过程中。我们目前就认为 urLookup 对象已经存在,且已经有了我们需要的映射关系,它应该是在启动容器的时候就完成了这一步,但是我们目前先不管它。

        获取到 handler 之后就要去获取拦截器,然后跟拦截器一起组成拦截器链

    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        // 实例化执行器链对象,将 handler 先填充进去
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
        // 先获取到请求的路径信息,也是通过解析 Request
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
        // 这里就直接遍历拦截器列表了
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                // 如果拦截器与当前请求路径匹配就加入执行器链
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}

         同样的,AbstractHandlerMapping 维护了 List<HandlerInterceptor> adaptedInterceptors 变量,即拦截器数组,同样猜测也是在容器启动的过程中就获取到全部的拦截器维护了起来,我们请求的时候直接拿来用。所以目前我们在请求过程中暂时先不要管它是从哪里来的,我们能用就好了。

        总结一下,HandlerMapping 为了给每个请求返回对应的处理器和拦截器,它维护了两个重要的变量,一个是 Map 类型的 urlLookup, 它用来维护请求路径和处理器的对应关系,另外一个是拦截器列表 adaptedInterceptors,每次请求来的时候都遍历这个列表调用拦截器的 match 方法来判断是否需要对当前请求进行拦截。而至于怎么维护的,我们下篇文章再聊。

为什么需要 HandlerAdapter ?

        现在我们已经获取到了 Handler 和拦截器,按理说直接调用处理器的业务方法不就好了么,干嘛还要找一下适配器,然后再通过适配器来调用 Handler。

        首先要理解一下什么是适配器,我们生活中也有很多使用适配器的场景,一个场景的例子就是电源适配器,我们知道有些手机因为品牌的不同,导致充电口都是不一样的。那么怎么解决这个问题呢?生产充电器的场景可以针对不同的手机品牌做不同的充电器,但是如果人家手机厂商又改变充电口模型了,那么你这生产充电器的厂家也要跟着变,这样很明显是有问题的。所以就又了适配器的产生,手机只管生产手机的事情,充电器也还按照之前的方式生产充电器,如果这两个不匹配了,搞一个转接头就好了。

        那么 Handler 也是同理,其实有很多类型的 Handler, 如果是 Servlet,那么请求过来的时候需要调用 Servlet 的 Service 方法。如果是实现了 Controller 接口的 Handler,需要调用handleRequest 方法,如果是通过 @RequestMapping 注解类型的 Handler,那么它的类型就是 HandlerMethod,需要调用 handleInternal 方法,此外还有其它类型的 Handler,如果这些处理逻辑都交由 DispatcherServlet 来做,那么 DispatcherServlet 就会变得很臃肿,且免不了一些重复的代码。但是如果每种 Handler 都有对应的适配器,而不同的适配器暴露给 DispatcherServlet 的接口是一致了,那么就会使整个流程都更加清晰简洁,耦合性也跟着降低。其实我们前面讲 AOP 拦截器链的时候也接触到了适配器的概念,这些都是适配器模式在 Spring MVC 中的应用。

    // 找到处理该 handler 类型的适配器
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
            // 遍历适配器列表,调用各个适配器的 supports 方法验证是否匹配
			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");
	}

        这个跟拦截器的匹配差不多,也是提前维护一份列表,然后调用的过程中一个个匹配,找到合适的那一个。什么时候维护的也是下一篇文章再说,本文介绍 @RequestMapping 类型的处理器,其对应的适配器类型为 RequestMappingHandlerAdapter,所以该方法的返回结果为 RequestMappingHandlerAdapter 类型的适配器。

适配器调用处理器方法的流程

        每个适配器都会有 handle(HttpServletRequest request, HttpServletResponse response, Object handler) 方法,这就是 DispatcherServlet 调用适配器方法的入口。我们现在已经获取到了RequestMappingHandlerAdapter 适配器,但是它又没有该方法,所以一定是它的父类有该方法,所以看一下 AbstractHandlerMethodAdapter.handle 方法。

    // 适配器提供给 DispatcherServlet 的方法
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return handleInternal(request, response, (HandlerMethod) handler);
	}
    
    // RequestMappingHandlerAdapter 适配器真正的处理逻辑,可以看到主要逻辑是调用了 invokeHandlerMethod 方法来执行处理器方法获取 ModelAndView 对象。
    protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// No synchronization on session demanded at all...
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

		//。。。。。。省略部分代码

		return mav;
	}

    // RequestMappingHandlerAdapter 调用处理器的方法来获取
    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);
            // Handler 类型转换, 由 HandlerMethod 类型转换为 ServletInvocableHandlerMethod 类型
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
        
// 设置参数解析器		    invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}

			// 。。。省略部分非本文主线逻辑代码
            
            // 调用处理器的方法
            invocableMethod.invokeAndHandle(webRequest, mavContainer);

            // 由上一步的结果进一步组装 ModelAndView 对象
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}
    
    // ServletInvocableHandlerMethod.invokeAndHandle 方法
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

        // 调用父类的方法执行处理器方法
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		
        // 省略部分非本文主线代码
	}

    // InvocableHandlerMethod 的 invokeForRequest 方法
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
        // 获取调用处理器方法需要的参数
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
        // 传入参数,调用处理器方法
		return doInvoke(args);
	}
    // InvocableHandlerMethod.doInvoke 方法
    protected Object doInvoke(Object... args) throws Exception {
		ReflectionUtils.makeAccessible(getBridgedMethod());
		try {
            // 通过方法的反射调用该方法
			return getBridgedMethod().invoke(getBean(), args);
		}
		catch (IllegalArgumentException ex) {
			assertTargetBean(getBridgedMethod(), getBean(), args);
			String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
			throw new IllegalStateException(formatInvokeError(text, args), ex);
		}
		catch (InvocationTargetException ex) {
			// Unwrap for HandlerExceptionResolvers ...
			Throwable targetException = ex.getTargetException();
			if (targetException instanceof RuntimeException) {
				throw (RuntimeException) targetException;
			}
			else if (targetException instanceof Error) {
				throw (Error) targetException;
			}
			else if (targetException instanceof Exception) {
				throw (Exception) targetException;
			}
			else {
				throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
			}
		}
	}

        总结一下上面的代码,主线逻辑很简单,首先就是 Dispatcher 调用适配器的方法,然后适配器方法又会调用到处理器的方法,而且在调用处理器方法之前会先把参数值解析出来,至于怎么解析参数值也是有挺多讲究的,改篇文章再详细 分析。不过最终,我们通过适配器最终调用了处理器的方法,也就是 @Controller 层标注了 @RequestMapping 的方法。

总结

        本文主要探索了如何执行 @RequestMapping 标注的处理器方法,当客户端发送一个请求之后会先到达 Servlet 容器层,Servlet 容器会把请求参数封装成 Request 对象,然后找到匹配的 Servlet (也就是本文提到的 DispatcherServlet)并交由它处理,请求进入到 Servlet 流程之后,首先会调用 Servlet 的 service 方法,最终抵达 DispatcherServlet.doDispatch(),随后就会首先调用处理器映射器 HandlerMapping 找到跟当前请求匹配的处理器,然后由处理器找到合适的适配器,最终由适配器触发处理器方法的调用,如此一来便执行了标注了 @RequestMapping 的处理器方法,也即我们常说的 Controller 层的方法。

回答开头提到的几个问题

  • FrameworkServlet 是一个抽象类,谁继承了它,它对应的实例又是从哪里来的?

        DispatcherServlet 继承了 FrameworkServlet, 它是 Spring MVC 中唯一的 Servlet, 如果不自己添加 Servlet 的话,所有的请求都会打到该 Servlet 身上,至于它什么时候实例化的下一篇文章再分析。

  • RequestMapping 配置的是访问接口方法的路径,怎么通过该路径找到对应的接口方法呢?

        HandlerMapping 维护了一个 Map 类型的 urlLookup, 它用来维护请求路径和处理器的对应关系。容器启动的时候就会解析 Controller 层的配置信息,生成对应关系,详细的下一篇文章再继续探究

  • Servlet 获取参数是通过 getParameter 等方法自己手动去获取,Controller 是怎么获取到参数值的,Controller 接口方法的参数名称可以任意取吗?

        这个可以单独开一篇文章来写,等我写好了把文章的链接粘贴过来,稍安勿躁

  • Controller 和 Servlet 有什么关系?

        Controller 层其实就是一个普通的 Bean,跟我们平时写的 Service 层或者数据持久层都是一样的,每个标注了 @Controller 或者 @RestController 注解的类,在容器启动的过程中都会生成一个 Bean 交由 Spring 容器保管。从这个层面上看,它跟 Servlet 没有半毛钱关系,它就安安静静的在 Spring 容器里面躺着呢。如果非要扯上一点关系的话,那就是 DispatcherServlet 会使用这个bean,调用该 bean 里面的方法。在容器启动的时候会解析 @Controller 层配置的 @RequestMapping 信息来将该 Bean 与请求路径做个映射,当 DispatcherServlet 感知到由客户端请求的时候,就通过这个映射关系找到对应的 bean 以及该 bean 中的方法,通过反射执行一下对应的方法。

  • DispatcherServlet, HandlerMapping, HandlerAdapter 等组件什么时候添加到项目中的?

        在 Spring MVC 项目中,DispatcherServlet 是在 web.xml 中配置的,跟配置其他 Servlet 的方式一样,还可以配置 contextConfigLocation 作为 Spring MVC 的配置文件的路径

     <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--指定 Spring MVC 配置文件的路径-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <!--设置启动即加载-->
        <load-on-startup>1</load-on-startup>
    </servlet>

        在 spring-mvc.xml 的配置文件里加上 <mvc:annotation-driven></mvc:annotation-driven> 相当于在 xml 中同时加入了如下配置,当然如果不想要这么多,也可以单独配置想要的 HandlerMapping 等 bean 信息。

<!-- HandlerMapping --> 
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean> 

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>

<!-- HandlerAdapter --> 
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean> 

<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean> 

<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>

<!-- HadnlerExceptionResolvers --> 
<bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"></bean> 

<bean class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver"></bean> 

<bean class="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver"></bean>

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值