Tomcat源碼解析系列(三)Service

前言
上篇文章中分析了 Server 類的 init 和 start 方法,其中最核心的內容就是調用了 StandardServer 類的的 Service 類型的成員的 init 和 start 方法。Service 是 Tomcat 的核心組件之一。Service 的實現類是 StandardService。StandardService 和 StandardServer 同樣也是繼承自 LifecycleMBeanBase。apache


1 StandardService#initInternal 方法
首先看 initInternal 方法segmentfault

/**
 * Invoke a pre-startup initialization. This is used to allow connectors
 * to bind to restricted ports under Unix operating environments.
 */
@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    if (engine != null) {
        engine.init();
    }

    // Initialize any Executors
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }

    // Initialize mapper listener
    mapperListener.init();

    // Initialize our defined Connectors
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            connector.init();
        }
    }
}


/**
 * Retrieves all executors
 * @return Executor[]
 */
@Override
public Executor[] findExecutors() {
    synchronized (executors) {
        Executor[] arr = new Executor[executors.size()];
        executors.toArray(arr);
        return arr;
    }
}

initInternal 代碼很短,思路也很清晰,就是依次調用了這個成員變量的 init 方法app

engine.init() 
executor.init 
mapperListener.init()
connector.init()

先看看這幾個成員變量的定義dom

private Engine engine = null;

/**
 * The list of executors held by the service.
 */
protected final ArrayList<Executor> executors = new ArrayList<>();


/**
 * Mapper listener.
 */
protected final MapperListener mapperListener = new MapperListener(this);


/**
 * The set of Connectors associated with this Service.
 */
protected Connector connectors[] = new Connector[0];

上面四個成員變量,除了 mapperListener,都是在 Catalina 解析 server.xml 文件是根據配置文件的配置初始化的。ide

其中 engine 和 connector 是也是 Tomcat 核心組件之二,會分別單獨解析,這裏就先略過。ui

executor 的實現類是,StandardThreadExecutor,也是繼承自 LifecycleMBeanBase,它的做用跟線程池相似。this

mapperListener 的做用是在 start 的時候將容器類對象註冊到 Mapper 對象中。線程

/**
 * Create mapper listener.
 *
 * @param service The service this listener is associated with
 */
public MapperListener(Service service) {
    this.service = service;
    this.mapper = service.getMapper();
}

service.getMapper() 返回的是 StandardService 對象的 mapper 成員變量。debug

/**
 * Mapper.
 */
protected final Mapper mapper = new Mapper();

Mapper是 Tomcat 處理 Http 請求時很是重要的組件。Tomcat 使用 Mapper 來處理一個 Request 到 Host、Context 的映射關係,從而決定使用哪一個 Service 來處理請求。rest

MapperListener 也是繼承自 LifecycleMBeanBase,不過沒有重載 initInternal 方法。

2 StandardService#startInternal 方法

/**
 * Start nested components ({@link Executor}s, {@link Connector}s and
 * {@link Container}s) 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 void startInternal() throws LifecycleException {

    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name", this.name));
    setState(LifecycleState.STARTING);

    // Start our defined Container first
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }

    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    mapperListener.start();

    // Start our defined Connectors second
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            // If it has already failed, don't try and start it
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

startInternal 跟 initInternal 方法同樣,也是依次調用

engine.start();
executor.start();
mapperListener.start();
connector.start();

一樣地,engine 和 connector 的 start 方法會分別單獨講解,這裏先略過。

executor 的 start 方法初始化了內部的線程池。

2.1 MapperListener#startInternal 方法
MapperListener 的 startInternal 方法

@Override
public void startInternal() throws LifecycleException {

    setState(LifecycleState.STARTING);

    Engine engine = service.getContainer();
    if (engine == null) {
        return;
    }

    findDefaultHost();

    addListeners(engine);

    Container[] conHosts = engine.findChildren();
    for (Container conHost : conHosts) {
        Host host = (Host) conHost;
        if (!LifecycleState.NEW.equals(host.getState())) {
            // Registering the host will register the context and wrappers
            registerHost(host);
        }
    }
}

2.1.1 findDefaultHost() 方法
首先看 findDefaultHost() 方法

private void findDefaultHost() {

    Engine engine = service.getContainer();
    String defaultHost = engine.getDefaultHost();

    boolean found = false;

    if (defaultHost != null && defaultHost.length() > 0) {
        Container[] containers = engine.findChildren();

        for (Container container : containers) {
            Host host = (Host) container;
            if (defaultHost.equalsIgnoreCase(host.getName())) {
                found = true;
                break;
            }

            String[] aliases = host.findAliases();
            for (String alias : aliases) {
                if (defaultHost.equalsIgnoreCase(alias)) {
                    found = true;
                    break;
                }
            }
        }
    }

    if (found) {
        mapper.setDefaultHostName(defaultHost);
    } else {
        log.error(sm.getString("mapperListener.unknownDefaultHost", defaultHost, service));
    }
}

findDefaultHost() 是主要是找出 defaultHost ,並調用
mapper.setDefaultHostName(defaultHost);
這個 defaultHost 是 server.xml 的 <Engine> 標籤的屬性,通常都是 "localHost"。
從上面代碼 for 代碼塊裏能夠看出,Host 是 Engine 的子 Container。for 語句就是找出一個名字跟 defaultHost 指定的名字相同的 Host 對象。

2.1.2 addListeners(engine) 方法

/**
 * Add this mapper to the container and all child containers
 *
 * @param container
 */
private void addListeners(Container container) {
    container.addContainerListener(this);
    container.addLifecycleListener(this);
    for (Container child : container.findChildren()) {
        addListeners(child);
    }
}

這個方法的做用是,將 MapperListener 這個監聽器添加到 Engine 及其子容器中

2.1.3 for循環代碼塊
for循環代碼塊的做用就是,調用 registerHost方法來註冊 Engine 的字容器 Host。

/**
 * Register host.
 */
private void registerHost(Host host) {

    String[] aliases = host.findAliases();
    mapper.addHost(host.getName(), aliases, host);

    for (Container container : host.findChildren()) {
        if (container.getState().isAvailable()) {
            registerContext((Context) container);
        }
    }

    // Default host may have changed
    findDefaultHost();

    if(log.isDebugEnabled()) {
        log.debug(sm.getString("mapperListener.registerHost",
                host.getName(), domain, service));
    }
}

registerHost 方法先調用 mapper.addHost,而後調用 registerContext 方法註冊 Host 的子容器 Context。
mapper.addHost 方法是將 Host 加入的 Mapper 類的的成員變量
MappedHost[] hosts 中。
接着看 registerContext 方法

/**
 * Register context.
 */
private void registerContext(Context context) {

    String contextPath = context.getPath();
    if ("/".equals(contextPath)) {
        contextPath = "";
    }
    Host host = (Host)context.getParent();

    WebResourceRoot resources = context.getResources();
    String[] welcomeFiles = context.findWelcomeFiles();
    List<WrapperMappingInfo> wrappers = new ArrayList<>();

    for (Container container : context.findChildren()) {
        prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerWrapper",
                    container.getName(), contextPath, service));
        }
    }

    mapper.addContextVersion(host.getName(), host, contextPath,
            context.getWebappVersion(), context, welcomeFiles, resources,
            wrappers);

    if(log.isDebugEnabled()) {
        log.debug(sm.getString("mapperListener.registerContext",
                contextPath, service));
    }
}

registerContext 裏先獲取一些對象,好比 WebResourceRoot 對象、WrapperMappingInfo 對象,而後調用 mapper.addContextVersion。

Mapper#addContextVersion 方法比較瑣細,就不細講了。
其主要邏輯是將 Context 對象,以及 Context 的子容器 Wrapper 對象,每個都分別構建一個對應的 MappedContext 和 MappedWrapper 對象,
而後把 MappedContext 和 MappedWrapper 塞進 ContextVersion 對象中,
最後把 Context 和 ContextVersion 的對應關係放在 Mapper 對象的一個 Map 裏。

這裏的 MappedContext 和 MappedWrapper 在 Tomcat 處理 Http 請求的時候是比較關鍵的。

registerHost 最後再更新了一下可能發生改變裏的的 defaultHost

3 engine、host、context、wrapper 和 connector

上面的分析中提到了 Engine、Host、Context、Wrapper 和 Connector。除了 Connector,其他的都是 Container 接口的實現類。
它們的父類是 ContainerBase,ContainerBase 繼承自 LifecycleMBeanBase,所以 Container 也有生命週期方法。
每個 Container 均可能有子 Container,其中,Engine 的子 Container 是 Host,Host 的子 Container 是 Context,Context 的子 Container 是 Wrapper,這裏說的父子關係,不是指類繼承關係,而是說一個 Container 內部有一個 Map,這個 Map 保存了其餘類型的 Container。

Container 是可以執行客戶端請求而且給出響應的對象,Tomcat 是用 這些 Container 對象逐層地處理請求的。關於 Container 是如何處理請求的,這裏就不細講了,留待後面的文章分析。

Connector 也繼承了 LifecycleMBeanBase。Connector 是 Tomcat 的核心組件。他的主要做用是處理 Http 鏈接,並轉發請求給 Container 處理的。


小結本篇文章介紹了 StandardService 的 init 和 start 方法,能夠看出,StandardService 的 init、start 方法中調用 Engine、Connector、MapperListener 的 init、start 方法,經過這些對象的對應方法來完成 Service 的啓動。在本篇文章中,簡單介紹了一下 Engine、Host、Context、Wrapper 和 Connector,這些都是 Tomcat 的核心組件。後面的文章將分別進入這些組件。

相關文章
相關標籤/搜索