1. 程式人生 > >Web應用伺服器 相關知識梳理(二)Tomcat的系統架構

Web應用伺服器 相關知識梳理(二)Tomcat的系統架構

    Tomcat非常模組化,其總體架構本人歸納為以下:

      

      話說 有這樣的一家人 被稱為Tomcat:

      (一)Server

          父親被稱為Server,其權利可謂至高無上,控制家中每對孩子;作為每對夫妻的生存環境,掌控整個Tomcat生命週期;其標準實現類StandardServer中的一個重要方法addService:

/**
 * Add a new Service to the set of defined Services.
 *
 * @param service The Service to be added
 */
@Override
public void addService(Service service) {

    service.setServer(this);

    synchronized (servicesLock) {
        
        //Server使用一個數組來管理Service,每新增一個Service就把原來的Service拷貝到一個新的陣列中,再把新的Service放入Service陣列中。
        Service results[] = new Service[services.length + 1];
        System.arraycopy(services, 0, results, 0, services.length);
        results[services.length] = service;
        services = results;

        if (getState().isAvailable()) {
            try {
                service.start();
            } catch (LifecycleException e) {
                // Ignore
            }
        }

        // Report this property change to interested listeners
        support.firePropertyChange("service", null, service);
    }
}

      (二)Service

          Service就是連線每對小夫妻婚姻的結婚證,每個結婚證都被父親Server保管,其標準實現類StandardService中的兩個重要方法addConnector及setContainer:

/**
 * Add a new Connector to the set of defined Connectors, and associate it
 * with this Service's Container.
 *
 * @param connector The Connector to be added
 */
@Override
public void addConnector(Connector connector) {

    synchronized (connectorsLock) {
    	//類似動態陣列 不是List集合
        connector.setService(this);
        Connector results[] = new Connector[connectors.length + 1];
        System.arraycopy(connectors, 0, results, 0, connectors.length);
        results[connectors.length] = connector;
        connectors = results;

        if (getState().isAvailable()) {
            try {
                connector.start();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardService.connector.startFailed",connector), e);
            }
        }

        // Report this property change to interested listeners
        support.firePropertyChange("connector", null, connector);
    }
}
@Override
public void setContainer(Engine engine) {
    Engine oldEngine = this.engine;
    // 如果已經關聯 則去掉舊關聯關係
    if (oldEngine != null) {
        oldEngine.setService(null);
    }
    //如果未關聯 便進行關聯
    this.engine = engine;
    if (this.engine != null) {
        this.engine.setService(this);
    }
    if (getState().isAvailable()) {
        //若未關聯  則將engine啟動
        if (this.engine != null) {
            try {
                this.engine.start();
            } catch (LifecycleException e) {
                log.warn(sm.getString("standardService.engine.startFailed"), e);
            }
        }
        // Restart MapperListener to pick up new engine.
        try {
            mapperListener.stop();
        } catch (LifecycleException e) {
            log.warn(sm.getString("standardService.mapperListener.stopFailed"), e);
        }
        try {
            mapperListener.start();
        } catch (LifecycleException e) {
            log.warn(sm.getString("standardService.mapperListener.startFailed"), e);
        }
        if (oldEngine != null) {
            try {
                oldEngine.stop();
            } catch (LifecycleException e) {
                log.warn(sm.getString("standardService.engine.stopFailed"), e);
            }
        }
    }
    // Report this property change to interested listeners
    support.firePropertyChange("container", oldEngine, this.engine);
}

       (三)Connector

                Connector作為每對小夫妻中的男人,主要負責外部交流;對瀏覽器發過來的TCP連線請求通過Request及Response物件進行和Container交流。

                那Request及Response物件在一次請求中是如何變化的吶???

                       

              其中在Connector及Container之間Request、Response類的關係在程式碼上的展示如下:

                

               ---------------上下Request及Response兩類關係中均使用到門面設計模式,詳情另見-----------

             

               那麼在一次請求中如何根據這個URL到達正確的Container容器吶???

                這種對映工作在Tomcat 5 以前是通過org.apache.tomcat.util.http.Mapper類來完成,這個類儲存了Container中所有子容器的資訊,org.apache.catalina.connector.Request類在進入Container容器之前,Mapper類將會根據此次請求的hostname及contestpath將Host和Context設定到該Request的mappingData屬性中,所以Request在進入Container容器之前已經確定要訪問哪個子容器;在Tomcat 5以後該Mapper類的功能被移到Request類中。

               通過將MapperListener類作為一個監聽者加到整個Container容器中每個子容器上,來確保任何一個子容器變化,相應的儲存容器關係的MapperListener的mapper屬性同時被修改,如下MapperListener類中原始碼:

@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);
        }
    }
}

/**
 * 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);
    }
}


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.warn(sm.getString("mapperListener.unknownDefaultHost",defaultHost, service));
    }
}

/**
 * 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);
        }
    }
    if(log.isDebugEnabled()) {
        log.debug(sm.getString("mapperListener.registerHost",host.getName(), domain, service));
    }
}

        (四)Servlet容器Container

            Container作為每對小夫妻中的女人,主要負責內部事務處理;該容器父介面邏輯上由4個父子關係的子容器元件構成:Engine、Host、Context,而Context又包含Wrapper。Container容器作為邏輯核心元件,其實並不存在;其Engine引擎作為top容器,無父容器,如以下server.xml檔案中可以看出:

<Server port="" shutdown="SHUTDOWN">
    <Service name="Catalina">
        <Executor ...... />
        <Connector port="" ... />
        <Connector port="" ... />
        <Engine name="Catalina" ... >
            <Host name="localhost" appBase="webapps" ... >
		<Valve className="" .../>
                <Context docBase="" path="" ... />
            </Host>
        </Engine>
    </Service>
</Server>

       appBase :所指向的目錄應該是準備用於存放這一組Web應用程式的目錄,而不是具體某個 Web 應用程式的目錄本身(即使該虛擬主機只由一個 Web 應用程式組成);預設是webapps。

       path:是虛擬目錄,訪問的時候用127.0.0.1:8080/welcome/*.html訪問網頁,welcome前面要加/;
       docBase:是網頁實際存放位置的根目錄,對映為path虛擬目錄。

        (五)Engine引擎

               每個Service只能包含一個Engine引擎,其作用主要是決定從Connector聯結器過來的請求應該交由哪一個Host處理,一個Engine代表一套完整的Servlet引擎;其標準實現類StandardEngine的addChild方法:

/**
 * Add a child Container, only if the proposed child is an implementation
 * of Host.
 *
 * @param child Child container to be added
 */
@Override
public void addChild(Container child) {
    //其新增的子容器型別也只能是Host型別
    if (!(child instanceof Host))
        throw new IllegalArgumentException(sm.getString("standardEngine.notHost"));
    super.addChild(child);
}

         (六)Host

               一個Host在Engine中代表一臺虛擬主機,虛擬主機的作用就是執行多個應用,並負責安裝和展開這些應用及對這些應用的區分。同時Host並不是必需的,但是要執行war程式,就必須要使用Host;因為在war中必有web.xml,該檔案的解析必需使用Host。其標準實現類StandardHost的addChild方法同理Engine:

/**
 * Add a child Container, only if the proposed child is an implementation
 * of Context.
 *
 * @param child Child container to be added
 */
@Override
public void addChild(Container child) {

    child.addLifecycleListener(new MemoryLeakTrackingListener());

    if (!(child instanceof Context))
        throw new IllegalArgumentException(sm.getString("standardHost.notContext"));
    super.addChild(child);
}

        (七)Context

             Context容器具備Servlet基本的執行環境,所以真正管理Servlet的容器是Context;一個Context對應一個Web工程,簡單的Tomcat可以沒有Engine及Host,只要有Context容器就能執行Servlet。

新增一個Web應用時:

/**
 * @param host The host in which the context will be deployed
 * @param contextPath The context mapping to use, "" for root context. 訪問路徑
 * @param docBase Base directory for the context, for static files. 存放的物理路徑
 *  Must exist, relative to the server home
 * @param config Custom context configurator helper
 * @return the deployed context
 * @see #addWebapp(String, String)
 */
public Context addWebapp(Host host, String contextPath, String docBase,LifecycleListener config) {

    silence(host, contextPath);
    // 新增一個Web應用時便會建立一個StandardContext實現類物件
    Context ctx = createContext(host, contextPath);
    ctx.setPath(contextPath);
    ctx.setDocBase(docBase);
    ctx.addLifecycleListener(getDefaultWebXmlListener());
    ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));

    ctx.addLifecycleListener(config);

    if (config instanceof ContextConfig) {
        // prevent it from looking ( if it finds one - it'll have dup error )
        ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
    }

    if (host == null) {
        getHost().addChild(ctx);
    } else {
        host.addChild(ctx);
    }
    return ctx;
}

private Context createContext(Host host, String url) {
    String contextClass = StandardContext.class.getName();
    if (host == null) {
        host = this.getHost();
    }
    if (host instanceof StandardHost) {
        contextClass = ((StandardHost) host).getContextClass();
    }
    try {
        return (Context) Class.forName(contextClass).getConstructor().newInstance();
    } catch (InstantiationException | IllegalAccessException
            | IllegalArgumentException | InvocationTargetException
            | NoSuchMethodException | SecurityException
            | ClassNotFoundException e) {
        throw new IllegalArgumentException(
                "Can't instantiate context-class " + contextClass
                        + " for host " + host + " and url "+ url, e);
    }
}

        之後進行Web應用的初始化,主要就是解析web.xml檔案;將解析後的相應屬性儲存在WebXml物件,並將該物件內容設定到Context容器中。

        在server.xml檔案中Context容器配置時當reloadable設為true時,war被修改後Tomcat會自動載入這個應用:                 

      

        原始碼詳解:

    

 reload方法按照先呼叫stop方法再呼叫start方法,完成一次Context的一次重新載入。

 backgroundProcess方法是在StandardContext的父類ContainerBase類中的內部類ContainerBackgroundProcessor中被週期呼叫,這個內部類類執行在一個後臺執行緒中:

/**
 * Private thread class to invoke the backgroundProcess method
 * of this container and its children after a fixed delay.
 */
protected class ContainerBackgroundProcessor implements Runnable {

    @Override
    public void run() {
        Throwable t = null;
        String unexpectedDeathMessage = sm.getString("containerBase.backgroundProcess.unexpectedThreadDeath",
                Thread.currentThread().getName());
        try {
            while (!threadDone) {
                try {
                    Thread.sleep(backgroundProcessorDelay * 1000L);
                } catch (InterruptedException e) {
                    // Ignore
                }
                if (!threadDone) {
                    processChildren(ContainerBase.this);
                }
            }
        } catch (RuntimeException|Error e) {
            t = e;
            throw e;
        } finally {
            if (!threadDone) {
                log.error(unexpectedDeathMessage, t);
            }
        }
    }

    protected void processChildren(Container container) {
        ClassLoader originalClassLoader = null;

        try {
            if (container instanceof Context) {
                Loader loader = ((Context) container).getLoader();
                // Loader will be null for FailedContext instances
                if (loader == null) {
                    return;
                }

                // Ensure background processing for Contexts and Wrappers
                // is performed under the web app's class loader
                originalClassLoader = ((Context) container).bind(false, null);
            }
            container.backgroundProcess();
            Container[] children = container.findChildren();
            for (int i = 0; i < children.length; i++) {
                if (children[i].getBackgroundProcessorDelay() <= 0) {
                    processChildren(children[i]);
                }
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error("Exception invoking periodic operation: ", t);
        } finally {
            if (container instanceof Context) {
                ((Context) container).unbind(false, originalClassLoader);
           }
        }
    }
}

        (八)Wrapper

            Wrapper負責管理一個Servlet,包括Servlet的裝載、初始化、執行及銷燬;最底層的容器,無子容器,及無addChild方法;其實現類StandardWrapper中的一個重要方法loadServlet:

/**
 * Load and initialize an instance of this servlet, if there is not already  載入並初始化Servlet例項
 * at least one initialized instance.  This can be used, for example, to
 * load servlets that are marked in the deployment descriptor to be loaded
 * at server startup time.
 * @return the loaded Servlet instance
 * @throws ServletException for a Servlet load error
 */
public synchronized Servlet loadServlet() throws ServletException {

    // Nothing to do if we already have an instance or an instance pool
    if (!singleThreadModel && (instance != null))
        return instance;

    PrintStream out = System.out;
    if (swallowOutput) {
        SystemLogHandler.startCapture();
    }

    Servlet servlet;
    try {
        long t1=System.currentTimeMillis();
        // Complain if no servlet class has been specified
        if (servletClass == null) {
            unavailable(null);
            throw new ServletException(sm.getString("standardWrapper.notClass", getName()));
        }
        // 獲取InstanceManager例項
        InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
        try {
        	// 載入:獲取servletClass,然後交給InstanceManager去建立一個基於servletClass.class的Servlet例項
            servlet = (Servlet) instanceManager.newInstance(servletClass);
        } catch (ClassCastException e) {
            unavailable(null);
            // Restore the context ClassLoader
            throw new ServletException(sm.getString("standardWrapper.notServlet", servletClass),e);
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            unavailable(null);

            // Added extra log statement for Bugzilla 36630:
            // http://bz.apache.org/bugzilla/show_bug.cgi?id=36630
            if(log.isDebugEnabled()) {
                log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
            }

            // Restore the context ClassLoader
            throw new ServletException(sm.getString("standardWrapper.instantiate", servletClass),e);
        }

        if (multipartConfigElement == null) {
            MultipartConfig annotation = servlet.getClass().getAnnotation(MultipartConfig.class);
            if (annotation != null) {
                multipartConfigElement = new MultipartConfigElement(annotation);
            }
        }

        // Special handling for ContainerServlet instances
        // Note: The InstanceManager checks if the application is permitted
        // to load ContainerServlets
        if (servlet instanceof ContainerServlet) {
            ((ContainerServlet) servlet).setWrapper(this);
        }

        classLoadTime=(int) (System.currentTimeMillis() -t1);

        if (servlet instanceof SingleThreadModel) {
            if (instancePool == null) {
                instancePool = new Stack<>();
            }
            singleThreadModel = true;
        }
        // 初始化Servlet例項
        initServlet(servlet);

        fireContainerEvent("load", this);

        loadTime=System.currentTimeMillis() -t1;
    } finally {
        if (swallowOutput) {
            String log = SystemLogHandler.stopCapture();
            if (log != null && log.length() > 0) {
                if (getServletContext() != null) {
                    getServletContext().log(log);
                } else {
                    out.println(log);
                }
            }
        }
    }
    return servlet;
}

private synchronized void initServlet(Servlet servlet)throws ServletException {

    if (instanceInitialized && !singleThreadModel) return;

    // Call the initialization method of this servlet
    try {
        if( Globals.IS_SECURITY_ENABLED) {
            boolean success = false;
            try {
                Object[] args = new Object[] { facade };
                SecurityUtil.doAsPrivilege("init",servlet,classType,args);
                success = true;
            } finally {
                if (!success) {
                    // destroy() will not be called, thus clear the reference now
                    SecurityUtil.remove(servlet);
                }
            }
        } else {
            //核心:呼叫Servlet的init方法,並將StandardWrapper物件的門面類物件StandardWrapperFacade作為ServletConfig引數傳入
            servlet.init(facade);
        }

        instanceInitialized = true;
    } catch (UnavailableException f) {
        unavailable(f);
        throw f;
    } catch (ServletException f) {
        // If the servlet wanted to be unavailable it would have
        // said so, so do not call unavailable(null).
        throw f;
    } catch (Throwable f) {
        ExceptionUtils.handleThrowable(f);
        getServletContext().log("StandardWrapper.Throwable", f );
        // If the servlet wanted to be unavailable it would have
        // said so, so do not call unavailable(null).
        throw new ServletException(sm.getString("standardWrapper.initException",getName()),f);
    }
}

        在該Servlet載入之前,先確定是否屬於JSP,如果是,則會載入並初始化一個JspServlet例項:

      

          與Servlet主動關聯的類有三個:ServletConfig、ServletRequest、ServletResponse;ServletConfig在Servlet初始化的時候作為引數進入Servlet;其他兩個都是當請求到達時呼叫Servlet傳遞過來的,那ServletContext吶,與ServletConfig有何異同???

            Servlet的執行是典型的“握手型的互動式”執行模式,可以理解為:模組之間的資料交換要準備一個交易場景,這個場景一直跟隨這個交易過程直到交易結束;而交易場景的初始化需要一些指定引數,都存放在配置類ServletConfig中;交易場景由ServletContext來表示,同時可以通過ServletConfig來獲取;ServletRequest、ServletResponse通常作為運輸工具來傳遞要互動的資料。 

(九)元件的生命線——“Lifecycle”

           Tomcat中元件的生命週期都是通過Lifecycle介面控制,元件只要繼承這個介面並實現其中的方法則可以統一控制其子元件,所以最高級別的元件Server就可以一層一層地控制所有元件的生命週期,如StandardServer中:

              

       或者 StandardService中: