Tomcat是如何处理web.xml的


前言

上一篇文章,我们分析了Catalina的load()方法,这一篇文章我们就来分析下start()方法

  public void start() {

        if (getServer() == null) {
            load();
        }

        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }

        long t1 = System.nanoTime();

        // Start the new server
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }

   //...省略了一大坨代码
    }

我们可以看到套路和之前的load()方法是一样的,这里主要还是调用了Server实例的start()方法,那我们把目光锁定在StandardServer的start()方法

StardardServer.startInternal()

也是一样的套路,init()的实现在祖先类中,主要的调用方法startInternal的实现也是使用了模板方法模式设计的,我们来看他在StardardServer的实现。

    @Override
    protected void startInternal() throws LifecycleException {

        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);

        globalNamingResources.start();

        // Start our defined Services
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
    }

如出一辙,还是调用了Service实例的start()方法

StandardService.startInternal()

start()方法依旧是使用了模板方法模式,我们直接看statInternal()方法

 @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 (container != null) {
            synchronized (container) {
                container.start();
            }
        }

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

        // Start our defined Connectors second
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                try {
                    // If it has already failed, don't try and start it
                    if (connector.getState() != LifecycleState.FAILED) {
                        connector.start();
                    }
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
        }
    }

这个方法调用了三个子元素的start()方法

我们先来看 container.start();

这几个子元素的start()到需要分别使用一篇文章来讲

container.start();

注意了,这一部分就涉及到解析web.xml的操作了,不过别着急,我们来一层一层一步一步的分析。

当然我们还是要结合server.xml和createStartDigester()来确定Contianer对应的实现类

先给出server.xml与Contianer有关的部分。


<Server port="8005" shutdown="SHUTDOWN">


  
  <Service name="Catalina">


    <Engine defaultHost="localhost" name="Catalina">

      
      

      
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
      Realm>

      <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">

        
        

        
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log." suffix=".txt"/>

      <Context docBase="C:\Coding\WorkSpace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\ROOT" path="" reloadable="false"/><Context docBase="C:\Coding\WorkSpace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\SpringMVC" path="/SpringMVC" reloadable="true" source="org.eclipse.jst.j2ee.server:SpringMVC"/>Host>
    Engine>
  Service>
Server>

只给出Contianer相关的父节点和子节点,叔叔节点以及兄弟节点都略去了。

我们再看下相关的解析规则是怎样定义的。

 digester.addRuleSet(new EngineRuleSet("Server/Service/"));
        digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
        digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
        addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
        digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));

        // When the 'engine' is found, set the parentClassLoader.
        digester.addRule("Server/Service/Engine",
                         new SetParentClassLoaderRule(parentClassLoader));
        addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");

EngineRuleSet的addRuleInstances

 @Override
    public void addRuleInstances(Digester digester) {

        digester.addObjectCreate(prefix + "Engine",
                                 "org.apache.catalina.core.StandardEngine",
                                 "className");
        digester.addSetProperties(prefix + "Engine");
        //注册监听器
        digester.addRule(prefix + "Engine",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.EngineConfig",
                          "engineConfigClass"));
        digester.addSetNext(prefix + "Engine",
                            "setContainer",
                            "org.apache.catalina.Container");

HostRuleSet的addRuleInstances

@Override
    public void addRuleInstances(Digester digester) {

        digester.addObjectCreate(prefix + "Host",
                                 "org.apache.catalina.core.StandardHost",
                                 "className");
        digester.addSetProperties(prefix + "Host");
        ......        //注册监听器
        digester.addRule(prefix + "Host", 
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.HostConfig",
                          "hostConfigClass"));
        digester.addSetNext(prefix + "Host",
                            "addChild",
                            "org.apache.catalina.Container");

        ......

    }

ContextRuleSet的addRuleInstances

 @Override
    public void addRuleInstances(Digester digester) {

        if (create) {
            digester.addObjectCreate(prefix + "Context",
                    "org.apache.catalina.core.StandardContext", "className");
            digester.addSetProperties(prefix + "Context");
        } else {
            digester.addRule(prefix + "Context", new SetContextPropertiesRule());
        }

通过解析规则和xml文件我们可以得出以下结论:

Service里的Container对应的是StandardEngine

StandardEngine->StandardHost->StandardContext (前者是后者的父容器,因为是通过addChild方法添加的,意思很明确,就是添加子容器,方法具体内容后面会讲)

并且StandardEngine,StandardHost都注册了监听器

StandardEngine.startInternal()

container.start()的start()方法依旧是采用了模板方法模式设计,所以我们直接看startInternal方法

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

你是不是发现这与之前的都不一样,这里只调用了父类的startInternal方法,本身却没有任何实现。

StandardEngine的直接父类和StandardServer及StandService的直接父类是不一样的,前者是ContainerBase(容器类的直接父类,直接继承LifecycleMBeanBase),而后者直接是LifecycleMBeanBase。

我们来看ContainerBase的startInternal()方法

ContainerBase.startInternal()

   @Override
    protected synchronized void startInternal() throws LifecycleException {

  //...省略一坨代码

        // Start our child containers, if any
        Container children[] = findChildren();
        List> results = new ArrayList>();
        for (int i = 0; i < children.length; i++) {
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }

        boolean fail = false;
        for (Future 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();

    }

先说下这个方法都干了什么:

1.通过findChildren找到Engine的子容器Host

2.使用线程池来start子容器们,子容器start的线程包装在StartChild这个类中。

3.调用 threadStart();来

先看findChildren

      /**
     * The child Containers belonging to this Container, keyed by name.
     */
    protected HashMap children =
        new HashMap();

  @Override
    public Container[] findChildren() {

        synchronized (children) {
            Container results[] = new Container[children.size()];
            return children.values().toArray(results);
        }

    }

我们发现就是取出children这个属性转化为数组,那么children是什么时候注入的呢。

之前我们说过了StandardHost是通过addChild注入到StandardEngine中的。所以children就是通过addchild注入的

    //StandardEngine
    /**
     * 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) {

        if (!(child instanceof Host))
            throw new IllegalArgumentException
                (sm.getString("standardEngine.notHost"));
        super.addChild(child);

    }
    //ContainerBase
  @Override
    public void addChild(Container child) {
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction dp =
                new PrivilegedAddChild(child);
            AccessController.doPrivileged(dp);
        } else {
            addChildInternal(child);
        }
    }

    private void addChildInternal(Container child) {

        if( log.isDebugEnabled() )
            log.debug("Add child " + child + " " + this);
        synchronized(children) {
            if (children.get(child.getName()) != null)
                throw new IllegalArgumentException("addChild:  Child name '" +
                                                   child.getName() +
                                                   "' is not unique");
            child.setParent(this);  // May throw IAE
            children.put(child.getName(), child);
        }

所以我们通过findChildren先成功的找到StandardHost数组,然后我们通过StartChild来执行StandardHost的start()操作。

    //一个带返回值的线程
    private static class StartChild implements Callable<Void> {
        @Override
        public Void call() throws LifecycleException {
            child.start();
            return null;
        }
    }
    }

在这个线程里便执行了StandardHost的start()方法了

我们再看 threadStart();

threadStart()

//*启动将定期检查会话超时的后台线程

    /**
     * Start the background thread that will periodically check for
     * session timeouts.
     */
    protected void threadStart() {

        if (thread != null)
            return;
        if (backgroundProcessorDelay <= 0)
            return;

        threadDone = false;
        String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
        thread = new Thread(new ContainerBackgroundProcessor(), threadName);
        thread.setDaemon(true);
        thread.start();

    }
    protected class ContainerBackgroundProcessor implements Runnable {

        @Override
        public void run() {
//...省略一坨代码
            try {

                    if (!threadDone) {
                        Container parent = (Container) getMappingObject();
                        ClassLoader cl =
                            Thread.currentThread().getContextClassLoader();
                        if (parent.getLoader() != null) {
                            cl = parent.getLoader().getClassLoader();
                        }
                        processChildren(parent, cl);
                    }
                }
          //...省略一坨代码
        }
              protected void processChildren(Container container, ClassLoader cl) {
            try {
                if (container.getLoader() != null) {
                    Thread.currentThread().setContextClassLoader
                        (container.getLoader().getClassLoader());
                }
                container.backgroundProcess();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error("Exception invoking periodic operation: ", t);
            } finally {
                Thread.currentThread().setContextClassLoader(cl);
            }
            Container[] children = container.findChildren();
            for (int i = 0; i < children.length; i++) {
                if (children[i].getBackgroundProcessorDelay() <= 0) {
                    processChildren(children[i], cl);
                }
            }
        }
    }
 /**
     * Execute a periodic task, such as reloading, etc. This method will be
     * invoked inside the classloading context of this container. Unexpected
     * throwables will be caught and logged.
     */
    @Override
    public void backgroundProcess() {

//....省略一大坨代码
        fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
    }

这一段代码看起来似乎有点长,但是都很好理解,就是开一个后台线程来不断检测环境,时刻检测是不是有reload(热部署,你懂的,不懂的自己去查一个哈)的情况出现等等,就不过多解释了,我们直接看最后一个方法:

fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null)

    protected void fireLifecycleEvent(String type, Object data) {
        lifecycle.fireLifecycleEvent(type, data);
    }
//LifecycleSupport类 ,用来构造事件对象
    public void fireLifecycleEvent(String type, Object data) {
    //创建一个事件对象实例
        LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
        LifecycleListener interested[] = listeners;
        for (int i = 0; i < interested.length; i++)
            interested[i].lifecycleEvent(event);

    }

可能你看这段代码会很懵逼,不知道到底在讲些什么鬼东西,那是因为我还有个重要的东西没讲到,那就是监听器的注册,我们之前在使用xml配置文件结合解析规则分析的时候有提到,这些容器都在实例化的时候注册了监听器,我们来回头看看StandardEngine的监听器的注册

   digester.addRule(prefix + "Engine",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.EngineConfig",
                          "engineConfigClass"));

我们可以知道注册的监听器是org.apache.catalina.startup.EngineConfig

我们再看看LifecycleListenerRule的begin方法

 @Override
    public void begin(String namespace, String name, Attributes attributes)
        throws Exception {

        Container c = (Container) digester.peek();
        Container p = null;
        Object obj = digester.peek(1);
        if (obj instanceof Container) {
            p = (Container) obj;
        }

        String className = null;
//...日常省略一坨代码
      //中间一坨就是为了找到正确的className  
        // Instantiate a new LifecycleListener implementation object
      //实例化监听器类
      Class clazz = Class.forName(className);
        LifecycleListener listener =
            (LifecycleListener) clazz.newInstance();
    //将监听器加入到StandardEngine实例中
        // Add this LifecycleListener to our associated component
        c.addLifecycleListener(listener);
    }

addLifecycleListener

这个方法在LifecycleBase中实现

//充当事件    
private LifecycleSupport lifecycle = new LifecycleSupport(this);

@Override
    public void addLifecycleListener(LifecycleListener listener) {
        lifecycle.addLifecycleListener(listener);
    }

熟悉观察者模式(或者说监听器模式,但是设计模式里好像没有单独交监听器模式的)的同学看到这里就应该能明白上面fireLifecycleEvent的意思了。

这里事件监听器是EngineConfig,事件源是LifecycleEvent,事件对象是StandardEngine

好了,我们现在要看我们的回调方法 interested[i].lifecycleEvent(event);了

这里是EngineConfig的lifecycleEvent,我们发现这个监听器,并没有对PERIODIC_EVENT这种类型的事件做出相应的动作,所以继续往下走,执行他的子容器StandardHost的相应事件。

用同样的方法找到StandardHost的监听器,HostConfig,我们看看这个监听器的lifecycleEvent方法,

HostConfig.lifecycleEvent

 @Override
    public void lifecycleEvent(LifecycleEvent event) {



        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
            check();
        } 

把无用的代码都删掉了,不多bb,直接进入到check()方法

check()

  /**
     * Check status of all webapps.
     */
    protected void check() {

        if (host.getAutoDeploy()) {
            // Check for resources modification to trigger redeployment
            DeployedApplication[] apps =
                deployed.values().toArray(new DeployedApplication[0]);
            for (int i = 0; i < apps.length; i++) {
                if (!isServiced(apps[i].name))
                    checkResources(apps[i], false);
            }

            // Check for old versions of applications that can now be undeployed
            if (host.getUndeployOldVersions()) {
                checkUndeploy();
            }

            // Hotdeploy applications
            deployApps();
        }
    }
     */
    protected void deployApps() {

        File appBase = appBase();//// 在server.xml中的Host标签指定appbase的属性为 webapps  
        File configBase = configBase();
        String[] filteredAppPaths = filterAppPaths(appBase.list());////列出appBase下的所有文件、文件夹,进行过滤
        // Deploy XML descriptors from configBase
        deployDescriptors(configBase, configBase.list());
        // Deploy WARs
        deployWARs(appBase, filteredAppPaths);///部署war包  
        // Deploy expanded folders
        deployDirectories(appBase, filteredAppPaths);//部署项目文件夹  

    }

平时我们发布web的时候可以直接拷贝web的目录或者war压缩包到webapps目录下的.因此这里都一一对应了,deployDirectories该方法对应的就是web目录的发布,deployWARs对应的就是war包的发布方式。

我们重点看deployDirectories的部署方式。

deployDirectories

  protected void deployDirectories(File appBase, String[] files) {

        if (files == null)
            return;

        ExecutorService es = host.getStartStopExecutor();
    //为什么要使用Callable而不是Runnable呢,我想应该是想得到线程运行的结果是否有报错把。如果得不到返回,证明就是有错误,就可以抛出异常,让异常在我们想要抛出的地方抛出
        List> results = new ArrayList>();

        for (int i = 0; i < files.length; i++) {

            if (files[i].equalsIgnoreCase("META-INF"))
                continue;
            if (files[i].equalsIgnoreCase("WEB-INF"))
                continue;
            File dir = new File(appBase, files[i]);
            if (dir.isDirectory()) {
                ContextName cn = new ContextName(files[i], false);

                if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
                    continue;

                results.add(es.submit(new DeployDirectory(this, cn, dir)));
            }
        }

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

DeployDirectory

    private static class DeployDirectory implements Runnable {

        private HostConfig config;
        private ContextName cn;
        private File dir;

        public DeployDirectory(HostConfig config, ContextName cn, File dir) {
            this.config = config;
            this.cn = cn;
            this.dir = dir;
        }

        @Override
        public void run() {
            config.deployDirectory(cn, dir);
        }
    }

一个用来部署的线程

deployDirectory


    public static final String ApplicationContextXml = "META-INF/context.xml";

protected void deployDirectory(ContextName cn, File dir) {



//...省略了一大段代码,上面是如果META-INF/context.xml存在的时候实例化StandardContext

          //****重点在这
          else {
                context = (Context) Class.forName(contextClass).newInstance();
            }

            Class clazz = Class.forName(host.getConfigClass());
            LifecycleListener listener =
                (LifecycleListener) clazz.newInstance();
            context.addLifecycleListener(listener);

            context.setName(cn.getName());
            context.setPath(cn.getPath());
            context.setWebappVersion(cn.getVersion());
            context.setDocBase(cn.getBaseName());
            host.addChild(context);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString("hostConfig.deployDir.error",
                    dir.getAbsolutePath()), t);
        } finally {
          //....省略了一堆代码
        }
    }

先说下这个方法的作用吧:

如果META-INF/context.xml存在,Context实例对象,由XML文件中的配置来实例化,感兴趣的可以看源码,方式就是接着解析server.xml的套路。

如果没有这个配置文件的存在,就按我们代码给出的方式给出,代码很简单,就不解释了,要注意,在这里,已经将StandardContext设置成了StandardHost的子容器了,并且也给StandardContext注册了监听器了。这里是很重要的,StandardContext和StandardHost的关系终于建立了。

到这里我们就把StandardEngine的start()方法分析完了。

下一篇文章我们将分析StandardHost的start()方法,正式揭开web.xml的处理过程。

从server.xml文件可以知道Host的父容器是Engine,而从org.apache.catalina.startup.Catalina类的createStartDigester可以知道server.xml的Engine标签创建的是org.apache.catalina.core.StandardEngine类的实例.从上一章分析可以知道StandardEngine的启动是在StandardService类的startInternal方法里面启动的,部分代码如下:

你可能感兴趣的:(javaweb)