目录
Catalina中解析server.xml的配置规则
Lifecycle/Lifecyclebase
Containerbase
initInternal
startInternal
threadStart
backgroundProcess
StandardEngine
initInternal
startInternal
EngineConfig
StandardHost
initInternal
startInternal
HostConfig
beforeStart
start
stop
check
StandardContext
才疏学浅,现在阅读源码也是学习笔记,大神勿喷,主要是对web应用程序启动加载的阅读
Connector组件最终调用如下方法将请求送入容器处理,Tomcat中的Container容器有四个子容器,分别是Engine,Host,Context,Wrapper,这四个按顺序之间是父子关系,还有个Pipeline管道的概念,pipeline中链表结构,存放Value组件,每个容器中都有pipeline,责任链模式,请求会在容器的管道每个Value中依次处理
connector.getService().getContainer().getPipeline().getFirst().invoke( request, response);
如下图的XML配置文件,一个Engine容器管理多个虚拟主机,每个Host下可以管理多个应用程序Context,每个Context中又存在多个处理程序的Warpper(Servlet)
另外的Realm组件是进行权限控制(加载tomcat-user.xml访问应用时验证),Context下Value组件是对每次请求进行日志处理
Catalina中解析server.xml的配置规则
org.apache.catalina.startup.Catalina#createStartDigester
tomcat启动时,解析server.xml,配置对各组件的解析规则,再一步一步根据规则,从父组件到子组件,挨个解析、初始化、启动。
EngineRuleSet
HostRuleSet
ContextRuleSet
为三个容器都加上了一个生命周期事件监听器,分别是EngineConfig, HostConfig, ContextConfig,默认是这三类,可以自定义。这三个监听器在对应容器初始化,启动前等对容器进行配置等,例如ContextConfig在启动Context前,查找应用程序的web.xml,以及获取初始化应用程序的类(SpringBoot程序获取SpringBootServletInitializer之类)。
Lifecycle/Lifecyclebasetomcat几乎所有组件都继承了这个类,控制生命周期,其中定义了init()、start()、stop()等入口方法,组件都使用这些方法进行启动和终止。主要就是设置当前容器状态并对当前组件监听器发布相关事件,例如init_beforeinit_afterstart_beforestart_after等等,再调用子类实现的initInternal()、startInternal()等方法处理核心逻辑
ContainerbaseStandardEngine,StandardHost,StandardContext,StandardWrapper四个容器实现类都继承自父类Containerbase,先来看父类中进行了什么 *** 作
initInternal初始化核心方法,初始化前后会发布 before_init 和 after_init 事件,容器监听器处理事件
创建了一个用于启动/停止子容器的线程池,用来异步启动/停止子容器
public abstract class Containerbase extends LifecycleMBeanbase implements Container { @Override protected void initInternal() throws LifecycleException { // 启动和停止子容器使用的线程池,异步 *** 作子容器 BlockingQueuestartInternalstartStopQueue = new linkedBlockingQueue<>(); startStopExecutor = new ThreadPoolExecutor( getStartStopThreadsInternal(), getStartStopThreadsInternal(), 10, TimeUnit.SECONDS, startStopQueue, new StartStopThreadFactory(getName() + "-startStop-")); startStopExecutor.allowCoreThreadTimeOut(true); // 注册MBean super.initInternal(); } }
启动容器核心方法,启动前后会发布 before_start 和 after_start 事件,容器监听器处理事件
启动容器下配置的集群/权限组件,并异步启动子容器,发布当前容器Starting事件,启动后台周期任务线程
@Override protected synchronized void startInternal() throws LifecycleException { // 启动子容器和各种组件 logger = null; getLogger(); // 自带集群组件,session同步 Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { ((Lifecycle) cluster).start(); } // Realm权限控制组件启动 Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).start(); } // 线程池异步启动子容器 // StartChild任务里边就是child.start();一行 Container children[] = findChildren(); ListthreadStart> results = new ArrayList<>(); for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StartChild(children[i]))); } MultiThrowable multiThrowable = null; for (Future result : results) { try { result.get(); } catch (Throwable e) { log.error(sm.getString("containerbase.threadedStartFailed"), e); if (multiThrowable == null) { multiThrowable = new MultiThrowable(); } multiThrowable.add(e); } } if (multiThrowable != null) { throw new LifecycleException(sm.getString("containerbase.threadedStartFailed"), multiThrowable.getThrowable()); } // 启动管道中的链式Value组件 if (pipeline instanceof Lifecycle) { ((Lifecycle) pipeline).start(); } // 设置启动中状态,发布容器STARTING事件 setState(LifecycleState.STARTING); // 每个容器都会有后台线程周期性的执行后台任务 threadStart(); }
启动线程,周期的执行当前容器及子容器的后台任务,默认情况下,Engine会10秒一次执行子容器的后台任务
protected void threadStart() { // 当前容器已经启动就return if (thread != null) return; // 如果配置的后台周期任务间隔事件大于0才会启动线程执行后台任务 // 默认Engine初始化为10,会启动一个 // 除此之外其他容器server.xml不配置的话都默认-1,不启动 if (backgroundProcessorDelay <= 0) return; threadDone = false; String threadName = "ContainerBackgroundProcessor[" + toString() + "]"; thread = new Thread(new ContainerBackgroundProcessor(), threadName); thread.setDaemon(true); thread.start(); } protected class ContainerBackgroundProcessor implements Runnable { @Override public void run() { // 删除了try-catch-finally代码 while (!threadDone) { try { Thread.sleep(backgroundProcessorDelay * 1000L); } catch (InterruptedException e) { // Ignore } if (!threadDone) { // 依次执行当前容器的backgroundProcess方法和子容器的backgroundProcess方法 processChildren(Containerbase.this); } } } protected void processChildren(Container container) { ClassLoader originalClassLoader = null; try { if (container instanceof Context) { Loader loader = ((Context) container).getLoader(); if (loader == null) { return; } originalClassLoader = ((Context) container).bind(false, null); } // 执行本容器的backgroundProcess方法 container.backgroundProcess(); Container[] children = container.findChildren(); for (int i = 0; i < children.length; i++) { // 如果子容器的间隔时间未设置才会继续调用子容器的backgroundProcess方法 // 因为如果设置了间隔时间,该容器会有自己的后台线程去执行 if (children[i].getBackgroundProcessorDelay() <= 0) { processChildren(children[i]); } } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } finally { if (container instanceof Context) { ((Context) container).unbind(false, originalClassLoader); } } } }backgroundProcess
容器后台周期执行的动作
执行该容器下各组件的 backgroundProcess 后台任务,并发布当前容器的 periodic 周期事件
@Override public void backgroundProcess() { if (!getState().isAvailable()) return; Cluster cluster = getClusterInternal(); if (cluster != null) { try { cluster.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerbase.backgroundProcess.cluster", cluster), e); } } Realm realm = getRealmInternal(); if (realm != null) { try { realm.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerbase.backgroundProcess.realm", realm), e); } } Valve current = pipeline.getFirst(); while (current != null) { try { current.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerbase.backgroundProcess.valve", current), e); } current = current.getNext(); } fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null); }StandardEngine
没什么重要的,主要就是设置了backgroundProcessorDelay参数,在engine中启动了执行后台周期任务的线程,默认情况下子容器host,context等都不会设置这个参数,因此后台任务线程默认情况只有一个
public StandardEngine() { super(); // engine容器具有一个基本的处理请求的Value组件StandardEnginevalve pipeline.setBasic(new StandardEnginevalve()); try { setJvmRoute(System.getProperty("jvmRoute")); } catch(Exception ex) { log.warn(sm.getString("standardEngine.jvmRouteFail")); } // engine默认存在一个周期任务线程 // 间隔10秒执行一次engine和其子容器的backgroundProcess 方法 backgroundProcessorDelay = 10; }initInternal
@Override protected void initInternal() throws LifecycleException { // 获取Realm配置 getRealm(); // 执行Containerbase中方法,创建startstop线程池 super.initInternal(); } @Override public Realm getRealm() { // 获取xml中配置的Realm Realm configured = super.getRealm(); // 确保engine存在一个Realm对象,如果没配置给个空实现 if (configured == null) { configured = new NullRealm(); this.setRealm(configured); } return configured; }startInternal
啥也没干,打印了一行信息 ,直接走父类,启动子组件和子容器
@Override protected synchronized void startInternal() throws LifecycleException { // Log our server identification information if(log.isInfoEnabled()) log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo()); // Standard container startup super.startInternal(); }EngineConfig
Engine容器的默认生命周期监听器,继承自LifecycleListener,关注Start和Stop事件,没干实质的东西,打印了两行
@Override public void lifecycleEvent(LifecycleEvent event) { // Identify the engine we are associated with try { engine = (Engine) event.getLifecycle(); } catch (ClassCastException e) { log.error(sm.getString("engineConfig.cce", event.getLifecycle()), e); return; } // Process the event that has occurred if (event.getType().equals(Lifecycle.START_EVENT)) start(); else if (event.getType().equals(Lifecycle.STOP_EVENT)) stop(); } protected void start() { if (engine.getLogger().isDebugEnabled()) engine.getLogger().debug(sm.getString("engineConfig.start")); } protected void stop() { if (engine.getLogger().isDebugEnabled()) engine.getLogger().debug(sm.getString("engineConfig.stop")); }StandardHost
public StandardHost() { super(); // 设置默认的Host容器的基础Value组件 pipeline.setBasic(new StandardHostValve()); }initInternal
Host并没有自己独有的初始化动作,走父类Containerbase.initInternal方法,初始化start-stop线程池
startInternal为当前Host容器增加一个Value组件,ErrorReportValve,用来返回如下的错误界面
@Override protected synchronized void startInternal() throws LifecycleException { // Set error report valve String errorValve = getErrorReportValveClass(); if ((errorValve != null) && (!errorValve.equals(""))) { try { boolean found = false; Valve[] valves = getPipeline().getValves(); for (Valve valve : valves) { if (errorValve.equals(valve.getClass().getName())) { found = true; break; } } if(!found) { Valve valve = (Valve) Class.forName(errorValve).getConstructor().newInstance(); getPipeline().addValve(valve); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString( "standardHost.invalidErrorReportValveClass", errorValve), t); } } super.startInternal(); }HostConfig
Host容器的监听器,用来部署其下配置的context,所谓部署,即就是根据配置将对应路径下的应用文件夹/War包封装为一个Context容器对象添加到当前Host子容器,然后启动Context容器,并监听该Context的配置文件,如果被修改了,进行重新部署
public class HostConfig implements LifecycleListener { public void lifecycleEvent(LifecycleEvent event) { // Identify the host we are associated with try { host = (Host) event.getLifecycle(); if (host instanceof StandardHost) { // 将StandardHost一些属性设置到HostConfig中 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.BEFORE_START_EVENT)) { // 启动前事件 before_start beforeStart(); } else if (event.getType().equals(Lifecycle.START_EVENT)) { // start事件 start(); } else if (event.getType().equals(Lifecycle.STOP_EVENT)) { // 停止事件 stop(); } } }beforeStart
Host容器startInternal() 之前发布before_start事件,进入此方法
判断两个文件夹是否存在,否则创建:例如我的D:TomcatSourceCodetomcat8webapps 应用文件夹 和 D:TomcatSourceCodetomcat8confCatalinalocalhost 存放host下应用的配置文件
public void beforeStart() { if (host.getCreateDirs()) { File[] dirs = new File[] {host.getAppbaseFile(),host.getConfigbaseFile()}; for (int i=0; istart 多途径部署应用Context
public void start() { try { // 将当前HostConfig注册MBean ObjectName hostON = host.getObjectName(); oname = new ObjectName (hostON.getDomain() + ":type=Deployer,host=" + host.getName()); Registry.getRegistry(null, null).registerComponent (this, oname, this.getClass().getName()); } catch (Exception e) { log.warn(sm.getString("hostConfig.jmx.register", oname), e); } // 默认启动时自动部署host下的app, 可xml中配置Host.deployOnStartup属性修改 if (host.getDeployonStartup()) deployApps(); } // 部署 protected void deployApps() { // %TOMCAT_HOME%webapps File appbase = host.getAppbaseFile(); // %TOMCAT_HOME%confCatalinalocalhost File configbase = host.getConfigbaseFile(); // 获取%TOMCAT_HOME%webapps下的文件列表 // Host标签有个参数可以控制哪个Context不加载,例如 deployIgnore="manager",即manager应用不加载 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); }这里需要解释一下,Tomcat部署应用有多种途径
- 最常见的就是直接把java应用class和依赖以 项目名/WEB-INF的目录结构放到webapps/下,tomcat会扫描webapps下的文件夹,将每个文件夹作为一个应用Context添加到当前Host中
- webapps目录下直接放War包,Tomcat解压然后同上
- server.xml中
标签下,配置 标签,配置应用路径等,路径可以是war也可以是文件夹 - conf/Catalina/host名/ 目录下添加应用Context的xml配置文件,配置内容和server.xml中Context配置一致,如图:
tomcat用以上几种方式,将应用添加到当前Host容器中,并寻找web.xml文件开始启动应用程序。
相对应的deployApps方法中 deployDescriptors 就是第四种方式,deployWARs第二种,deployDirectories则是最基本的第一种方式
deployDirectory
获取到webapps下的文件夹列表后,每个文件夹都是一个应用,异步部署,下边是单个应用部署的代码 。
每个应用都对应一个StandardContext容器,所谓部署就是实例化StandardContext对象,将context添加到当前host容器,启动context,并注册context的配置文件到Map中监听,如果修改,会在周期任务执行时重新部署
protected void deployDirectory(ContextName cn, File dir) { long startTime = 0; // Deploy the application in this directory if( log.isInfoEnabled() ) { startTime = System.currentTimeMillis(); log.info(sm.getString("hostConfig.deployDir", dir.getAbsolutePath())); } Context context = null; File xml = new File(dir, Constants.ApplicationContextXml); File xmlCopy = new File(host.getConfigbaseFile(), cn.getbaseName() + ".xml"); DeployedApplication deployedApp; boolean copyThisXml = isCopyXML(); boolean deployThisXML = isDeployThisXML(dir, cn); try { if (deployThisXML && xml.exists()) { synchronized (digesterLock) { try { context = (Context) digester.parse(xml); } catch (Exception e) { log.error(sm.getString( "hostConfig.deployDescriptor.error", xml), e); context = new FailedContext(); } finally { digester.reset(); if (context == null) { context = new FailedContext(); } } } if (copyThisXml == false && context instanceof StandardContext) { // Host is using default value. Context may override it. copyThisXml = ((StandardContext) context).getCopyXML(); } if (copyThisXml) { Files.copy(xml.toPath(), xmlCopy.toPath()); context.setConfigFile(xmlCopy.toURI().toURL()); } else { context.setConfigFile(xml.toURI().toURL()); } } else if (!deployThisXML && xml.exists()) { // Block deployment as meta-INF/context.xml may contain security // configuration necessary for a secure deployment. log.error(sm.getString("hostConfig.deployDescriptor.blocked", cn.getPath(), xml, xmlCopy)); context = new FailedContext(); } else { context = (Context) Class.forName(contextClass).getConstructor().newInstance(); } Class> clazz = Class.forName(host.getConfigClass()); LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance(); context.addLifecycleListener(listener); context.setName(cn.getName()); context.setPath(cn.getPath()); context.setWebappVersion(cn.getVersion()); context.setDocbase(cn.getbaseName()); host.addChild(context); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("hostConfig.deployDir.error", dir.getAbsolutePath()), t); } finally { deployedApp = new DeployedApplication(cn.getName(), xml.exists() && deployThisXML && copyThisXml); // Fake re-deploy resource to detect if a WAR is added at a later // point deployedApp.redeployResources.put(dir.getAbsolutePath() + ".war", Long.valueOf(0)); deployedApp.redeployResources.put(dir.getAbsolutePath(), Long.valueOf(dir.lastModified())); if (deployThisXML && xml.exists()) { if (copyThisXml) { deployedApp.redeployResources.put( xmlCopy.getAbsolutePath(), Long.valueOf(xmlCopy.lastModified())); } else { deployedApp.redeployResources.put( xml.getAbsolutePath(), Long.valueOf(xml.lastModified())); // Fake re-deploy resource to detect if a context.xml file is // added at a later point deployedApp.redeployResources.put( xmlCopy.getAbsolutePath(), Long.valueOf(0)); } } else { // Fake re-deploy resource to detect if a context.xml file is // added at a later point deployedApp.redeployResources.put( xmlCopy.getAbsolutePath(), Long.valueOf(0)); if (!xml.exists()) { deployedApp.redeployResources.put( xml.getAbsolutePath(), Long.valueOf(0)); } } addWatchedResources(deployedApp, dir.getAbsolutePath(), context); // Add the global redeploy resources (which are never deleted) at // the end so they don't interfere with the deletion process addGlobalRedeployResources(deployedApp); } deployed.put(cn.getName(), deployedApp); if( log.isInfoEnabled() ) { log.info(sm.getString("hostConfig.deployDir.finished", dir.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime))); } }stop只是从MBean注册表中移除当前Host
checkEngine启动了一个线程定时执行子容器的backgroundProcess()后台处理方法,执行时会发布periodic周期事件,check即事件触发后执行的方法,HostConfig对已经部署的应用配置文件(conf/localhost/xxx.xml,meta-INF/content.xml)进行检测,如果文件修改被修改,重新部署该应用程序
监听的配置文件列表,修改context.xml重新部署,修改了web.xml重新加载应用Servlet、Filter等:
StandardContextTomcat源码笔记(八)Context
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)