浅读tomcat架构设计之tomcat容器Container(3)
Container是tomcat容器的接口,接口位置org.apache.catalina.Container
Container一共有4个子接口,分别是:Engine,Host,Context,Wrapper和一个默认实现类ContainerBase:
截图看下就会清晰很多:
不知道为啥,截图后画质那么的马赛克...
每个子接口都是一个容器,这四个子容器都有一个对应的Standard***实现类:
红线标出来的Standard***对应的是具体子容器的的实现类,并且这些子容器实现类都继承自ContainerBase类,以其中一个子容器实现类为例:
而ContainerBase又继承自LifecycleMBeanBase
所以四个子容器都是tomcat生命周期管理模式,看完子容器和子容器的接口实现类后,给我的感觉就是无限套娃
下面讲讲四个子容器的作用含义,它们和我们的tomcat部署项目/发布项目息息相关,具有很强的关联性
Engine:引擎,用于管理多个站点,一个Service最多只能有一个Engine
Host:代表一个站点,也可以叫虚拟主机,通过配置host就可以添加站点 www.xxx.com就是Host,代表我们的站点,oa.xxx.com是我们的另外一个Host,他指向了另一个站点
Context:代表一个应用程序,对应着我们开发的系统,或者是WEB-INF目录及下面的web.xml文件,通俗易懂点来说www.xxx.com/xxx,xxx应用就是Context
Wrapper:每个Wrapper封装着一个Servlet 其实就是web.xml中配置的Servlet
4种容器的配置方法:
Engine和Host的配置都在tomcat conf/server.xml中,server.xml是tomcat最重要的配置文件,tomcat大部分功能都可以在这个文件中配置
<Engine name="Catalina" defaultHost="localhost">
我安装tomcat,默认Engine的设置是Catalina,默认的Host站点使用是localhost,这里可以自己设置成别的Host站点.
Context有三种配置方法 (1)通过文件配置 (2)war包上传放到Host目录下,tomcat会自动查找自解压 (3)将应用的文件夹放在Host目录下,tomcat会自动查找并添加到Host中
tocmat部署不想长篇大论讲了, tomcat部署参考:https://www.cnblogs.com/ysocean/p/6893446.html ,里面讲了好几种方法.
Wrapper的配置就是我们web.xml中配置的Servlet,一个Servlet对应一个Wrapper.
Container的启动是通过init和start方法来完成的,Container的四个子容器的共同父类都是ConatinerBase,ConatinerBase类定义了两个方法,分别是:startInternal和initInternal
先聊聊ContainerBase类的这两个方法的具体实现:
org.apache.catalina.core.ContainerBase:
protected void initInternal() throws LifecycleException { BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue(); this.startStopExecutor = new ThreadPoolExecutor(this.getStartStopThreadsInternal(), this.getStartStopThreadsInternal(), 10L, TimeUnit.SECONDS, startStopQueue, new ContainerBase.StartStopThreadFactory(this.getName() + "-startStop-")); this.startStopExecutor.allowCoreThreadTimeOut(true); super.initInternal(); }
initInternal方法主要是先是初始化,创建对象,然后创建一个线程池来管理启动和关闭内部线程
ThreadPoolExecutor继承自Executor用于管理线程,一直往上回溯,会发现是Executor
接着看startInternal方法:
protected synchronized void startInternal() throws LifecycleException { this.logger = null; this.getLogger(); Cluster cluster = this.getClusterInternal(); if (cluster != null && cluster instanceof Lifecycle) { ((Lifecycle)cluster).start(); } Realm realm = this.getRealmInternal(); if (realm != null && realm instanceof Lifecycle) { ((Lifecycle)realm).start(); } Container[] children = this.findChildren(); List<Future<Void>> results = new ArrayList(); for(int i = 0; i < children.length; ++i) { results.add(this.startStopExecutor.submit(new ContainerBase.StartChild(children[i]))); } boolean fail = false; Iterator i$ = results.iterator(); while(i$.hasNext()) { Future result = (Future)i$.next(); try { result.get(); } catch (Exception var9) { log.error(sm.getString("containerBase.threadedStartFailed"), var9); fail = true; } } if (fail) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed")); } else { if (this.pipeline instanceof Lifecycle) { ((Lifecycle)this.pipeline).start(); } this.setState(LifecycleState.STARTING); this.threadStart(); } }
startInternal方法很长,拆分下代码讲解:
Cluster cluster = this.getClusterInternal(); if (cluster != null && cluster instanceof Lifecycle) { ((Lifecycle)cluster).start(); } Realm realm = this.getRealmInternal(); if (realm != null && realm instanceof Lifecycle) { ((Lifecycle)realm).start(); }
如果cluster和realm不为null,并且cluster/realm引用指向LIfecycle,就调用start方法
继续往下拆分看:
Container[] children = this.findChildren(); List<Future<Void>> results = new ArrayList(); for(int i = 0; i < children.length; ++i) { results.add(this.startStopExecutor.submit(new ContainerBase.StartChild(children[i]))); }
这部分代码是调用所有子容器的start方法来启动子容器,跟进ContainerBase.StartChild函数看看:
继续往下拆分代码:
if (fail) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed")); } else { if (this.pipeline instanceof Lifecycle) { ((Lifecycle)this.pipeline).start(); } this.setState(LifecycleState.STARTING); this.threadStart(); }
这段代码大致就是调用管道中start方法,启动完成后将生命周期设置成LifecycleState.STARTING,最后启动后台线程:
Engine:
前面我们讲了,四大子容器的实现类是Standard***类.那么Engine的默认实现类是StandardEngine类:
org.apache.catalina.core.StandardEngine:
如果使用Engine就会调用Engine默认实现类StandardEngine的initInternal和startInternal方法:
我们来看看:
protected void initInternal() throws LifecycleException { this.getRealm(); super.initInternal(); } protected synchronized void startInternal() throws LifecycleException { if (log.isInfoEnabled()) { log.info("Starting Servlet Engine: " + ServerInfo.getServerInfo()); } super.startInternal(); }
代码不是很多,发现他是调用他父类的方法,他的父类是ContainerBase,我们一开始就分析过ContainerBase类的initInternal和startInternal方法了.
Host:
Host接口的默认实现类是org.apache.catalina.core.StandardHost:
跟进类,发现StandardHost没有重写父类的initInternal,只有startInternal方法被重写:
如果他没有,初始化会默认会调用它父类的initInternal
startInternal方法代码如下:
protected synchronized void startInternal() throws LifecycleException { String errorValve = this.getErrorReportValveClass(); if (errorValve != null && !errorValve.equals("")) { try { boolean found = false; Valve[] valves = this.getPipeline().getValves(); Valve[] arr$ = valves; int len$ = valves.length; for(int i$ = 0; i$ < len$; ++i$) { Valve valve = arr$[i$]; if (errorValve.equals(valve.getClass().getName())) { found = true; break; } } if (!found) { Valve valve = (Valve)Class.forName(errorValve).newInstance(); this.getPipeline().addValve(valve); } } catch (Throwable var8) { ExceptionUtils.handleThrowable(var8); log.error(sm.getString("standardHost.invalidErrorReportValveClass", new Object[]{errorValve}), var8); } } super.startInternal(); }
代码也是好多一大块.
拆开一部分:
截图的是重点,主要做的就是获取管道的值,然后判断管理里面是否有指定的Valve
如果没找到管道值,就调用addValve方法,加入到管道中,代码如下:
if (!found) { Valve valve = (Valve)Class.forName(errorValve).newInstance(); this.getPipeline().addValve(valve); }
这是个重点,对我们后续讲解tomcat 内存马提供很大的帮助,valve的内存马注入是一定要调用addValue的,否则匹配不到!
Host的启动除了调用StandardHost的startInternal方法,还会调用HostConfig中的一些方法:
对应位置:org.apache.catalina.startup.HostConfig
这个类主要作用就是Host站点的配置
比较重要的就是部署应用了,deployApps方法,代码如下
protected void deployApps() { File appBase = this.host.getAppBaseFile(); File configBase = this.host.getConfigBaseFile(); String[] filteredAppPaths = this.filterAppPaths(appBase.list()); this.deployDescriptors(configBase, configBase.list()); this.deployWARs(appBase, filteredAppPaths); this.deployDirectories(appBase, filteredAppPaths); }
上面介绍了三种方法来部署tomcat项目,
(1)通过xml:
(2):war包部署
(3)文件夹部署:
Context:
Context的默认实现类是org.apache.catalina.core.StandardContext:
startInternal方法如下:
拆取比较重要部分,方法内容实在是太多了:
if (ok && !this.listenerStart()) { log.error("Error listenerStart"); ok = false; } if (ok) { this.checkConstraintsForUncoveredMethods(this.findConstraints()); } try { Manager manager = this.getManager(); if (manager != null && manager instanceof Lifecycle) { ((Lifecycle)manager).start(); } } catch (Exception var19) { log.error("Error manager.start()", var19); ok = false; } if (ok && !this.filterStart()) { log.error("Error filterStart"); ok = false; } if (ok && !this.loadOnStartup(this.findChildren())) { log.error("Error loadOnStartup"); ok = false; }
主要含义就是调用了web.xml中定义的Listener,另外还初始化了filterStart和loadOnStartup
filterStart:org.apache.catalina.core.StandardContext:
使用filterConfigs存储键值对,前面简单看了hostConfig,filterConfig同理:
他的实例类是:org.apache.catalina.core.ApplicationFilterConfig,等我讲Filter内存马的时候再详细解释它,这个先放一边.
Wrapper:
Wrapper是封装Servlet的包装器,Wrapper的默认实现类,没有重写initInternal,他默认调用父类的initInternal方法,startInternal方法代码如下:
org.apache.catalina.core.StandardWrapper:
protected synchronized void startInternal() throws LifecycleException { Notification notification; if (this.getObjectName() != null) { notification = new Notification("j2ee.state.starting", this.getObjectName(), (long)(this.sequenceNumber++)); this.broadcaster.sendNotification(notification); } super.startInternal(); this.setAvailable(0L); if (this.getObjectName() != null) { notification = new Notification("j2ee.state.running", this.getObjectName(), (long)(this.sequenceNumber++)); this.broadcaster.sendNotification(notification); } }
代码很简单,(1)使用broadcaster发送消息 (2)调用父类的startInternal (3)调用setAvailate方法,设置Servlet的有效时间