Tomcat源码分析之Engine

Engine

全类名:org.apache.catalina.core.StandardEngine;

由类依赖图可知,Engine是一个生命周期的类,实现了Lifecycle,也是一个容器,实现了Container。

image-20220418073433598

Container有4个子接口,实现了该接口的也都是容器。我们继续从Engine开始来分析它们之间的关系。

Engine

实现类:org.apache.catalina.core.StandardEngine;

创建
/**
     * Create a new StandardEngine component with the default basic Valve.
     */
    public StandardEngine() {

        super();
        // 管道增加引擎阀
        pipeline.setBasic(new StandardEngineValve());
        /* Set the jmvRoute using the system property jvmRoute */
        try {
            setJvmRoute(System.getProperty("jvmRoute"));
        } catch(Exception ex) {
            log.warn(sm.getString("standardEngine.jvmRouteFail"));
        }
        // By default, the engine will hold the reloading thread
        backgroundProcessorDelay = 10;

    }

主要是给StandardEngine的管道pipeline添加了引擎阀StandardEngineValve。

init
protected void initInternal() throws LifecycleException {
        // Ensure that a Realm is present before any attempt is made to start
        // one. This will create the default NullRealm if necessary.
        getRealm();
        super.initInternal();
    }

没有核心逻辑,跳过。

start
/**
     * Start this component and implement the requirements
     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    @Override
    protected synchronized void startInternal() throws LifecycleException {

        // Log our server identification information
        if (log.isInfoEnabled()) {
            log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
        }

        // Standard container startup 容器启动
        super.startInternal();
    }

看注释可知,容器启动在父类ContainerBase方法startInternal();

/**
     * Start this component and implement the requirements
     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    @Override
    protected synchronized void startInternal() throws LifecycleException {

        // 省略

        // Start our child containers, if any
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            // 启动子类
            results.add(startStopExecutor.submit(new StartChild(child)));
        }

        MultiThrowable multiThrowable = null;

        for (Future<Void> 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());
        }

        // Start the Valves in our pipeline (including the basic), if any
        // 启动管道阀
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).start();
        }
      
				// 调用监听事件
        setState(LifecycleState.STARTING);

        // Start our thread
        if (backgroundProcessorDelay > 0) {
            monitorFuture = Container.getService(ContainerBase.this).getServer()
                    .getUtilityExecutor().scheduleWithFixedDelay(
                            new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
        }
    }

这是容器的一个模板方法,核心逻辑:

1)找到子类,并启动

Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
  	// 启动子类
  	results.add(startStopExecutor.submit(new StartChild(child)));
}

2)启动管道

if (pipeline instanceof Lifecycle) {
  	((Lifecycle) pipeline).start();
}

3)调用监听事件

setState(LifecycleState.STARTING);

// 后面会调LifecycleBase的该方法
protected void fireLifecycleEvent(String type, Object data) {
  	LifecycleEvent event = new LifecycleEvent(this, type, data);
  	for (LifecycleListener listener : lifecycleListeners) {
   	 		listener.lifecycleEvent(event);
  	}
}

Engine的启动调用图:
image-20220418083733102

Engine的启动过程中pileline和listener并没有核心逻辑,核心逻辑在启动child上。另外图中pipeline省略了其他的像日志打印的Valve。

主要会执行StandardHost.start();

Host

StandardHost也是一个容器,启动调用图:
image-20220419064459920

Host的启动核心调用逻辑在HostConfig。

/**
 * Process the START event for an associated Host.
 *
 * @param event The lifecycle event that has occurred
 */
@Override
public void lifecycleEvent(LifecycleEvent event) {

  //省略

  // Process the event that has occurred
  if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
    check();
  } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
    beforeStart();
  } else if (event.getType().equals(Lifecycle.START_EVENT)) {
    start();
  } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
    stop();
  }
}

/**
 * Process a "start" event for this Host.
 */
public void start() {

  // 省略

  if (host.getDeployOnStartup()) {
    deployApps();
  }
}

/**
 * Deploy applications for any directories or WAR files that are found
 * in our "application root" directory.
 */
protected void deployApps() {
  File appBase = host.getAppBaseFile();
  File configBase = host.getConfigBaseFile();
  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);
}

/**
 * Deploy exploded webapps.
 * @param appBase The base path for applications
 * @param files The exploded webapps that should be deployed
 */
protected void deployDirectories(File appBase, String[] files) {

  if (files == null) {
    return;
  }

  ExecutorService es = host.getStartStopExecutor();
  List<Future<?>> results = new ArrayList<>();

  for (String file : files) {
    if (file.equalsIgnoreCase("META-INF")) {
      continue;
    }
    if (file.equalsIgnoreCase("WEB-INF")) {
      continue;
    }

    File dir = new File(appBase, file);
    if (dir.isDirectory()) {
      ContextName cn = new ContextName(file, false);

      if (tryAddServiced(cn.getName())) {
        try {
          if (deploymentExists(cn.getName())) {
            removeServiced(cn.getName());
            continue;
          }

          // DeployDirectory will call removeServiced 把部署任务扔到一个线程池里执行
          results.add(es.submit(new DeployDirectory(this, cn, dir)));
        } catch (Throwable t) {
          ExceptionUtils.handleThrowable(t);
          removeServiced(cn.getName());
          throw t;
        }
      }
    }
  }

  for (Future<?> result : results) {
    try {
      result.get();
    } catch (Exception e) {
      log.error(sm.getString("hostConfig.deployDir.threaded.error"), e);
    }
  }
}

整理下上面方法的调用步骤:
image-20220419074416976

启动一个线程DeployDirectory,异步方式调用HostConfig的deployDirectory()方法

/**
 * Deploy exploded webapp.
 * <p>
 * Note: It is expected that the caller has successfully added the app
 *       to servicedSet before calling this method.
 *
 * @param cn The context name
 * @param dir The path to the root folder of the webapp
 */
protected void deployDirectory(ContextName cn, File dir) {

    // 非启动核心流程,省略
  	
    Context context = null;
    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());
    // 虚拟主机添加context,最终会调用父类方法ContainerBase.addChildInternal(Container child)
    host.addChild(context);
    
    // 非启动核心流程,省略
}

ContainerBase的addChildInternal方法:
private void addChildInternal(Container child) {

  // 非启动核心流程,省略

  // Start child
  // Don't do this inside sync block - start can be a slow process and
  // locking the children object can cause problems elsewhere
  try {
    if ((getState().isAvailable() ||
         LifecycleState.STARTING_PREP.equals(getState())) &&
        startChildren) {
      // 调用context启动方法
      child.start();
    }
  } catch (LifecycleException e) {
    throw new IllegalStateException(sm.getString("containerBase.child.start"), e);
  }
}

child.start() 调用的就是StandContext的start()方法

Context

Context也是一个容器,实现类:org.apache.catalina.core.StandardContext

StandardContext启动调用图:
image-20220419083238946

分别来分析下Context调用:

第一步:创建WebappLoader

会创建一个自定义的类加载器,回看之前分享的Bootsrap创建的3个类加载器,加上在这里创建的,tomcat的类加载器如图

image-20220419110957628

第二步:执行ContextConfig

会加载解析web.xml,创建wrapper,每一个wrapper就对应一个Servlet.

调用链如图:
image-20220419111840690

第三步:循环判断child是否已启动,未启动执行child的start,也即执行StandardWrapper.start,没有核心逻辑。

第四步:执行pipeline.start,没有核心逻辑。

第五步:创建Manager StandardManager,该类用于管理会话Session。

第六步:创建InstanceManager,默认实现类DefaultInstanceManager,该类用于实例化对象,包括Listener、Filter、Servlet.

第七步:调用方法listenerStart()、filterStart()、loadOnStartup(findChildren()),这几个方法分别为创建并设置监听、过滤器、以及加载和初始设置了load-on-startup的Servlet。

/**
     * Load and initialize all servlets marked "load on startup" in the
     * web application deployment descriptor.
     *
     * @param children Array of wrappers for all currently defined
     *  servlets (including those not declared load on startup)
     * @return <code>true</code> if load on startup was considered successful
     */
    public boolean loadOnStartup(Container children[]) {

        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (Container child : children) {
            Wrapper wrapper = (Wrapper) child;
            int loadOnStartup = wrapper.getLoadOnStartup();
            if (loadOnStartup < 0) {
                continue;
            }
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> list = map.get(key);
            if (list == null) {
                list = new ArrayList<>();
                map.put(key, list);
            }
            list.add(wrapper);
        }

        // Load the collected "load on startup" servlets
        for (ArrayList<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    wrapper.load();
                } catch (ServletException e) {
                    getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                          getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                    // NOTE: load errors (including a servlet that throws
                    // UnavailableException from the init() method) are NOT
                    // fatal to application startup
                    // unless failCtxIfServletStartFails="true" is specified
                    if(getComputedFailCtxIfServletStartFails()) {
                        return false;
                    }
                }
            }
        }
        return true;

    }

执行完这几步,Context启动完成。

根据以上可知容器之间的关系图:
image-20220419124209695

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值