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有关的部分。

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">


  <!-- A "Service" is a collection of one or more "Connectors" that share
       a single "Container" Note:  A "Service" is not itself a "Container",
       so you may not define subcomponents such as "Valves" at this level.
       Documentation at /docs/config/service.html
   -->
  <Service name="Catalina">


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

      <!--For clustering, please take a look at documentation at:
          /docs/cluster-howto.html  (simple how to)
          /docs/config/cluster.html (reference documentation) -->
      <!--
      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
      -->

      <!-- Use the LockOutRealm to prevent attempts to guess user passwords
           via a brute-force attack -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
      </Realm>

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

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t &quot;%r&quot; %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<Future<Void>> results = new ArrayList<Future<Void>>();
        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();

    }

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

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

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

3.调用 threadStart();来

先看findChildren

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

  @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<Void> 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<Future<?>> results = new ArrayList<Future<?>>();

        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方法里面启动的,部分代码如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值