springmvc源码流程解析(一)

Springmvc 是基于servlet 规范来完成的一个请求响应模块,也是spring 中比较大的一个 模块,现在基本上都是零xml 配置了,采用的是约定大于配置的方式,所以我们的springmvc 也是采用这种零xml 配置的方式。

要完成这种过程,要解决两个问题:1、取代web.xml 配置 ,2、取代springmvc.xml 配置。 取代web.xml 配置在servlet 中有一个规范,就是当servlet 容器启动的时候会根据SPI 规范加载 META-INF/services 文件夹下面的javax.servlet.ServletContainerInitializer 文件,该文件下面的 类会实现javax.servlet.ServletContainerInitializer 接口。

SPI 也被称为服务接口扩展,(Service Provider Interface) 直译服务提供商接口, 不要被这个名字唬到了, 其实很好理解的一个东西: 就是根据Servlet厂商(服务提供商)提供要求的一个接口, 在固定的目录 (META-INF/services)放上以接口全类名 为命名的文件, 文件中放入接口的实现的 全类名,该类由我们自己实现,按照这种约定的方式(即SPI规范),服务提供商会 调用文件中实现类的方法, 从而完成扩展。

该类SpringServletContainerInitializer在启动的时候会被servlet 容器(例如tomcat)实例化,然后调用onStartup 方法,并且servlet 容器会收集实现了@HandlesTypes 注解里面的接口的类(WebApplicationInitializer),并且做为入参传入到onStartup 方法中,我们拿到set 容器中的类就可以反射调用接口里面的方法了,这是servlet 规范,该规范就能保证servlet 容器在启动的时候就会完成这些操作。Springmvc 就借助这一点完成了取代web.xml 的工作。

我们定义了一个类MyWebAppInitializer继承自AbstractAnnotationConfigDispatcherServletInitializer,最上层的接口其实就是WebApplicationInitializer,这个接口有两个重要实现getRootConfigClasses将RootConfig引入了进来,这个RootConfig就是父容器的配置类,相当于之前的spring.xml;另一个方法getServletConfigClasses引入进来了WebAppConfig,这个是是子容器的配置类,相当于之前的spring-mvc.xml。

父容器会对com.dsk下的包进行扫描,但是排除了类上注解为@RestController和@Controller的类。因为controller要交给springmvc自身的子容器去管理,父容器会管理一些@Service相关的类。

子容器会对com.dsk下的包进行扫描,但是只扫描了类上注解为@RestContrller和@Controller的类

相关的类介绍完之后,看下核心的onStartup方法,我们定义的MyWebAppInitializer与其父类的继承关系分别是MyWebAppInitializer-》AbstractAnnotationConfigDispatcherServletInitializer-》AbstractDispatcherServletInitializer-》AbstractContextLoaderInitializer-》WebApplicationInitializer

第一步继续会调用父类的onStartUp方法

第二步注册dispatcherServlet

我们再往下一层AbstractContextLoaderInitializer父类的onStartup方法,可以看到这一步就是注册监听器ContextLoaderListener,注册进servletContext,这个监听器的作用其实就是当servlet容器(例如tomcat)启动成功后,会调用监听器的contextInitialized方法。

这一步相当于在web.xml中配置了ContextLoaderListener,如下

registerContextLoaderListener方法中不只是注册了listener,还创建了父容器rootAppContext

创建父容器的方法createRootApplicationContext中,其实就是熟悉的AnnotationConfigWebApplicationContext,他将父容器也就是RootConfig注册进了BeanDefinitionRegistry中

总结registerContextLoaderListener方法做了两件事情

1、创建父容器,也就是spring的AnnotationConfigWebApplicationContext

2、将ContextLoaderListener加入到servletContet中,注意此时ContextLoaderListener持有父容器对象rootContext方法,目的是当servlet容器启动成功后,会通知到监听器,此时监听器会调用父容器的refresh方法,将类注入到spring容器中。

那么父类AbstractContextLoaderInitializer的onStartUp方法就看完了,回到AbstractDispatcherServletInitializer类

这一步就是将dispatcherServlet注册进servletContext中,并把上下文对象设置到了

dispatcherServlet 对象中,相当于web.xml的如下配置

这个方法registerDispacherServlet不止是将dispatcherServlet加入到了servletContext,同时也创建了子容器,可以看到这个子容器也是AnnotationConfigWebApplicationContext,只不过此时注册进来的类是WebAppConfig,子类会将带有@Controller的类扫描进来,注入到子容器中。

那么onStartUp方法就执行完了,做个总结:

一、registerContextLoaderListener(servletContext);

做了两件事情1、创建父容器,且listener中持有父容器对象;2、将ContextLoaderListener加入到ServletContext

二、registerDispatcherServlet(servletContext);

做了两件事情1、创建子容器,且dispatcherServlet中持有子容器对象;2、将DispatcherServlet加入到ServletContext;

ContextLoaderListener启动:

执行完onStartUp方法后,此时父容器和子容器还没有启动,因为并没有执行到他们的refresh方法,我们知道当servlet容器(tomcat启动成功后)会调用监听器ContextLoaderListener.contextInitialized方法,这里会启动spring容器,把spring上下文对象放入到了servletContext中

DispatcherServlet 的启动:

了解DispatcherServlet的生命周期的会知道,DispatcherServlet启动过程中会执行init()方法,在这里会进行springmvc子容器的启动

这里会从servletContext中获取父容器对象,并将子容器webApplicationContext的parent赋值为rootContext,然后执行子容器的refresh()方法。

在调用子容器的刷新方法前,这里会注册一个监听器(该监听会初始化springmvc所需信息),

ContextRefreshedEvent可以看到该监听器监听的是容器refreshed事件, 会在子容器的refresh方法中的finishRefresh中发布,这是spring的监听器模式。

子容器的refresh()方法最后一步,会发布事件触发监听器的执行

这里面的每一个方法不用太细看, 就是给SpringMVC准备初始化的数据, 为后续SpringMVC处理请求做准备基本都是从容器中拿到已经配置的Bean(RequestMappingHandlerMapping、

RequestMappingHandlerAdapter、HandlerExceptionResolver )放到dispatcherServlet中做准备。但是这些Bean又是从哪来的呢?? 回到我们的WebAppConfig使用的@EnableWebMvc,也就是这个注解取代了springmvc.xml

  1. 导入了DelegatingWebMvcConfiguration@Import(DelegatingWebMvcConfiguration.class)

  2. DelegatingWebMvcConfiguration的父类就配置了这些Bean

  3. SpringBoot也是用的这种方式;

总结:

  1. Tomcat在启动时会通过SPI注册 ContextLoaderListener和DispatcherServlet对象

a. 同时创建父子容器

i. 分别创建在ContextLoaderListener初始化时创建父容器设置配置类

ii. 在DispatcherServlet初始化时创建子容器设置配置类

  1. Tomcat在启动时执行ContextLoaderListener和DispatcherServlet对象的初始化方

法, 执行容器refresh进行加载

  1. 在子容器加载时 创建SpringMVC所需的Bean和预准备的数据:(通过配置类

@EnableWebMvc配置(DelegatingWebMvcConfiguration)------可实现WebMvcConfigurer进行定制扩展)

a. RequestMappingHandlerMapping,它会处理@RequestMapping 注解

b. RequestMappingHandlerAdapter,则是处理请求的适配器,确定调用哪个类的哪个方法,并且构造方法参数,返回值。

c. HandlerExceptionResolver 错误视图解析器

d. addDefaultHttpMessageConverters 添加默认的消息转换器(解析json、解析xml)等

  1. 子容器需要注入父容器的Bean时(比如Controller中需要@Autowired例如Service的Bean); 会先从子容器中找,没找到会去父容器中找。

一、Spring和SpringMVC为什么需要父子容器?不要不行吗?就实现层面来说不用子父容器也可以完成所需功能(参考:SpringBoot就没用子父容器)?

  1. 所以父子容器的主要作用应该是早期Spring为了划分框架边界。有点单一职责的味道service、dao层我们一般使用spring框架来管理、controller层交给springmvc管理

  2. 规范整体架构 使 父容器service无法访问子容器controller、子容器controller可以访问父容器 service

  3. 方便子容器的切换。如果现在我们想把web层从spring mvc替换成struts,那么只需要将springmvc.xml替换成Struts的配置文件struts.xml即可,而spring.xml不需要改变。

  4. 为了节省重复bean创建

二、是否可以把所有Bean都通过Spring容器来管理?(Spring的applicationContext.xml中配置全局扫描)?

不可以,这样会导致我们请求接口的时候产生404。 如果所有的Bean都交给父容器,SpringMVC在初始化HandlerMethods的时候(initHandlerMethods)无法根据Controller的handler方法注册HandlerMethod,并没有去查找父容器的bean;也就无法根据请求URI 获取到 HandlerMethod来进行匹配.

三、是否可以把我们所需的Bean都放入Spring-mvc子容器里面来管理(springmvc的springservlet.xml中配置全局扫描)?

可以 , 因为父容器的体现无非是为了获取子容器不包含的bean, 如果全部包含在子容器完全用

不到父容器了, 所以是可以全部放在springmvc子容器来管理的。虽然可以这么做不过一般应该是不推荐这么去做的,一般人也不会这么干的。如果你的项目里有用到事物、或者aop记得也需要把这部分配置需要放到Spring-mvc子容器的配置文件来,不然一部分内容在子容器和一部分内容在父容器,可能就会导致你的事务或者AOP不生效。 所以如果aop或事务如果不生效也有可能是通过父容器中的类(spring)去增强子容器的类(Springmvc),也就无法增强。

下个章节我们解析springmvc的请求源码流程。

相关推荐
m0_375599735 小时前
Hadoop:单机伪分布式部署
大数据·hadoop·分布式
flying robot6 小时前
PySpark和Hadoop
大数据·hadoop·分布式
小森饭6 小时前
Kubernetes运行大数据组件-运行hive
大数据·hive·云原生·kubernetes
Darryl大数据7 小时前
八、MapReduce 大规模数据处理深度剖析与实战指南
大数据·hadoop·mapreduce
yangjiwei02077 小时前
Flume采集Kafka数据到Hive
hive·kafka·flume
Mephisto.java7 小时前
【力扣 | SQL题 | 每日4题】力扣2004, 1454,1613,1709
hive·sql·mysql·leetcode·oracle·database
Mephisto.java9 小时前
【大数据学习 | Zookeeper】客户端命令行操作
linux·hadoop·sql·mysql·zookeeper·hbase
三日看尽长安花10 小时前
【数据仓库】
数据仓库
mysql学习中17 小时前
hive修改表操作与array类型
大数据·数据仓库·hive·hadoop·hdfs·mapreduce
Kika写代码19 小时前
【Hadoop】hadoop的路径分不清?HDFS路径与本地文件系统路径的区别
大数据·hadoop·hdfs