我们谈到tomcat时,第一印象是它是一种servlet容器,这个概念是相当抽象和本质的,我们仍然对tomcat的内幕很陌生。我们知道,tomcat由Connector和Container两大组件构成,Connector在前面的文章已经介绍过了,今天我们就来看看Container是怎么回事。
一、Container基本结构
前文中有讲到,Connector和Container的初始化工作是由Digester解析conf/server.xml来完成的,而在server.xml中已经告诉了我们Container的基本结构。我们先来看看server.xml文件:
<Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> <Engine name="Catalina" defaultHost="localhost"> <Realm className="org.apache.catalina.realm.LockOutRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> </Host> </Engine> </Service> </Server>
通过xml文件,我们可以很清晰的看到,Server下包含了Service(可有多个),Service下包含了Connector和Engine,其实还可以包含Executor(线程池)。Engine正是与Connector处在同一水平的容器,Engine下面有Host,下面给出tomcat模块组成图。
前面在模拟tomcat连接器时,正是将servlet容器的实例作为参数传入到连接器的setContainer()方法中,这样连接器才能调用servlet容器的invoke()方法。
接下来给出一张大众化的Container组件结构图。
通过代码可以知道,tomcat提供了一个Container接口来抽象容器,并且细分了4种类型的容器,分别是Engine、Host、Context和Wrapper,对应不同的概念层次。
· Engine:表示整个Catalina的servlet引擎
· Host:表示一个拥有数个上下文的虚拟主机
· Context:表示一个Web应用,一个context包含一个或多个wrapper
· Wrapper:表示一个独立的servlet
Engine是顶层容器,java程序能够运行是因为有引擎即jvm,很自然的,Engine正是可以配置jvm(jvmRoute="jvm1")。Host是Engine的子容器,Context是Host的子容器,Wrapper是Context的子容器,这4个接口的标准实现分别是StandardEngine、StandardHost、StandardContext、StandardWrapper,她们都在org.apache.catalina.core包类,分析tomcat容器正式主要分析这4个类。下图展示了Container接口及其子接口和实现类的UML类图。
通过上面的两个图,我们知道了Container的结构,那么Container的初始化工作是怎么完成的呢?
二、Container初始化
回到Catalina类中,在load方法中调用了createStartDigester方法。
/** * Create and configure the Digester we will be using for startup. */ protected Digester createStartDigester() { Digester digester = new Digester(); // Initialize the digester // Configure the actions we will be using // 如果遇到”Server“元素起始符;则创建"org.apache.catalina.core.StandardServ // er"的一个实例对象,并压入堆栈;如果"Server"元素的"className"属性存在,那么用 // 这个属性的值所指定的class来创建实例对象,并压入堆栈。 digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); // 从server.xml读取"Server"元素的所有{属性:值}配对,用对应的Setter方法将属性值 // 设置到堆栈顶层元素(Server)。 digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className"); digester.addSetProperties("Server/Service"); digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service"); digester.addObjectCreate("Server/Service/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Listener"); digester.addSetNext("Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); //Executor //Connector // Add RuleSets for nested elements digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); digester.addRuleSet(new EngineRuleSet("Server/Service/")); digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/"); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); // When the 'engine' is found, set the parentClassLoader. digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(parentClassLoader)); addClusterRuleSet(digester, "Server/Service/Engine/Cluster/"); return (digester); }
从简化的源码中可以看到,digester对server.xml设置的标签动作有5种调用:
- addObjectCreate:遇到起始标签的元素,初始化一个实例对象入栈
- addSetProperties:遇到某个属性名,使用setter来赋值
- addSetNext:遇到结束标签的元素,调用相应的方法
- addRule:调用rule的begin 、body、end、finish方法来解析xml,入栈和出栈给对象赋值
- addRuleSet:调用addRuleInstances来解析xml标签
从这些规则和xml中可以看到,Calatina的Server对象是StandardServer。StandardService包含了多个Connector(xml中有2个connector)和一个StandardEngine Container。StandardEngine包含了一个Host Container。
三、Context容器加载web服务与热部署
从confg/server.xml中我们可以看到Server的容器的初始化只有Engine和Host,那么Context是什么时候初始化的呢,是怎么加载我们的web application,怎么实现的热部署呢?
先说结论,tomcat的Engine会启动一个线程,该线程每10s会发送一个事件,监听到该事件的部署配置类会自动去扫描webapp文件夹下的war包,将其加载成一个Context,即启动一个web服务。
OK,回过头看conf/server.xml和createStartDigester,添加了HostRuleSet,进入HostRuleSet类中,可以看到这么一行代码:
digester.addRule(prefix + "Host", new LifecycleListenerRule ("org.apache.catalina.startup.HostConfig", "hostConfigClass"));
继续进入LifecycleListenerRule类可以发现,在监听事件中增加了HostConfig类的对象,也就是StandardHost中新增了一个HostConfig监听器。再回过头来进入StandardEngine的starInternal方法super.startInternal(父类ContainerBase)中有这行代码:
threadStart();
进入后发现开启了一个线程,调用ContainerBackgroundProcessor这个的run方法,而这个run方法可以看到,
protected class ContainerBackgroundProcessor implements Runnable { @Override public void run() { // try { while (!threadDone) { try { Thread.sleep(backgroundProcessorDelay * 1000L);//在StandardEngine中构造方法设置默认backgroundProcessorDelay=10,即10s调用一次 } catch (InterruptedException e) { // Ignore } if (!threadDone) { // processChildren(parent, cl); } } } // } }
也就是说该线程每10s会调用一次processChildren,继续跟踪该方法,会看到调用Engine、Host、Context、Wrapper各容器组件及与它们相关的其它组件的backgroundProcess方法。
@Override public void backgroundProcess() { if (loader != null) { try { loader.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e); } } // fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null); }
这个方法中比较重要的两个
loader.backgroundProcess():调用了载入器的WebappLoader的backgroundProcess方法,进入这个方法可以看到:
public void backgroundProcess() { if (reloadable && modified()) { try { Thread.currentThread().setContextClassLoader (WebappLoader.class.getClassLoader()); if (container instanceof StandardContext) { ((StandardContext) container).reload(); } } finally { if (container.getLoader() != null) { Thread.currentThread().setContextClassLoader (container.getLoader().getClassLoader()); } } } else { closeJARs(false); } }
看判断条件reloadable和modified(),reloadable即为是否开启热部署,而modified()则是当前文件是否有修改的判断,当开启了热部署且有修改就会调用Context的reload方法进行重加载,实现web服务的**热部署**。
fireLifecycleEvent:对容器的监听对象发送Lifecycle.PERIODIC_EVENT事件,调用LifecycleListener的lifecycleEvent。
public void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(lifecycle, type, data); LifecycleListener interested[] = listeners; for (int i = 0; i < interested.length; i++) interested[i].lifecycleEvent(event); }
好的,前面说到StandardHost通server.xml配置了HostConfig监听器,那么进入HostConfig查看对该事件的响应方法lifecycleEvent
public void lifecycleEvent(LifecycleEvent event) { // Identify the host we are associated with try { host = (Host) event.getLifecycle(); if (host instanceof StandardHost) { setCopyXML(((StandardHost) host).isCopyXML()); setDeployXML(((StandardHost) host).isDeployXML()); setUnpackWARs(((StandardHost) host).isUnpackWARs()); setContextClass(((StandardHost) host).getContextClass()); } } catch (ClassCastException e) { log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e); return; } // 看事件与其对应的方法调用 if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) { check(); } else if (event.getType().equals(Lifecycle.START_EVENT)) { start(); } else if (event.getType().equals(Lifecycle.STOP_EVENT)) { stop(); } }
可以看到Lifecycle.PERIODIC_EVENT事件会调用其check方法。
protected void check() { if (host.getAutoDeploy()) {//这个条件对应这server.xml的Host配置的autoDeploy="true" DeployedApplication[] apps = deployed.values().toArray(new DeployedApplication[0]); for (int i = 0; i < apps.length; i++) { if (!isServiced(apps[i].name)) //资源查找 checkResources(apps[i], false); } if (host.getUndeployOldVersions()) { checkUndeploy(); } //部署 deployApps(); } }
很显然,如果server.xml的Host配置了能够自动部署(StandardHost默认autoDeploy=true),那么会调用deployApps方法。也就是说tomcat每10s会调用一次deployApps,完成**热部署**。当然,启动tomcat时则是START_EVENT,调用start()方法。
public void start() { // ... if (!appBase().isDirectory()) { log.error(sm.getString( "hostConfig.appBase", host.getName(), appBase().getPath())); host.setDeployOnStartup(false); host.setAutoDeploy(false); } if (host.getDeployOnStartup()) deployApps(); }
protected void deployApps() { File appBase = appBase(); File configBase = configBase(); String[] filteredAppPaths = filterAppPaths(appBase.list()); // Deploy XML descriptors from configBase deployDescriptors(configBase, configBase.list()); // Deploy WARs deployWARs(appBase, filteredAppPaths); // Deploy expanded folders deployDirectories(appBase, filteredAppPaths); }
可以看到可以通过xml,war包等直接部署!
protected void deployDescriptor(ContextName cn, File contextXml) { // Context context = null; // Class<?> clazz = Class.forName(host.getConfigClass());//默认值:ContextConfig LifecycleListener listener = (LifecycleListener) clazz.newInstance(); context.addLifecycleListener(listener); // host.addChild(context); // }
而部署的过程,其实就是创建了Context对象,并添加到Host中。
此外从HostConfig部署Contex的方法中可以看到,有3中方式部署war包:
1 在server.xml的Host标签中声明Context标签
2 将war包放入webapps中
3 context.xml配置方式
至此,我们已经知道了Engine、Host、Context的加载了,同时也知道了tomcat是怎么加载我们的web服务,是怎么实现的热部署。那么接下来就剩下最后一个Wrapper的加载了。
很捉急,在server.xml中没有关于Wrapper的初始化加载,那么在哪里呢?
同样回到,上面的deployApps()方法中,在其三种部署方式中都有一节代码
Class<?> clazz = Class.forName(host.getConfigClass());//默认值:ContextConfig LifecycleListener listener = (LifecycleListener) clazz.newInstance(); context.addLifecycleListener(listener);
这段代码的作用是给Context容器添加了ContextConfig监听器。而在Context的startInternal方法中,发送了监听事件:
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
ContextConfig监听到该事件,调用configureStart方法,在该方法中调用webConfig(),webConfig完成web.xml解析,生成servlet、filter等信息,并配置加载Wrapper。通过对ContextConfig的分析可以知道,Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。
四、关于StandardContext
StandardContext简单点说就是Servlet容器,是需要我们重点研究的对象,它包含了ApplicationContext (implements ServletContext),查看代码,我们可以发现容器对web.xml中相关元素的加载过程。
//// startInternal() // Set up the context init params mergeParameters(); // Call ServletContainerInitializers ex. SpringServletContainerInitializer for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) { try { entry.getKey().onStartup(entry.getValue(), getServletContext()); } catch (ServletException e) { log.error(sm.getString("standardContext.sciFail"), e); ok = false; break; } } // Configure and call application event listeners if (ok) { if (!listenerStart()) { log.error( "Error listenerStart"); ok = false; } } try { // Start manager if ((manager != null) && (manager instanceof Lifecycle)) { ((Lifecycle) getManager()).start(); } } catch(Exception e) { log.error("Error manager.start()", e); ok = false; } // Configure and call application filters if (ok) { if (!filterStart()) { log.error("Error filterStart"); ok = false; } } // Load and initialize all "load on startup" servlets if (ok) { if (!loadOnStartup(findChildren())){ log.error("Error loadOnStartup"); ok = false; } }我们常用的spring的ContextLoaderListener就是在此处加载的,注意,在Servlet3.0之前,listener不是按顺序调用的。关于Servlet,强烈建议读一下《Servlet规范》。
好了,从Engine---Host---Contex----Wrapper这个链路上的容器初始化和app加载已经完成。接下来的文章我们来看请求在容器里所走过的代码逻辑。
相关推荐
1.版本:matlab2014/2019a/2021a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
1.版本:matlab2014/2019a/2021a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
1.版本:matlab2014/2019a/2021a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
在Java中,实现二叉树的中序遍历同样可以通过递归来完成。中序遍历的顺序是:首先递归地中序遍历左子树,然后访问根节点,最后递归地中序遍历右子树。 在这段代码中,Node类定义了二叉树的节点,BinaryTree类包含一个指向根节点的指针和inOrder方法,用于递归地进行中序遍历。printInOrder方法调用inOrder方法并打印出遍历的结果。 在Main类中,我们创建了一个示例二叉树,并调用printInOrder方法来输出中序遍历的结果。输出应该是:4 2 5 1 3,这表示中序遍历的顺序是左子树(4),然后是根节点(2),接着是右子树的左子树(5),然后是右子树的根节点(1),最后是右子树的右子树(3)。
无头单向非循环链表的实现(函数定义文件)
"PTA" 通常指的是一种在线编程平台,例如“Pata”或者某些特定学校或组织的编程练习与自动评测系统。在这种平台或系统中,学生或程序员会提交代码来解决各种问题,然后系统会自动运行并评测这些代码的正确性。 当提到“两个有序链表的合并PTA”时,这通常意味着在PTA平台上解决一个特定的问题,即合并两个有序链表。具体任务可能是给定两个已按升序排序的链表,要求编写代码来合并这两个链表,形成一个新的有序链表。
1.版本:matlab2014/2019a/2021a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
搜索引擎的设计与实现
工作总结,新年计划,岗位总结,工作汇报,个人总结,述职报告,范文下载,新年总结,新建计划。
【基于Springboot+Vue的Java毕业设计】无人超市管理系统项目实战(源码+录像演示+说明).rar 【项目技术】 开发语言:Java 框架:Spingboot+vue 架构:B/S 数据库:mysql 【演示视频-编号:314】 https://pan.quark.cn/s/8dea014f4d36 【实现功能】 无人超市管理系统有管理员,用户两个角色。管理员功能有个人中心,用户管理,商品类型管理,支付类型管理,公告类型管理,商品信息管理,出入库管理,出入库详情管理,购买管理,购买详情管理,公告信息管理。用户可以注册登录,自助购买,点击购买管理里面收银就可以选择支付类型和商品然后提交,还可以查看购买详情和公告信息。
1.版本:matlab2014/2019a/2021a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
年会班会资料,节目策划,游戏策划,策划案,策划方案,活动方案,筹办,公司年会,开场白,主持人,策划主题,主持词,小游戏。
在现有省、市港口信息化系统进行有效整合基础上,借鉴新 一代的感知-传输-应用技术体系,实现对码头、船舶、货物、重 大危险源、危险货物装卸过程、航管航运等管理要素的全面感知、 有效传输和按需定制服务,为行政管理人员和相关单位及人员提 供高效的管理辅助,并为公众提供便捷、实时的水运信息服务。 建立信息整合、交换和共享机制,建立健全信息化管理支撑 体系,以及相关标准规范和安全保障体系;按照“绿色循环低碳” 交通的要求,搭建高效、弹性、高可扩展性的基于虚拟技术的信 息基础设施,支撑信息平台低成本运行,实现电子政务建设和服务模式的转变。 实现以感知港口、感知船舶、感知货物为手段,以港航智能 分析、科学决策、高效服务为目的和核心理念,构建“智慧港口”的发展体系。 结合“智慧港口”相关业务工作特点及信息化现状的实际情况,本项目具体建设目标为: 一张图(即GIS 地理信息服务平台) 在建设岸线、港口、港区、码头、泊位等港口主要基础资源图层上,建设GIS 地理信息服务平台,在此基础上依次接入和叠加规划建设、经营、安全、航管等相关业务应用专题数据,并叠 加动态数据,如 AIS/GPS/移动平台数据,逐步建成航运管理处 "一张图"。系统支持扩展框架,方便未来更多应用资源的逐步整合。 现场执法监管系统 基于港口(航管)执法基地建设规划,依托统一的执法区域 管理和数字化监控平台,通过加强对辖区内的监控,结合移动平 台,形成完整的多维路径和信息追踪,真正做到问题能发现、事态能控制、突发问题能解决。 运行监测和辅助决策系统 对区域港口与航运业务日常所需填报及监测的数据经过科 学归纳及分析,采用统一平台,消除重复的填报数据,进行企业 输入和自动录入,并进行系统智能判断,避免填入错误的数据, 输入的数据经过智能组合,自动生成各业务部门所需的数据报 表,包括字段、格式,都可以根据需要进行定制,同时满足扩展 性需要,当有新的业务监测数据表需要产生时,系统将分析新的 需求,将所需字段融合进入日常监测和决策辅助平台的统一平台中,并生成新的所需业务数据监测及决策表。 综合指挥调度系统 建设以港航应急指挥中心为枢纽,以各级管理部门和经营港 口企业为节点,快速调度、信息共享的通信网络,满足应急处置中所需要的信息采集、指挥调度和过程监控等通信保障任务。 设计思路 根据项目的建设目标和“智慧港口”信息化平台的总体框架、 设计思路、建设内容及保障措施,围绕业务协同、信息共享,充 分考虑各航运(港政)管理处内部管理的需求,平台采用“全面 整合、重点补充、突出共享、逐步完善”策略,加强重点区域或 运输通道交通基础设施、运载装备、运行环境的监测监控,完善 运行协调、应急处置通信手段,促进跨区域、跨部门信息共享和业务协同。 以“统筹协调、综合监管”为目标,以提供综合、动态、实 时、准确、实用的安全畅通和应急数据共享为核心,围绕“保畅通、抓安全、促应急"等实际需求来建设智慧港口信息化平台。 系统充分整合和利用航运管理处现有相关信息资源,以地理 信息技术、网络视频技术、互联网技术、移动通信技术、云计算 技术为支撑,结合航运管理处专网与行业数据交换平台,构建航 运管理处与各部门之间智慧、畅通、安全、高效、绿色低碳的智 慧港口信息化平台。 系统充分考虑航运管理处安全法规及安全职责今后的变化 与发展趋势,应用目前主流的、成熟的应用技术,内联外引,优势互补,使系统建设具备良好的开放性、扩展性、可维护性。
【基于Java+Springboot的毕业设计】线上医院挂号系统(源码+演示视频+说明).rar 【项目技术】 开发语言:Java 框架:Spingboot+vue 架构:B/S 数据库:mysql 【演示视频-编号:300】 https://pan.quark.cn/s/8dea014f4d36 【实现功能】 本次开发的线上医院挂号系统实现了字典管理、论坛管理、会员管理、单页数据管理、医生管理、医生留言管理、医生挂号订单管理、管理员管理等功能。
工作总结,新年计划,岗位总结,工作汇报,个人总结,述职报告,范文下载,新年总结,新建计划。
Excel数据看板,Excel办公模板,Excel模板下载,Excel数据统计,数据展示
最全英语六级真题,从12年到23年总共66个真题。全网最全。
工作总结,新年计划,岗位总结,工作汇报,个人总结,述职报告,范文下载,新年总结,新建计划。
基于深度学习的人体姿态识别.zip
01. XX塑业有限公司ERP物料编码规则(DOC 6页).doc