Tomcat是如何加载Spring和SpringMVC及Servlet相关知识
概述
大家是否清楚,Tomcat是如何加载Spring和SpringMVC,今天我们就弄清下这个过程(记录最关键的东西)
其中会涉及到大大小小的知识,包括加载时候的设计模式,Servlet知识等,看了你肯定有所收获~
Tomcat
tomcat是一种Java写的Web应用服务器,也被称为Web容器,专门运行Web程序
tomcat启动
tomcat启动了之后会在操作系统中生成一个Jvm(Java虚拟机)的进程,从配置监听端口(默认8080)监听发来的HTTP/1.1协议的消息
默认配置文件这样

当Tomcat启动完成后,它就会加载其安装目录下webapps里的项目(放war包会自动解压成项目)
小提问:webapps里多个项目,是运行在同一个JVM上吗
是运行在同一个JVM上的(Tomcat启动时创建的那个),多个项目就是多个线程,之所以项目间数据不共享,是因为类加载器不一样的缘故
加载Web程序(Spring+SpringMVC框架)
tomcat启动完毕后,最关键的是生成了ServletContext(Tomcat的上下文),然后会根据webapps项目里的web.xml进行加载项目
下面是一个SpringMVC+Spring项目的部分web.xml
<!--以下为加载Spring需要的配置-->
<!--Spring配置具体参数的地方-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml
</param-value>
</context-param>
<!--Spring启动的类-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <!--以下为加载SpringMVC需要的配置-->
<servlet>
<servlet-name>project</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup> <!--servlet被加载的顺序,值越小优先级越高(正数)--> <servlet-mapping>
<servlet-name>project</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</servlet>
初始化Spring
tomcat首先会加载进ContextLoaderListener,然后将applicationContext.xml里写的参数注入进去,来完成一系列的Spring初始化(如各种bean,数据库资源等等)
这里就是经常听到的Ioc容器的初始化了,我们搜索这个类发现以下代码
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
//省略其他方法
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
//省略其他方法
}
这里最重要的是通过ServletContext,初始化属于Spring的上下文WebApplicationContext,并将其存放在ServletContext
WebApplicationContext多重要老铁们都懂得,我们经常用webApplicationContext.getBean()来获取被Spring管理的类,所以这也是IOC容器的核心
Spring采用这种监听器来启动自身的方法,也是一种设计模式,叫观察者模式:
整个过程是这样的,Tomcat加载webapps项目时,先通过反射加载在web.xml标明的类(通通放入一个数组)
到某个时刻,我tomcat(事件源,事件的起源)会发起一个叫ServletContextEvent的事件(里面带着各种参数)
凡是实现了ServletContextListener接口的类,我都会调用里面的contextInitialized方法,并把这个事件参数传进去
咳咳,现在我看看这个数组里有没符合条件的(遍历),发现真有实现这个接口的(类 instanceof 接口),就调用contextInitialized方法
于是Spring就被动态加载进来了~~
题外话:
加载一个类,可以用用完整的类名,通过java反射加载,Class.forName(类名)
也能直接new 一个类 来加载
初始化SpringMVC
看配置文件,标签是servlet,我们得首先了解下servlet是什么东东
Servlet简介
Servlet是一个接口,为web通信而生(说白了就是一堆sun公司的大佬们开会,拍板造出的类,有固定的几个方法)

tomcat有一套定义好的程序(其实不只是tomcat,能跑java写的web应用服务器如Jetty等,都有这固定程序)
1.当tomcat加载进来一个类时,如果它实现了Servlet接口,那么会记载到一个Map里,然后执行一次init()方法进行Servlet初始化
2.当tomcat收到浏览器的请求后,就会在Map里找对应路径的Servlet处理,路径就是写在<url-pattern>标签里的参数,调用service()这个方法
3.当Servlet要被销毁了,就调用一次destroy()方法
各位看到这是不是感觉相识,跟Spring加载差不多嘛,都是实现了一个接口后就被命运(tomcat)安排~~
当然,我们自己实现Servlet接口太鸡儿麻烦了,于是有HttpServlet(一个抽象类)帮我们实现了大部分方法(包含http头的设置,doXXX方法判断等等)

所以我们只要继承HttpServlet就实现几个方法就能用啦

SpringMVC加载
为什么要讲Servlet,因为SpringMVC的核心就是DispatcherServlet(前置控制器),如图

DispatcherServlet由SpringMVC的实现,已经实现的很棒棒了,我们不需要再动它
tomcat从web.xml中加载DispatcherServlet,然后会调用它的init()方法
Servlet配置文件默认在/WEB-INF/<servlet-name>-servlet.xml,所以现在默认叫project-servlet.xml
当然,也能自己指定文件
<!--以下为加载SpringMVC需要的配置-->
<servlet>
<servlet-name>project</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup> <!--指定配置文件-->
<init-param>
<param-name>contextCOnfigLocation</param-name>
<param-value>classPath:spring-servlet.xml</param-value>
</init-param> <servlet-mapping>
<servlet-name>project</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</servlet>
当SpringMVC加载好后,浏览器有请求过来,如果是.html结尾,tomcat就会交给DispatcherServlet处理
而DispatcherServlet会根据路径找到对应的处理器处理,可以理解为我们写的Controller接收到了(具体SpringMVC处理流程会写一篇博文)
至此,浏览器发送请求,到执行我们写的代码这个流程就结束了 ~~撒花~~
Spring和SpringMVC的容器问题
既然讲到tomcat加载这两个框架,大家发现没有,在web.xml中,Spring加载是写在DispatcherServlet加载前面的
我们不妨来看看DispatcherServlet的初始化方法,由于DispatcherServlet是通过层层继承而来的,所以初始化的方法也变成了

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
//其他方法
@Override
public final void init() throws ServletException {
//其他代码
// Let subclasses do whatever initialization they like.
initServletBean();
}
protected void initServletBean() throws ServletException {
}
//其他方法
}
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
//其他方法
@Override
protected final void initServletBean() throws ServletException {
//其他代码
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
//其他代码
}
protected WebApplicationContext initWebApplicationContext() {
//获得了Spring创造的webApplicationContext,关键
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext); //设置父上下文
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//其他代码
}
//其他方法
}
我们可以看到,HttpServlet将init()实现了,留下了initServletBean()抽象方法
而FrameworkServlet实现了initServletBean()方法并定义成final(不允许重写),在此方法中调用了initWebApplicationContext()
而initWebApplicationContext()中说明了如果tomcat里存在webapplication就获取它,然后将其设置为SpringMVC的父上下文
至此DispatcherServlet初始化完成(当然我省略了其他的初始化做的事)
因此Spring和SpringMVC容器之间是父子关系,由于子容器可以访问父容器的内容,而反过来不行,所以不要想在Service里自动注入Controller这种操作

因此会有下面情况
Spring配置进行扫包的时候,如果将Controller也扫进来了会怎样
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
...../ 此处省略> <!-- 扫描全部包 -->
<context:component-scan base-package="com.shop"></context:component-scan>
<!--本来正确写法是这样的>
<!--
<!-- 扫描包Service实现类 -->
<context:component-scan base-package="com.shop.service"></context:component-scan>
>
</beans>
那么,Controller将进入Spring的上下文,SpringMVC里就没Controller了,到时候有请求给DispatcherServlet时,就会找不到Controller而404
小提问:那么SpringMVC扫描所有的包可以吗
这个是可以的,SpringMVC不需要Spring也能使用,但是加入Spring是为了更好的兼容其他的框架(数据库框架等等)
但是如果用了Spring就不能这样做,包括Spring扫Controller,SpringMVC也扫一次Controller这些操作,会出现各种奇怪的问题
以上就讲完啦,希望大家点点赞,或者一键3连不迷路~~
参考:https://www.cnblogs.com/wyq1995/p/10672457.html
参考:https://blog.csdn.net/s740556472/article/details/54879954
Tomcat是如何加载Spring和SpringMVC及Servlet相关知识的更多相关文章
- Tomcat启动时加载数据到缓存---web.xml中listener加载顺序(优先初始化Spring IOC容器)
JavaWebSpringTomcatCache 最近用到在Tomcat服务器启动时自动加载数据到缓存,这就需要创建一个自定义的缓存监听器并实现ServletContextListener接口,并且 ...
- Tomcat启动时加载数据到缓存---web.xml中listener加载顺序(例如顺序:1、初始化spring容器,2、初始化线程池,3、加载业务代码,将数据库中数据加载到内存中)
最近公司要做功能迁移,原来的后台使用的Netty,现在要迁移到在uap上,也就是说所有后台的代码不能通过netty写的加载顺序加载了. 问题就来了,怎样让迁移到tomcat的代码按照原来的加载顺序进行 ...
- java web 加载Spring --web.xml 篇
spring是目前最流行的框架.今天谈谈对spring的认识 起步 javaweb中我们首先会遇到的配置文件就是web.xml,这是javaweb为我们封装的逻辑,不在今天的研究中.略过,下面是一个标 ...
- 如何重新加载 Spring Boot 上的更改,而无需重新启动服务器?
这可以使用 DEV 工具来实现.通过这种依赖关系,您可以节省任何更改,嵌入式 tomcat将重新启动.Spring Boot 有一个开发工具(DevTools)模块,它有助于提高开发人员的生产力.Ja ...
- 【Spring】Junit加载Spring容器作单元测试
如果我们需要对我们的Service方法作单元测试,恰好又是用Spring作为IOC容器的,我们可以这么配置Junit加载Spring容器,方便做单元测试. > 基本的搭建 (1)引入所需的包 & ...
- Web.xml配置详解之context-param (加载spring的xml,然后初始化bean看的)
http://www.cnblogs.com/goody9807/p/4227296.html(很不错啊) 容器先加载spring的xml,然后初始化bean时,会为bean赋值,包括里面的占位符
- Tomcat的class加载的优先顺序
Tomcat的class加载的优先顺序一览 1.最先是$JAVA_HOME/jre/lib/ext/下的jar文件. 2.环境变量CLASSPATH中的jar和class文件. 3.$CATALINA ...
- struts加载spring
为了在Struts中加载Spring context,需要在struts-config.xml文件中加入如下部分: <struts-config> <plug-in classNam ...
- Spring Boot中采用Mockito来mock所测试的类的依赖(避免加载spring bean,避免启动服务器)
最近试用了一下Mockito,感觉真的挺方便的.举几个应用实例: 1,需要测试的service中注入的有一个dao,而我并不需要去测试这个dao的逻辑,只需要对service进行测试.这个时候怎么办呢 ...
随机推荐
- RPC协议实践入门
RPC 是什么 RPC(Remote Procedure Call) 是一个计算机通信协议.该协议允许运行与一台计算机的程序调用另一个地址空间的程序,是一个通过发送请求-接受回应进行信息交互的系统. ...
- 正则表达式-获取Json属性值
需求 需要获取json的字符串参数中的某个属性的值,只用json转对象后再获取层级比较多,所以使用简单的正则表达式进行获取 具体实现 public static void main(String[] ...
- 【linux】gpio子系统
目录 前言 linux子系统 gpio子系统 gpio子系统实战-系统调用 前言 目前不涉及驱动源码 参考链接 linux子系统 在 Linux 系统中 绝大多数硬件设备都有非常成熟的驱动框架 驱动工 ...
- yum安装Ceph指定Jewel版本
前言 通过yum安装指定的rpm包,这个一般是 yum --showduplicates list ceph | expand ,然后去通过yum安装指定的版本即可,这个在hammer下是没有问题的, ...
- 灵活运用的@RequestParam和@RequestBody
最近在编写项目的过程中,老出现前后端传递参数格式不一致.不统一的问题,对于一个已经快工作一年的Java程序员来说,实属不合格,所以我就下来好好研究了一下@RequestParam和@RequestBo ...
- h5 图片上传旋转问题
https://blog.csdn.net/netdxy/article/details/51518494 https://www.cnblogs.com/liu-fei-fei/p/5974403. ...
- mysql学习——数据表基本操作1
选择数据库 创建数据表 包括字段名和数据类型两部分 查看数据表 使用主键约束 主键又称主码,能够唯一的表示表中的一条记录,分为单字段主键与多字段联合主键 单字段主键 定义主键的两种方式: 1.在最后指 ...
- ppt-1 操作界面与基本操作
1.Ctrl+N快速建立新文档 2.新模板:文件--新建--可免费搜索.下载新模板 3.恢复未保存的演示文稿 文件--打开(首先看到的是近期使用的演示文稿,)--鼠标滚动至末尾,可看到"恢复 ...
- 常用命令合集『Postgres、Redis、Docker等等』每周更新,建议收藏备用
Command CMD POSTGRES 进入数据库命令行 psql -U 用户名 -d 数据库名 psql -U example -d exampledb 导出数据库 pg_dump -U 用户名 ...
- 【PYTEST】第一章常用命令
pytest入门 安装pytest 运行pytest pytest常用命令 1. 安装pytest pip install pytest 2. 运行pytest 2.1 pytest默认搜索当前目录下 ...