Tomcat源码笔记(七)Engine

Tomcat源码笔记(七)Engine,第1张

Tomcat源码笔记(七)Engine

目录

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/Lifecyclebase

tomcat几乎所有组件都继承了这个类,控制生命周期,其中定义了init()、start()、stop()等入口方法,组件都使用这些方法进行启动和终止。主要就是设置当前容器状态并对当前组件监听器发布相关事件,例如init_beforeinit_afterstart_beforestart_after等等,再调用子类实现的initInternal()、startInternal()等方法处理核心逻辑

Containerbase

StandardEngine,StandardHost,StandardContext,StandardWrapper四个容器实现类都继承自父类Containerbase,先来看父类中进行了什么 *** 作

initInternal 

初始化核心方法,初始化前后会发布 before_init 和 after_init 事件,容器监听器处理事件

创建了一个用于启动/停止子容器的线程池,用来异步启动/停止子容器

public abstract class Containerbase extends LifecycleMBeanbase
        implements Container {

    @Override
    protected void initInternal() throws LifecycleException {
        // 启动和停止子容器使用的线程池,异步 *** 作子容器
        BlockingQueue startStopQueue = new linkedBlockingQueue<>();
        startStopExecutor = new ThreadPoolExecutor(
                getStartStopThreadsInternal(),
                getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
                startStopQueue,
                new StartStopThreadFactory(getName() + "-startStop-"));
        startStopExecutor.allowCoreThreadTimeOut(true);

        // 注册MBean
        super.initInternal();
    }
}
startInternal

启动容器核心方法,启动前后会发布 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();
        List> 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();
    }
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; i 
start 

多途径部署应用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部署应用有多种途径

  1. 最常见的就是直接把java应用class和依赖以 项目名/WEB-INF的目录结构放到webapps/下,tomcat会扫描webapps下的文件夹,将每个文件夹作为一个应用Context添加到当前Host中
  2. webapps目录下直接放War包,Tomcat解压然后同上
  3. server.xml中标签下,配置标签,配置应用路径等,路径可以是war也可以是文件夹
  4. 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

check

Engine启动了一个线程定时执行子容器的backgroundProcess()后台处理方法,执行时会发布periodic周期事件,check即事件触发后执行的方法,HostConfig对已经部署的应用配置文件(conf/localhost/xxx.xml,meta-INF/content.xml)进行检测,如果文件修改被修改,重新部署该应用程序

监听的配置文件列表,修改context.xml重新部署,修改了web.xml重新加载应用Servlet、Filter等:

StandardContext

Tomcat源码笔记(八)Context

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5684151.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存