SSM项目的启动流程深入解析

2023-11-07

1 环境说明

本文的内容基于Tomcat9.0.10、Spring 4.3

2 Tomcat加载应用的顺序

在我们正式介绍SSM项目是怎么启动之前,我们要先来简单介绍一下Tomcat。很多人在介绍Tomcat的时候,都把Tomcat叫做Servlet容器,之所以这样称呼Tomcat,是因为Tomcat就是一个围绕着Servlet工作的一个服务器软件,最直观、也最简单粗暴的解释就是在Tomcat上运行的应用,都是基于servlet-api.jar这个jar包来开发的。打开tomcat安装目录下的conf文件夹中的server.xml,我们可以看到它的主要节点如下:

<Server>
	<Service>
		<Connector/>
		<Connector/>
		<Engine>
			<Host>
				<!--  现在常常使用自动部署,所以在配置文件中找不到Context元素,可以手动添加 -->
				<Context/>
			</Host>
		</Engine>
	</Service>
...
</Server>

其中,Engine、Host、Context都是继承于一个叫做Container的类,此外还有一个比较特殊的类:Wrapper,这个类是跟项目中我们写的每一个Servlet类(在SpringMVC中对应着Controller类中的一个方法)是一一对应的,在其具体实现类中,就是使用了一个Stack类来存储所有其对应着的Servlet类的实例。大家记住这个结论就行,如果你真的想了解其中的具体细节,就需要去看一下Tomcat的源码了,我后面如果有时间的话,可能也会出一个看Tomcat源码的系列博客。为了方便大家理解,我画了一个图来帮助大家理解。每个不同的部件我都用不同的颜色标了出来,通过下面的图可以很清楚的看到,一个Tomcat就是最外面棕色的框,然后一个Tomcat可以有多个Service组件,一个Service可以有多个Connector组件和一个Engine组件,值得注意的是,不同的Connector的端口号都必须是唯一的,不能跟其他的端口重复,这个只要是学过计算机网络的应该知道这个规则。而每个Engine组件对应着多个Host组件,每个Host组件又对应着多个Context组件,每个Context组件下面对应的则是Wrapper(一般情况下,大家理解为Servlet也是没有问题的)。这里说一下Host(虚拟主机)的作用:运行多个Web应用(一个代表一个Web应用),并负责安装、展开、启动和结束每个Web应用。其中,Context组件对应着的则是我们所开发的应用。
在这里插入图片描述

Tomcat的各个组件
Tomcat在启动时通过它自定义的类加载器去各个Host(虚拟主机)指定的路径下加载相关的Web应用,然后将Web应用部署其到对应的Host虚拟主机中,如果Host主机设置了自动部署,Tomcat 在运行时会定期检查是否有新的Web应用或Web应用是否进行了更新。

Tomca自定义的类加载器会按照如下的顺序扫描并加载相关应用和检查应用是否有更新:

  1. 扫描虚拟主机指定的xmlBase下的XML配置文件
  2. 扫描虚拟主机指定的appBase下的WAR文件
  3. 扫描虚拟主机指定的appBase下的应用目录

3 SSM的启动流程

当Tomcat把加载了应用之后,会先读取项目中的web.xml的内容,读取顺序是从上到下一行一行的读的,但是加载顺序是先加载context-param元素中的值,然后加载listener元素中的值,接着是加载filter元素中的值,最后才是servlet元素中的值。这里给一下我的web.xml对应的内容

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--定义web应用的名称-->
    <display-name>ssmdemo</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/spring-context.xml</param-value>
    </context-param>

    <!-- Spring上下文监听器,用来加载Spring的上下文配置并初始化Spring -->
    <listener>
        <description>启动spring容器</description>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- LOG4J上下文监听器,用来加载LOG4J2的配置并初始化LOG4J -->
    <listener>
        <listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
    </listener>

    <listener>
        <listener-class>com.yixiaojun.ssmdemo.listener.MySessionListener</listener-class>
    </listener>


    <!-- 字符编码过滤器,将编码改为UTF-8-->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <!--对所有的请求都进行过滤-->
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- SpringMVC前置控制器,拦截匹配的请求,把拦截下来的请求,根据相应的规则分发到目标Controller来处理-->
    <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 指定路径SpringMVC上下文配置路径,也可以使用默认的规则,即:/WEB-INF/<servlet-name>-servlet.xml,如spring-mvc-servlet.xml-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/spring-mvc.xml</param-value>
        </init-param>
        <!-- 随web应用启动而启动 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <!--指定所有请求都通过DispatcherServlet来处理-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>


</web-app>

所以我们就可以得出SSM的加载流程了,具体流程如下图:
在这里插入图片描述
我们也可以通过观看日志的输出顺序来查看项目的启动流程是怎么样的,下面是我从一个SSM项目的启动过程中抽取的一部分关键日志,说明就以注释形式写在里面了。

#Tomcat启动成功后,开始连接到Tomcat
Connected to server

#发现项目的war包,开始部署项目
Artifact ssmdemo:war exploded: Artifact is being deployed, please wait...

#加载监听器中配置的org.springframework.web.context.ContextLoaderListener的父类ContextLoader中的initWebApplicationContext 方法,初始化应用上下文
org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization started

#初始化SpringIOC容器
org.springframework.web.context.support.XmlWebApplicationContext.prepareRefresh Refreshing Root WebApplicationContext

#加载从context-param中配置的contextConfigLocation的值所对应的文件spring-context.xml
org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/spring-context.xml]

#加载spring-context.xml中配置的/mybatis-config.xml
org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:496) - Parsed configuration file: 'ServletContext resource [/WEB-INF/mybatis/mybatis-config.xml]'

#加载spring-context.xml指定的mapper目录下的xml文件
org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:528) - Parsed mapper file: 'file [D:\Project\ssmdemo\target\ssmdemo\WEB-INF\classes\mapper\UserMapper.xml]'

# 应用初始化完成
org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization completed in 1605 ms

#开始开始加载Servlet配置的内容,即调用org.springframework.web.servlet.DispatcherServlet的父类FrameworkServlet 的initServletBean
org.springframework.web.servlet.DispatcherServlet.initServletBean FrameworkServlet 'spring-mvc': initialization started

#初始化SpringMVC的IOC容器
org.springframework.web.context.support.XmlWebApplicationContext.prepareRefresh Refreshing WebApplicationContext for namespace 'spring-mvc-servlet': startup date [Wed May 27 00:25:46 CST 2020]; parent: Root WebApplicationContext

#通过servlet容器配置的contextConfigLocation所对应的的文件来生成SpirngMVC的BeanDefinitions
org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/spring-mvc.xml]

#生成静态资源的映射url路径
org.springframework.web.servlet.handler.SimpleUrlHandlerMapping.registerHandler Mapped URL path [/js/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'

#生成Controller类的中的方法的映射url路径
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.register Mapped "{[/user/login],methods=[GET]}" onto public void com.yixiaojun.ssmdemo.controller.UserController.login(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException

#初始化AOP要拦截的切点
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.initControllerAdviceCache Looking for @ControllerAdvice: WebApplicationContext for namespace 'spring-mvc-servlet': startup date [Wed May 27 00:25:46 CST 2020]; parent: Root WebApplicationContext

#SpirngMVC初始化完成
org.springframework.web.servlet.DispatcherServlet.initServletBean FrameworkServlet 'spring-mvc': initialization completed in 1584 ms

4 部分Spring相关源码

4.1ContextLoaderListener.java

package org.springframework.web.context;
public class ContextLoaderListener extends ContextLoader
implements ServletContextListener  {
	public void contextInitialized(ServletContextEvent event)  {
		this.initWebApplicationContext(event.getServletContext());
	}

}

4.2 ContextLoader.java

package org.springframework.web.context;

public class ContextLoader {

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	this.context  =  this.createWebApplicationContext(servletContext);
	this.configureAndRefreshWebApplicationContext(cwac, servletContext);
	//将初始化之后的上下文存到servletContext中
	servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
	return this.context;
	}
	
	protected  void configureAndRefreshWebApplicationContext( ConfigurableWebApplicationContext wac,ServletContext sc) {
		wac.setServletContext(sc);
		//从ServletContext获取contextConfigLocation的值
		configLocationParam  =  sc.getInitParameter("contextConfigLocation");
		if(configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
			}
			//刷新XmlWebApplicationContext(初始化SpringIOC容器)
			wac.refresh();
	}
}

4.3 AbstractApplicationContext.java

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean {
   public void refresh() throws BeansException, IllegalStateException {
   	Object var1 = this.startupShutdownMonitor;
   	synchronized(this.startupShutdownMonitor){
   	//刷新前准备工作,记录容器启动时间和标记
   	this.prepareRefresh();
   	//创建bean工厂,读取XML配置里的Bean信息,装载XML配置里面的BeanDefinition
   	ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
   	//准备Bean工厂需要的上下文信息,包括对 @Autowired和@Qualifier注解的属性注入
   	this.prepareBeanFactory(beanFactory); 
   	try {
   		this.postProcessBeanFactory(beanFactory);
   		//激活各种  BeanFactory  处理器
   		this.invokeBeanFactoryPostProcessors(beanFactory);
   		//注册各种BeanPostProcessors,用于在bean被初始化时进行拦截,进行额外初始化操作,具体可以查看Bean的生命周期
   		this.registerBeanPostProcessors(beanFactory); 
   		this.initMessageSource(); 
   		this.initApplicationEventMulticaster();
   		this.onRefresh();
   		this.registerListeners();
   		// 初始化所有未初始化的非懒加载的单例Bean
   		this.finishBeanFactoryInitialization(beanFactory);
   		this.finishRefresh();
   		}catch(BeansException  var9){
   		}
   		this.destroyBeans();
   		this.cancelRefresh(var9); throw  var9;
   		} finally  {
   		this.resetCommonCaches();
   		}
   	}
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SSM项目的启动流程深入解析 的相关文章

  • 如何使用 FileChannel 将一个文件的内容附加到另一个文件的末尾?

    File a txt好像 ABC File d txt好像 DEF 我正在尝试将 DEF 附加到 ABC 所以a txt好像 ABC DEF 我尝试过的方法总是完全覆盖第一个条目 所以我总是最终得到 DEF 这是我尝试过的两种方法 File
  • 如何在 JFace 的 TableViewer 中创建复选框?

    我创建了一个包含两列的 tableViewer 我想将其中一列设为复选框 为此 我创建了一个 CheckBoxCellEditor 但我不知道为什么它不起作用 名为 tableName 的列显示其值正常 色谱柱规格如下 String COL
  • Java程序中的数组奇怪的行为[重复]

    这个问题在这里已经有答案了 我遇到了这个 Java 程序及其以意想不到的方式运行 以下程序计算 int 数组中元素对之间的差异 import java util public class SetTest public static void
  • CXF Swagger2功能添加安全定义

    我想使用 org apache cxf jaxrs swagger Swagger2Feature 将安全定义添加到我的其余服务中 但是我看不到任何相关方法或任何有关如何执行此操作的资源 下面是我想使用 swagger2feature 生成
  • 如何为 Gson 编写自定义 JSON 反序列化器?

    我有一个 Java 类 用户 public class User int id String name Timestamp updateDate 我收到一个包含来自 Web 服务的用户对象的 JSON 列表 id 1 name Jonas
  • Microsoft Graph 身份验证 - 委派权限

    我可以使用 Microsoft Graph 访问资源无需用户即可访问 https developer microsoft com en us graph docs concepts auth v2 service 但是 此方法不允许我访问需
  • Clip 在 Java 中播放 WAV 文件时出现严重延迟

    我编写了一段代码来读取 WAV 文件 大小约为 80 mb 并播放该文件 问题是声音播放效果很差 极度滞后 你能告诉我有什么问题吗 这是我的代码 我称之为doPlayJframe 构造函数内的函数 private void doPlay f
  • 序列化对象以进行单元测试

    假设在单元测试中我需要一个对象 其中所有 50 个字段都设置了一些值 我不想手动设置所有这些字段 因为这需要时间而且很烦人 不知何故 我需要获得一个实例 其中所有字段都由一些非空值初始化 我有一个想法 如果我要调试一些代码 在某个时候我会得
  • 如何将文件透明地传输到浏览器?

    受控环境 IE8 IIS 7 ColdFusion 当从 IE 发出指向媒体文件 例如 mp3 mpeg 等 的 GET 请求时 浏览器将启动关联的应用程序 Window Media Player 我猜测 IIS 提供文件的方式允许应用程序
  • 从 android 简单上传到 S3

    我在网上搜索了从 android 上传简单文件到 s3 的方法 但找不到任何有效的方法 我认为这是因为缺乏具体步骤 1 https mobile awsblog com post Tx1V588RKX5XPQB TransferManage
  • Spring Data 与 Spring Data JPA 与 JdbcTemplate

    我有信心Spring Data and Spring Data JPA指的是相同的 但后来我在 youtube 上观看了一个关于他正在使用JdbcTemplate在那篇教程中 所以我在那里感到困惑 我想澄清一下两者之间有什么区别Spring
  • 制作java包

    我的 Java 类组织变得有点混乱 所以我要回顾一下我在 Java 学习中跳过的东西 类路径 我无法安静地将心爱的类编译到我为它们创建的包中 这是我的文件夹层次结构 com david Greet java greeter SayHello
  • 使用 AWS Java SDK 为现有 S3 对象设置 Expires 标头

    我正在更新 Amazon S3 存储桶中的现有对象以设置一些元数据 我想设置 HTTPExpires每个对象的标头以更好地处理 HTTP 1 0 客户端 我们正在使用AWS Java SDK http aws amazon com sdkf
  • Java直接内存:在自定义类中使用sun.misc.Cleaner

    在 Java 中 NIO 直接缓冲区分配的内存通过以下方式释放 sun misc Cleaner实例 一些比对象终结更有效的特殊幻像引用 这种清洁器机制是否仅针对直接缓冲区子类硬编码在 JVM 中 或者是否也可以在自定义组件中使用清洁器 例
  • 如何在 Maven 中显示消息

    如何在 Maven 中显示消息 在ant中 我们确实有 echo 来显示消息 但是在maven中 我该怎么做呢 您可以使用 antrun 插件
  • 当单元格内的 JComboBox 中有 ItemEvent 时,如何获取 CellRow

    我有一个 JTable 其中有一列包含 JComboBox 我有一个附加到 JComboBox 的 ItemListener 它会根据任何更改进行操作 但是 ItemListener 没有获取更改的 ComboBox 所在行的方法 当组合框
  • Android JNI C 简单追加函数

    我想制作一个简单的函数 返回两个字符串的值 基本上 java public native String getAppendedString String name c jstring Java com example hellojni He
  • android Accessibility-service 突然停止触发事件

    我有一个 AccessibilityService 工作正常 但由于开发过程中的某些原因它停止工作 我似乎找不到这个原因 请看一下我的代码并告诉我为什么它不起作用 public class MyServicee extends Access
  • java8 Collectors.toMap() 限制?

    我正在尝试使用java8Collectors toMap on a Stream of ZipEntry 这可能不是最好的想法 因为在处理过程中可能会发生异常 但我想这应该是可能的 我现在收到一个我不明白的编译错误 我猜是类型推理引擎 这是
  • javax.persistence.Table.indexes()[Ljavax/persistence/Index 中的 NoSuchMethodError

    我有一个 Play Framework 应用程序 并且我was使用 Hibernate 4 2 5 Final 通过 Maven 依赖项管理器检索 我决定升级到 Hibernate 4 3 0 Final 成功重新编译我的应用程序并运行它

随机推荐

  • pandas日期格式

    文章目录 pandas中的日期格式 一 提取日期的属性 二 日期的偏移操作 三 日期格式化 pandas中的日期格式 日期格式的字符串转换为日期格式使用pd to datetime data Order Date pd to datetim
  • motrix下载没速度_现在流行的几个下载神器

    一 Motrix 一个完全替代迅雷及aria2的下载工具 Motrix官网地址 https motrix app zh CN 特性 简洁明了的图形操作界面 支持BT和磁力链任务 支持下载百度云盘资源 最高支持 10 个任务同时下载 单任务最
  • c++ 传入字符串 带返回值_C/C++面试之17道经典编程题目分析!推荐收藏

    以下是C C 面试题目 共计17个题目 其中涵盖了c的各种基础语法和算法 以函数接口设计和算法设计为主 这17个题目在C C 面试方面已经流行了多 年 大家需要抽时间掌握好 每一个题目后面附有参考答案 希望读者能够抽 时间做完题目后在看参考
  • 2021年国赛高教杯数学建模C题生产企业原材料的订购与运输解题全过程文档及程序

    2021年国赛高教杯数学建模 C题 生产企业原材料的订购与运输 原题再现 某建筑和装饰板材的生产企业所用原材料主要是木质纤维和其他植物素纤维材料 总体可分为 A B C 三种类型 该企业每年按 48 周安排生产 需要提前制定 24 周的原材
  • spring boot 获取jar包中的资源

    public static void getJarResourceFile String fileDir String desDir File dir new File desDir File separator fileDir if di
  • vscode远程调试代码

    目录 ssh连接 xdebug调试 ssh连接 vscode中使用插件 这里用虚拟机测试 这里用虚拟机测试 注意ssh是可以连接的 然后安装好remote后 点击左下角的 gt lt 在弹出的这个上选择connect to host连接一台
  • 【探索Linux】—— 强大的命令行工具 P.9(进程地址空间)

    阅读导航 前言 一 内存空间分布 二 什么是进程地址空间 1 概念 2 进程地址空间的组成 三 进程地址空间的设计原理 1 基本原理 2 虚拟地址空间 概念 大小和范围 作用 虚拟地址空间的优点 3 页表 四 为什么要有地址空间 五 总结
  • 变量的解构和解析

    1 数组的解构赋值 基本用法 按照一定的模式 从数组和对象中提取值 对变量进行赋值 let a b c 1 2 2 如果解构不成功就是 undefine 如下就是解构不成功的 let a let a b 2 如果等号右边不是数组将会报错 如
  • 如何获取客户端MAC地址

    收藏 如何获取客户端MAC地址 方法一 调用Windows的DOS命令 从输出结果中读取MAC地址 public static String getMACAddress String address String os System get
  • CKEditor在线编辑器

    CKEditor在线编辑器 CKEditor是个专门使用在网上属于开放源码的文字编辑器 它用于轻量化 不需要太复杂的安装步骤即可使用 它可以和PHP javascript asp java等不同的编程语言相结合 接下来介绍CKEditor在
  • jsp+ssm计算机毕业设计网络身份认证技术及方法【附源码】

    项目运行 环境配置 Jdk1 8 Tomcat7 0 Mysql HBuilderX Webstorm也行 Eclispe IntelliJ IDEA Eclispe MyEclispe Sts都支持 项目技术 JSP SSM mybati
  • python生成词云图

    python生成词云图 前言 python版本号 3 6 3 在网上看到词云图 一直觉得很有意思 最近没工作很空闲 就想着自己做一做 先放个效果图 这是用杰伦的三首歌 七里香 搁浅 借口 歌词文本做成的词云图 python modules
  • C#写14443读卡器上位机

    1 DLL打包进EXE C 之DLL封装进EXEhttps www likecs com show 204353411 html 需要的软件ILMerge 链接 百度网盘 请输入提取码 密码 szk6 安装的时候有一个地方选的时候需要注意
  • Java RandomAccessFile用法

    原文 http blog csdn net akon vm article details 7429245 RandomAccessFile RandomAccessFile是用来访问那些保存数据记录的文件的 你就可以用seek 方法来访问
  • 三星手机忘记密码如何恢复出厂设置

    手机型号 Galaxy S21 Ultra 5G 操作步骤 同时按住 关机键 音量下键 长按7秒后重启 屏幕熄灭后同时按住 关机键 音量上键 进入恢复模式界面后 同时松开按键 进入 wipe data factory reset 模式 使用
  • RTKLIB软件源码学习(Kalman滤波&最小二乘)

    由于RTKLIB源码的最小二乘和kalman滤波函数邻近 这里直接一起解读 函数部分整体并不难 在了解初级矩阵函数的使用后就可以知道每一步代表的意思 首先是kalman的核心公式 这里仅基于公式进行代码解读 预测 A是状态矩阵 B是控制矩阵
  • Android 控制LED 屏

    翻电脑 发现2013年做的安卓控制LED屏软件 那个时候物联网还没这么火热 APP控制设备也没怎么普遍 刚刚到公司自己给公司做的第一项目就是这个APP 没有美工 界面什么哒都是自己瞎弄的 纪念一下
  • 如何禁止一个软件烦人的更新提示?

    从方法上分析有如下方案 1 打开本软件 首选项 设置不检查更新 2 逆向修改 exe 文件跳过 检查更新 的那个函数 3 操作系统 防火墙 设置禁止这个 程序连接外网 4 修改 hosts文件 把 更新server的 IP 解析为 0 0
  • linux查看文件夹大小命令

    这本阿里P8撰写的算法笔记 再次推荐给大家 身边不少朋友学完这本书最后加入大厂 Github 疯传 史上最强悍 阿里大佬 LeetCode刷题手册 开放下载了 当磁盘大小超过标准时会有报警提示 这时如果掌握df和du命令是非常明智的选择 d
  • SSM项目的启动流程深入解析

    1 环境说明 本文的内容基于Tomcat9 0 10 Spring 4 3 2 Tomcat加载应用的顺序 在我们正式介绍SSM项目是怎么启动之前 我们要先来简单介绍一下Tomcat 很多人在介绍Tomcat的时候 都把Tomcat叫做Se