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中:

             

猜你喜欢

转载自blog.csdn.net/qq_39028580/article/details/80986204