美文网首页Tomcat
Tomcat启动分析(七) - 容器及相关组件

Tomcat启动分析(七) - 容器及相关组件

作者: buzzerrookie | 来源:发表于2018-09-13 19:27 被阅读1次

    本文首先分析Container接口及ContainerBase抽象类,然后分析Engine、Host、Context和Pipeline等组件的初始化和启动过程。

    Container

    Container是一个重要的接口,表示Servlet容器相关组件的抽象,类层次结构如下图所示:


    Container类层次结构.png
    public interface Container extends Lifecycle {
        // ----------------------------------------------------- Manifest Constants
        public static final String ADD_CHILD_EVENT = "addChild";
        public static final String ADD_VALVE_EVENT = "addValve";
        public static final String REMOVE_CHILD_EVENT = "removeChild";
        public static final String REMOVE_VALVE_EVENT = "removeValve";
        // ------------------------------------------------------------- Properties
        public Log getLogger();
        public String getLogName();
        public ObjectName getObjectName();
        public String getDomain();
        public String getMBeanKeyProperties();
        public Pipeline getPipeline();
        public Cluster getCluster();
        public void setCluster(Cluster cluster);
        public int getBackgroundProcessorDelay();
        public void setBackgroundProcessorDelay(int delay);
        public String getName();
        public void setName(String name);
        public Container getParent();
        public void setParent(Container container);
        public ClassLoader getParentClassLoader();
        public void setParentClassLoader(ClassLoader parent);
        public Realm getRealm();
        public void setRealm(Realm realm);
        // --------------------------------------------------------- Public Methods
        public void backgroundProcess();
        public void addChild(Container child);
        public void addContainerListener(ContainerListener listener);
        public void addPropertyChangeListener(PropertyChangeListener listener);
        public Container findChild(String name);
        public Container[] findChildren();
        public ContainerListener[] findContainerListeners();
        public void removeChild(Container child);
        public void removeContainerListener(ContainerListener listener);
        public void removePropertyChangeListener(PropertyChangeListener listener);
        public void fireContainerEvent(String type, Object data);
        public void logAccess(Request request, Response response, long time,
                boolean useDefault);
        public AccessLog getAccessLog();
        public int getStartStopThreads();
        public void setStartStopThreads(int startStopThreads);
        public File getCatalinaBase();
        public File getCatalinaHome();
    }
    
    • 一个容器可以包含其他容器,Container接口提供了addChild、findChild、findChildren、removeChild和setParent等接口方法;
    • 可以向容器添加事件监听器,Container接口提供了addContainerListener、findContainerListeners和removeContainerListener等接口方法,还提供了fireContainerEvent方法用于触发事件;

    ContainerBase抽象类是Container接口的默认实现,也是其他标准组件如StandardEngine、StandardWrapper、StandardHost和StandardContext的父类,其部分代码如下:

    public abstract class ContainerBase extends LifecycleMBeanBase
            implements Container {
        // 省略一些代码
        protected final HashMap<String, Container> children = new HashMap<>();
        protected final List<ContainerListener> listeners = new CopyOnWriteArrayList<>();
        protected String name = null;
        protected Container parent = null;
        protected ClassLoader parentClassLoader = null;
        protected final Pipeline pipeline = new StandardPipeline(this);
        /**
         * The number of threads available to process start and stop events for any
         * children associated with this container.
         */
        private int startStopThreads = 1;
        protected ThreadPoolExecutor startStopExecutor;
    
        @Override
        protected void initInternal() throws LifecycleException {
            BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
            startStopExecutor = new ThreadPoolExecutor(
                    getStartStopThreadsInternal(),
                    getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
                    startStopQueue,
                    new StartStopThreadFactory(getName() + "-startStop-"));
            startStopExecutor.allowCoreThreadTimeOut(true);
            super.initInternal();
        }
    
        @Override
        protected synchronized void startInternal() throws LifecycleException {
    
            // Start our subordinate components, if any
            logger = null;
            getLogger();
            Cluster cluster = getClusterInternal();
            if (cluster instanceof Lifecycle) {
                ((Lifecycle) cluster).start();
            }
            Realm realm = getRealmInternal();
            if (realm instanceof Lifecycle) {
                ((Lifecycle) realm).start();
            }
    
            // Start our child containers, if any
            Container children[] = findChildren();
            List<Future<Void>> results = new ArrayList<>();
            for (int i = 0; i < children.length; i++) {
                results.add(startStopExecutor.submit(new StartChild(children[i])));
            }
    
            boolean fail = false;
            for (Future<Void> result : results) {
                try {
                    result.get();
                } catch (Exception e) {
                    log.error(sm.getString("containerBase.threadedStartFailed"), e);
                    fail = true;
                }
    
            }
            if (fail) {
                throw new LifecycleException(
                        sm.getString("containerBase.threadedStartFailed"));
            }
    
            // Start the Valves in our pipeline (including the basic), if any
            if (pipeline instanceof Lifecycle)
                ((Lifecycle) pipeline).start();
    
            setState(LifecycleState.STARTING);
            // Start our thread
            threadStart();
        }
    
        // 省略一些代码
    }
    
    • pipeline变量引用一个StandardPipeline;
    • 初始化过程为自己创建了一个线程池,该线程池用于执行子容器的启动、停止等生命周期过程;
    • 启动过程利用初始化过程创建的线程池启动了各个子容器。

    Engine

    Engine元素表示整个请求处理的机构,它从一个或多个Connector中接收并处理请求,并将响应返回给Connector。Service元素内有且仅能有一个Engine元素。
    Engine接口继承了Container接口,StandardEngine类继承了ContainerBase基类并实现了Engine接口,其部分代码如下所示:

    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;
    }
    
    public void setService(Service service) {
        this.service = service;
    }
    
    @Override
    public void addChild(Container child) {
        if (!(child instanceof Host))
            throw new IllegalArgumentException
                (sm.getString("standardEngine.notHost"));
        super.addChild(child);
    }
    
    @Override
    public void setParent(Container container) {
        throw new IllegalArgumentException
            (sm.getString("standardEngine.notParent"));
    }
    
    @Override
    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();
    }
    
    @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();
    }
    
    • 成员变量的命名和注释基本解释了作用,也可参考Engine的配置文档
    • 构造函数为自己的Pipeline添加了基本阀StandardEngineValve;
    • initInternale和startInternal都很简单地调用了ContainerBase的对应方法,Engine的addChild只能添加Host,Engine也没有父容器,只有父Service(因为Engine在server.xml中是Service的子元素)。

    Host

    Host元素表示一个虚拟主机,在Host元素内可以有多个Context元素与之关联以表示不同的Web应用,Engine元素内可以配置多个Host,但其中一个的名称必须与Engine的defaultHost属性值相匹配。
    Host接口继承了Container接口,StandardHost类继承了ContainerBase基类并实现了Host接口,其部分代码如下所示。

    public class StandardHost extends ContainerBase implements Host {
        // ----------------------------------------------------------- Constructors
        /**
         * Create a new StandardHost component with the default basic Valve.
         */
        public StandardHost() {
    
            super();
            pipeline.setBasic(new StandardHostValve());
    
        }
        // ----------------------------------------------------- Instance Variables
        /**
         * The set of aliases for this Host.
         */
        private String[] aliases = new String[0];
    
        private final Object aliasesLock = new Object();
    
        /**
         * The application root for this Host.
         */
        private String appBase = "webapps";
        private volatile File appBaseFile = null;
    
        /**
         * The XML root for this Host.
         */
        private String xmlBase = null;
    
        /**
         * host's default config path
         */
        private volatile File hostConfigBase = null;
    
        /**
         * The auto deploy flag for this Host.
         */
        private boolean autoDeploy = true;
    
        /**
         * The Java class name of the default context configuration class
         * for deployed web applications.
         */
        private String configClass =
            "org.apache.catalina.startup.ContextConfig";
    
        /**
         * The Java class name of the default Context implementation class for
         * deployed web applications.
         */
        private String contextClass = "org.apache.catalina.core.StandardContext";
    
        /**
         * The deploy on startup flag for this Host.
         */
        private boolean deployOnStartup = true;
    
        /**
         * deploy Context XML config files property.
         */
        private boolean deployXML = !Globals.IS_SECURITY_ENABLED;
    
        /**
         * Should XML files be copied to
         * $CATALINA_BASE/conf/&lt;engine&gt;/&lt;host&gt; by default when
         * a web application is deployed?
         */
        private boolean copyXML = false;
    
        /**
         * The Java class name of the default error reporter implementation class
         * for deployed web applications.
         */
        private String errorReportValveClass =
            "org.apache.catalina.valves.ErrorReportValve";
    
        /**
         * Unpack WARs property.
         */
        private boolean unpackWARs = true;
    
        /**
         * Work Directory base for applications.
         */
        private String workDir = null;
    
        /**
         * Should we create directories upon startup for appBase and xmlBase
         */
        private boolean createDirs = true;
    
        /**
         * Track the class loaders for the child web applications so memory leaks
         * can be detected.
         */
        private final Map<ClassLoader, String> childClassLoaders =
                new WeakHashMap<>();
    
        /**
         * Any file or directory in {@link #appBase} that this pattern matches will
         * be ignored by the automatic deployment process (both
         * {@link #deployOnStartup} and {@link #autoDeploy}).
         */
        private Pattern deployIgnore = null;
        private boolean undeployOldVersions = false;
        private boolean failCtxIfServletStartFails = false;
    
        @Override
        public void addChild(Container child) {
    
            child.addLifecycleListener(new MemoryLeakTrackingListener());
    
            if (!(child instanceof Context))
                throw new IllegalArgumentException
                    (sm.getString("standardHost.notContext"));
            super.addChild(child);
    
        }
        // 省略一些代码
    }
    
    • 成员变量的命名和注释基本解释了作用,也可参考Host的配置文档
    • 构造函数为自己的Pipeline添加了基本阀StandardHostValve;
    • Host的addChild只能添加Context。

    startInternal方法如下所示,如果容器关联的pipeline中没有错误报告阀(errorReportValveClass),那么就给pipeline添加一个,否则什么也不做。

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

    Context

    Context元素表示一个Web应用,运行于一个特定的虚拟主机内。每个Web应用可以基于WAR文件,也可以基于相应的未打包目录。Catalina根据HTTP请求URI基于最长匹配选择处理该请求的Web应用,一旦选择,该Context就会根据定义好的servlet映射选择一个适当的servlet去处理。
    Context接口继承了Container接口,StandardContext类继承了ContainerBase基类并实现了Context接口,其部分代码如下所示。

    public class StandardContext extends ContainerBase
            implements Context, NotificationEmitter {
        /**
         * The display name of this web application.
         */
        private String displayName = null;
    
        /**
         * Override the default context xml location.
         */
        private String defaultContextXml;
    
        /**
         * Override the default web xml location.
         */
        private String defaultWebXml;
    
    
        /**
         * The distributable flag for this web application.
         */
        private boolean distributable = false;
    
        /**
         * The document root for this web application.
         */
        private String docBase = null;
    
        /**
         * The reloadable flag for this web application.
         */
        private boolean reloadable = false;
    
        /**
         * Encoded path.
         */
        private String encodedPath = null;
    
        /**
         * Unencoded path for this web application.
         */
        private String path = null;
    
        // 省略一些代码
        public StandardContext() {
            super();
            pipeline.setBasic(new StandardContextValve());
            broadcaster = new NotificationBroadcasterSupport();
            // Set defaults
            if (!Globals.STRICT_SERVLET_COMPLIANCE) {
                // Strict servlet compliance requires all extension mapped servlets
                // to be checked against welcome files
                resourceOnlyServlets.add("jsp");
            }
        }
        // 省略一些代码
    }
    
    • 成员变量的命名和注释基本解释了作用,也可参考Context的配置文档
    • 构造函数为自己的Pipeline添加了基本阀StandardContextValve;
    • Context的addChild只能添加Wrapper。

    Pipeline

    Pipeline接口的代码如下:

    public interface Pipeline {
        public Valve getBasic();
        public void setBasic(Valve valve);
        public void addValve(Valve valve);
        public Valve[] getValves();
        public void removeValve(Valve valve);
        public Valve getFirst();
        public boolean isAsyncSupported();
        public Container getContainer();
        public void setContainer(Container container);
        public void findNonAsyncValves(Set<String> result);
    }
    
    • getBasic、setBasic与基础阀(Basic Valve)的概念有关,Pipeline是由一系列阀组成的链表,基础阀始终指向最后一个阀;
    • 其余的方法与链表操作有关,如添加、移除阀等方法。

    StandardPipeline类继承LifecycleBase并实现了Pipeline接口,与阀有关的方法实现很简单,在此不再赘述。initInternal方法没有做任何事情,startInternal则依次启动了各个实现了Lifecycle接口的阀。

    @Override
    protected void initInternal() {
        // NOOP
    }
    
    @Override
    protected synchronized void startInternal() throws LifecycleException {
        // Start the Valves in our pipeline (including the basic), if any
        Valve current = first;
        if (current == null) {
            current = basic;
        }
        while (current != null) {
            if (current instanceof Lifecycle)
                ((Lifecycle) current).start();
            current = current.getNext();
        }
    
        setState(LifecycleState.STARTING);
    }
    
    • 在看StandardPipeline源码的过程中注意到basic永远指向最后一个阀,first指向第一个阀。当只有一个阀时,first为null但basic不会是null,这时getFirst会返回basic而不是null。

    Valve

    Valve阀是一个接口,代码如下。阀主要用在Pipeline中对请求的处理,是一种责任链设计模式。Tomcat内置了很多阀的实现,如上文提到的StandardEngineValve、StandardHostValve和StandardContextValve,具体可参考阀的配置文档

    public interface Valve {
        public Valve getNext();
        public void setNext(Valve valve);
        public void backgroundProcess();
        public void invoke(Request request, Response response)
            throws IOException, ServletException;
        public boolean isAsyncSupported();
    }
    
    • getNext和setNext分别获取和设置后继阀;
    • invoke方法处理请求,处理规约可以参考API文档

    相关文章

      网友评论

        本文标题:Tomcat启动分析(七) - 容器及相关组件

        本文链接:https://www.haomeiwen.com/subject/cepmgftx.html