SpringMVC源码分析

本文详细分析了SpringMVC框架的初始化过程,重点关注DispatcherServlet的初始化步骤,尤其是ContextRefreshListener的作用,以及如何根据请求找到并执行对应的处理方法,包括HandlerMapping的工作原理和拦截器的执行顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SpringMVC源码分析

1、初始化

我们都知道,在一般使用Spring mvc框架时候都会在web app文件夹下的WEB-INF里面servlet的配置,那么其实在tomcat启动的时候,就会读取这个文件,并调用DispatcherServlet中的init方法开始初始化。但由于DispatcherServlet这个类没有重写这个init方法,所以会掉父类的init方法
源码入口org.springframework.web.servlet.HttpServletBean#init
代码走读:

  • 初始化入口
    org.springframework.web.servlet.HttpServletBean#init
  • 进一步初始化
    org.springframework.web.servlet.FrameworkServlet#initServletBean
  • 初始化容器 (重要!)
    org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
  • 查找父容器(有就返回父容器,没有返回null)
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
    因为webApplicationContext是servlet的一个属性,其实是可以配置的,所以如果配置的话,那么就不用初始化,就将父容器作为一个属性set过去就行了。
  • 我们默认没有配置,那么就会走创建容器的代码
    org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.web.context.WebApplicationContext)
    org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.context.ApplicationContext)
    看到这其实就差不多了,不过多展开创建过程。简单理解就通过一个工具类创建了一个容器
    org.springframework.beans.BeanUtils#instantiateClass(java.lang.Class)
    默认会生成一个XmlWebApplicationContext容器,是Servlet中的contextClass的默认值,也可以通过配置文件修改。然后会继续拿ContextConfigLocation的值,这里也是我们的配置文件配置的springBean的配置文件
  • 执行初始化容器代码
org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext(){
//在这里会有一行重要的代码,作用简单理解就是给容器添加一个监听器。
//这个监听器的作用就是监听容器reFresh()方法执行完,开始做一些逻辑
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
//再往下就是refresh容器了,跟spring容器的初始化一样,这里不做展开,有兴趣可以看一下我的上一篇文章,有关spring的源码。
wac.refresh();
}

接下来重点研究一下这个ContextRefreshListener监听器,当监听到容器refresh会执行下面的方法
org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener#onApplicationEvent

  • 进到Servlet的onApplicationEvent方法中
    org.springframework.web.servlet.FrameworkServlet#onApplicationEvent
  • 再然后就是DispatcherServlet的onRefresh方法,
    其实可以理解为Servlet的初始化方法
    org.springframework.web.servlet.DispatcherServlet#onRefresh
    org.springframework.web.servlet.DispatcherServlet#initStrategies
    我们着重看一下里面的逻辑:
protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        //重要:初始化请求映射
        this.initHandlerMappings(context);
        //重要:加载一些适配器,比如参数转换的适配器,返回值转换的适配器等
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }

关于initHandlerMappings

我们先来看一下org.springframework.web.servlet.DispatcherServlet#initHandlerMappings这个方法里面的逻辑
大致逻辑是首先会在beanFactory中找HandlerMapping的类,如果有的话赋值给handlerMappings,如果没有就走默认创建逻辑。
org.springframework.web.servlet.DispatcherServlet#getDefaultStrategies
此方法会去加载DispatcherServlet.properties的配置文件中的类,其中默认配置的类有

org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,
org.springframework.web.servlet.function.support.RouterFunctionMapping

在这三个类在创建过程中,每个又都会有各自的初始化方法
BeanNameUrlHandlerMapping
它的父类实现了ApplicationContextAware接口,在重写setApplicationContext时候会调用org.springframework.context.support.ApplicationObjectSupport#initApplicationContext(org.springframework.context.ApplicationContext)方法
RequestMappingHandlerMapping
它的父类实现了InitializingBean接口,在重写的afterPropertiesSet方法中会去调用
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods 方法
RouterFunctionMapping
它的父类实现了InitializingBean接口,在重写的afterPropertiesSet方法中会去调用initRouterFunction、initMessageConverters等方法。
这三个类的初始化逻辑其实就是为了提前将请求路径和执行方法(类)做映射,比如RequestMappingHandlerMapping的初始化方法initHandlerMethods的代码走读如下:
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#processCandidateBean
在这个方法中会去调用一个很重要的判断代码:

  protected boolean isHandler(Class<?> beanType) {
        return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);
    }

这个代码想必大家一看就知道了吧,就是要找出来那些handler即能处理请求的类即常说的controller
找到之后会去执行
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#createRequestMappingInfo(java.lang.reflect.AnnotatedElement)
这段逻辑也有一段很重要的代码

 @Nullable
    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        RequestMapping requestMapping = (RequestMapping)AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        RequestCondition<?> condition = element instanceof Class ? this.getCustomTypeCondition((Class)element) : this.getCustomMethodCondition((Method)element);
        return requestMapping != null ? this.createRequestMappingInfo(requestMapping, condition) : null;
    }

这段逻辑大致意思就是会去查找该类下的所有方法,判断每一个方法中是不是带有@RequestMapping,如果有会把@RequestMapping注解的的一些值封装成一个RequestMappingInfo对象。然后返回时候是一key为Method,value为RequestMappingInfo对象的map。
然后会遍历这个map,将handler(即controller类)、RequestMappingInfo和Method存到一个registry中去,方便后面使用。
BeanNameUrlHandlerMapping这个类的初始化方法和另外一种不常见的实现请求controller层有关(实现Controller接口或HttpRequestHandler)有兴趣的可以自己研究一下。

2、DispatcherServlet根据请求找对应处理方法

当一个请求过来时候,tomcat处理请求会去调用DispatcherServlet的service方法,所以我们看源码时候,这个方法就是处理请求的入口:
org.springframework.web.servlet.FrameworkServlet#service
当你不是patch请求方式请求时候,会调用父类的
javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
这个方法的逻辑大概是会根据请求方式去调对应的方式,比如get请求会去调doGet方法,post请求会去调doPost方法等等。不过再往下走进入源码发现,无论是什么请求都最终会进入一个方法叫做
org.springframework.web.servlet.FrameworkServlet#processRequest
里面会去调用org.springframework.web.servlet.DispatcherServlet#doService
然后去调用
org.springframework.web.servlet.DispatcherServlet#doDispatch
这是一个真正的处理的逻辑
1、首先走进一个getHandler的方法
org.springframework.web.servlet.DispatcherServlet#getHandler
此方法的大致逻辑是遍历上面说的那三个默认的HandlerMapping,调用他们的getHandler,会去之前存储的映射关系中找到对应的处理类(实现Controller接口的形式的)或者处理类方法(注解@RequestMapping形式的)然后还有对应的适配器、拦截器链等封装成一个HandlerExecutionChain返回。
2、然后继续回到doDispatch主逻辑来,接下来就是从返回的HandlerExecutionChain中拿到对应的适配器
org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
3、然后执行所有拦截器的preHandle
org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
如果不通过就会直接返回了
4、然后才会真正的执行,返回一个moduleAndView对象
org.springframework.web.servlet.HandlerAdapter#handle
5、然后设置一个默认的视图名 applyDefaultViewName
6、然后执行所有拦截器的postHandle
org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle
7、最后才是处理返回结果,渲染视图org.springframework.web.servlet.DispatcherServlet#processDispatchResult
值得注意的是,在最终渲染完成之后,才会最终调用所有拦截器的afterCompletion方法

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值