当前位置: 首页 > biancheng >正文

秒懂SpringBoot之Mvc请求执行流程浅谈

[版权申明] 非商业目的注明出处可自由转载
出自:shusheng007

文章目录

  • 概述
  • SpringMvc 流程简述
  • 源码浅析
    • 注册控制器
    • 执行请求
  • 总结

概述

在Java Web开发领域Spring Mvc的流行度居高不下,假期花了天时间又读了下spring mvc的源码,大概整理一下,以便于查询,也便于后辈学习。

SpringMvc 流程简述

先上一张手撕大图…其实只要看懂了这张图对spring mvc的理解也就有了宏观的概念了,看不懂也没关系拉,不耽误我们搬砖挣钱…只不过只能一直搬砖

在这里插入图片描述

从图中看,spring mvc 有3大组件:HandlerMapping, HandlerAdapter,ViewResolver,其中ViewResolver从实战的角度来说快没有用武之地了,至于为什么我们稍后再说。其中每一步我都标了序号,请求就是按照序号被一路处理过来的

SpringBoot的出现,使的我们开发一个web程序简单到令人发指。如果你使用过spring mvc下面的代码肯定是再熟悉不过了,加几个注解,然后一个API就写好了,但是你是否思考过其背后的执行流程呢?

@RestController
@RequestMapping("/mvc")
public class MvcController {

    @GetMapping("/obj")
    public ResponseEntity<MyResponse> getObj(@RequestParam("name") String name){
        MyResponse response = new MyResponse();
        response.setName(name);
        response.setAge(18);

        ResponseEntity<MyResponse> entity = new ResponseEntity<>(response, HttpStatus.OK);
        return entity;
    }
}
  • HandlerMapping

通过HandlerMapping将我们写的Controller类(使用@Controller 或者@RequestMapping标记的类)里面带有@RequestMaping(包括各种变种,例如@GetMapping,@PostMapping等等)标记的方法以及拦截器(实现了HandlerInterceptor接口的类)包装成 HandlerExecutionChain返回给DispatherServlet

说白了,就是找到你写的那些处理器方法和拦截器,然后定位到你要请求的那个。由于SpringBoot的流行,我们几乎所有时间都在使用注解,所以最重要的一个实现类是:RequestMappingHandlerMapping。 这个很好记拉,名称前面那个RequestMapping就是你写Controller时候经常要加的@RequestMapping注解。

下面是其类继承结构图。
在这里插入图片描述

  • HandlerAdapter

HandlerMapping把处理器和拦截器都处理好了,把定位到的处理器和拦截器包装成HandlerExecutionChain对象返回给了DispatherServlet,接下来就是要具体执行里面的处理器和拦截器的逻辑了。这货非常重要,具体的执行逻辑,以及执行结果的转换都由它来负责,最后返回一个ModelAndView实例给DispatherServlet

由于注解的流行,我们经常使用的一个HandlerAdapter为RequestMappingHandlerAdapter。 这个也很好记拉,名称前面那个RequestMapping就是你写Controller时候经常要加的@RequestMapping注解。

其类继承结构图如下:
在这里插入图片描述

  • ViewResolver

这个组件主要是负责将逻辑视图解析为具体的视图。

到这块是不是又懵逼了,啥是逻辑视图?这块不懂也不能怪你,接触Jave web编程晚的同学可能都不知道啥是JSP了吧?现在前后端分离基本已经成为行业标准,所以也很少有人写jsp,以及使用其他模板引擎写网页了。以前web开发html都是后端写的,然后返给浏览器来渲染的,现在这种做法已经绝迹了吧?那个逻辑视图你就理解为一个指向你写的一个类似html文件的路径字符串好啦,ViewResolver就负责通过这个字符串找到你写的那个文件,然后结合数据将那个文件填充为html文件返回给前端。

这块不明白也没关系拉,现在都2022年了,这玩意在实践中基本用不上了,我们平时都是返回JSON,根本就走不到这一步,不过面试的时候有可能会用…

源码浅析

上面讲了个大概,我们稍微分析下源码吧,验证一下上面的流程

注册控制器

SpringMvc在启动的的时候就会把你写的那些Controller类(@Controller或者@RequestMapping标记)都给保存好。

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean{
}

//由于其实现了InitializingBean接口,afterPropertiesSet()方法会自动执行。启动处理handlerMathods的方法。

@Override
public void afterPropertiesSet() {
	initHandlerMethods();
}

//在initHandlerMethods()方法中,从所有的bean里过滤出使用@Controller或者@RequestMapping标记的类,然后处理它里面的那些处使用@RequestMapping标记的理器方法。
for (String beanName : getCandidateBeanNames()) {
	if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
		processCandidateBean(beanName);
	}
}

//例如我们写的这个例子中,此处的beanName = "mvcController"
protected void processCandidateBean(String beanName) {
	Class<?> beanType = obtainApplicationContext().getType(beanName);
	...
	//isHandler  判断MvcController是否被@Controller或者@RequestMapping标记
	if (beanType != null && isHandler(beanType)) {
	     //从handler中过滤出request方法,
		detectHandlerMethods(beanName);
	}
}

//在detectHandlerMethods方法中通过RequestMappingHandlerMapping的
RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType)
方法将那些处理器方法转化为RequestMappingInfo对象,

最后,通过registerHandlerMethod(handler, invocableMethod, mapping);将其保存到 AbstractHandlerMethodMapping的

private final MappingRegistry mappingRegistry = new MappingRegistry();里。

所以,程序启动后,你写的那些Controller里的处理器方法都已经以RequestMappingInfo的形式被注册到一个叫MappingRegistry的容器里面去了,等请求过来的时候就会从这里面取。

执行请求

首次调用的时候,会执行DispatcherServlet的initStrategies方法,这个方法用来获取各种组件,其中就包括我们的3大组件。

protected void initStrategies(ApplicationContext context) {
    ...
    initHandlerMappings(context);
	...
	initHandlerAdapters(context);
    ...
	initViewResolvers(context);
}

其中handlerMapping被保存到下面这个list中

private List<HandlerMapping> handlerMappings;

包括RequestMappingHandlerMapping的实例,

其中HandlerAdapter被保存到下面这个list中

private List<HandlerAdapter> handlerAdapters;

包括RequestMappingHandlerAdapter的实例

然后调用了DispatcherServlet的 doDispatch方法,这个方法非常重要,是整个请求执行流程的脉络。

注意:非初次调用接口不走初始化方法,直接走doDispatch方法。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response){
    //将HandlerMethod 和 intercetor 转化为HandlerExecutionChain
    HandlerExecutionChain mappedHandler = getHandler(processedRequest);
    
    //获取HandlerAdapter,例如RequestMappingHandlerAdapter
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    //执行拦截器前置方法
	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;
	}
     
    //调用方法
    ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    //拦截器后置方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    
    //拦截器完成方法
	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

}

最后会调用RequestMappingHandlerAdapter的

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod){		
    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    ...
	invocableMethod.invokeAndHandle(webRequest, mavContainer);
	...
	return getModelAndView(mavContainer, modelFactory, webRequest);
}

获得一个ModelAndView对象,其中在invokeAndHandle中调用了 public class InvocableHandlerMethod extends HandlerMethod{}

protected Object doInvoke(Object... args) throws Exception {
	Method method = getBridgedMethod();
    return method.invoke(getBean(), args);
	...
}

可见最后还是使用了反射, 最终会进入AbstractMessageConverterMethodProcessor 类的

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
	ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
	throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
}

方法,方法调用的结果在这里进行转换,例如经常被使用MappingJackson2MessageConverter转换为json。

总结

以上就是Spring Mvc大概的执行流程,希望你了了…

SpringBoot之所以好用是因为其对整个流程做了高度抽象,如果不懂其中的原理那么它对于我们来说就是一个黑盒,对于日常搬砖是没什么问题,一旦出了bug就抓瞎了…而且只有掌握了其原理,才能更好的使用它。

一如既往的你可以从GitHub上获得本文源码:mvc-inspect

相关文章:

  • switch循环语句
  • 牛客练习赛#84 F 莫比乌斯反演+杜教筛+技巧+斐波那契数列和gcd的结论+矩阵快速幂
  • ZZNUOJ_用C语言编写程序实现1342:支配值数目(附完整源码)
  • java毕业设计后勤管理系统餐饮评价监督系统(附源码、数据库)
  • 前端基础学习笔记
  • 【TS】联合类型--类型断言--类型推断
  • 谈笑风声的秘密
  • QT影城网上售票系统
  • NetCDF数据在ArcMap中的使用
  • 打怪升级(考验思路)