Tomcat源码阅读之StandardService与MapperListener分析

前面的文章分析了在tomcat中的container与pipeline的设计。。。我们知道Server是Service对象的容器,

而Service可以有多个connector对象,但是只能有一个container(一般就是engine对象)对象。。

所以分析各个不同的container对象的入口就在于service对象。。。而在tomcat中一般都是用StandardService这个类型。。。

先来看看一个初略的继承结构吧:

Tomcat源码阅读之StandardService与MapperListener分析


这里其实StandardService算是比较简单的了,首先它继承了LifecycleMBeanBase,这说明这个对象在启动之后将会被注册到JMX上去。。。同时它实现了service接口。。。这里就先来看看这个接口的定义吧:

package org.apache.catalina;

import org.apache.catalina.connector.Connector;
import org.apache.catalina.mapper.Mapper;

public interface Service extends Lifecycle {

    public Container getContainer();   //获取拥有的container,service对象只拥有一个container

    public void setContainer(Container container);   //设置拥有的container

    public String getName();  //service的名字

    public void setName(String name);

    public Server getServer();  //返回所属的server对象

    public void setServer(Server server);
    public ClassLoader getParentClassLoader();   //获取parentClassLoader

    public void setParentClassLoader(ClassLoader parent);

    public String getDomain();   //在jmx注册在哪个domain下面

    public void addConnector(Connector connector);   //添加一个connector

    public Connector[] findConnectors();  //返回所有的connector

    public void removeConnector(Connector connector);  // 移除一个connecter

    public void addExecutor(Executor ex);   //添加一个executor

    public Executor[] findExecutors();   //返回所有的executor

    public Executor getExecutor(String name);  //根据名字获取executor

    public void removeExecutor(Executor ex);     //移除一个executor

    Mapper getMapper();   //用于请求的map
}

上面接口的定义其实也还挺简单的,无非首先拥有的container对象的管理,另外这里会管理容纳多个connector对象,同时还有executor对象,在以前分析connector部分的时候,我们知道在connector的创建的时候可以指定executor对象,这里指定的executor就是定义在servie对象里面的,当然如果没有指定的话,那么将会创建默认的executor、。、、、

同时这里还有一个非常重要的值得注意的东西,那就是mapper,他将会具体的负责请求的路由。。。

好啦,那么接下来来看看StandardService类型的属性的定义吧:

    private String name = null;

    private static final StringManager sm =
        StringManager.getManager(Constants.Package);

    private Server server = null;   //所属的server对象

    protected final PropertyChangeSupport support = new PropertyChangeSupport(this);   //用于支持监听属性的修改

    protected Connector connectors[] = new Connector[0];    //connector的数组
    private final Object connectorsLock = new Object();  //当修改connector时候用到的锁

    protected final ArrayList<Executor> executors = new ArrayList<>();   //所有定义的executor,connector可以用这个里面定义的executor

    protected Container container = null;  //关联的container

    private ClassLoader parentClassLoader = null;   //classLoader

    protected final Mapper mapper = new Mapper();  //用于请求的map,host的map,warpper,context的map啥的

这里具体每个属性是干嘛用的注释应该说的蛮清楚的吧。。。接下来来看看StandardService启动吧:

    //启动
    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();  //先启动container
            }
        }

        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();  //启动所有的executor
            }
        }


        mapperListener.start();  //启动mapperListener

        // 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();  //启动所有的connector
                    }
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
        }
    }

这里代码应该也挺简单的吧,首先设置当前组件的状态,然后启动拥有的container对象,其实一般都是engine,然后启动所有定义的executor对象,接着启动mapperListener,这个非常重要,待会分析

接下来就是启动connector,这样service就算是启动起来了。。。

嗯,其实service这部分真的很简单。。没啥说的。。。

还啦。。。接下来就来看看mapperListener的start干了啥吧。。。

先来看看MapperListener的比较初略的继承体系吧:

Tomcat源码阅读之StandardService与MapperListener分析


这里LifecycleMBeanBase的继承,说明对象的启动会被注册到jmx上面去。。。另外这里比较有意思的是实现了ContainerListener和LifecycleListener,说明既可以响应container的事件,例如添加child,添加valve啥的,同时还可以响应组件的状态事件。。。。

这里先来看看它的属性的定义吧:

    private final Mapper mapper;  //service的mapper,用于对请求进行路由

    private final Service service;  //所属的service对象

    private static final StringManager sm =
        StringManager.getManager(Constants.Package);

    private final String domain = null;  //注册在jmx哪个域名下面

    public MapperListener(Mapper mapper, Service service) {
        this.mapper = mapper;
        this.service = service;
    }

这里属性不多吧,具体的干啥用的都在注释后面贴了出来。。。

接下来来看看它的启动吧:

    public void startInternal() throws LifecycleException {

        setState(LifecycleState.STARTING);

        // Find any components that have already been initialized since the
        // MBean listener won‘t be notified as those components will have
        // already registered their MBeans
        findDefaultHost();  //获取当前engine默认的host

        Engine engine = (Engine) service.getContainer();  //获取当前service的container,其实也就是engine
        addListeners(engine);  //为engine添加listener,将listener都设置为当前

        Container[] conHosts = engine.findChildren();  //获取这个engine所有的host
        for (Container conHost : conHosts) {  //遍历engine下面的所有的host定义
            Host host = (Host) conHost;
            if (!LifecycleState.NEW.equals(host.getState())) {
                // Registering the host will register the context and wrappers
                registerHost(host);  //登记host对象
            }
        }
    }

这个首先设置当前组件的状态,接着获取engine对象定义的默认的host的名字,看一段配置文件就知道了:

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

每一个engine都可以指定默认使用的host对象,后面的就是设置为默认的host对象的名字。。

这里获取service对象拥有的container对象,其实就是engine对象,然后调用addListeners方法为其添加监听,最后再遍历当前engine对象的所有的host对象,调用registerHost方法注册他们。。

这里先来看看这个addListeners方法吧:

    //为container对象添加lister,都设置为当前
    private void addListeners(Container container) {
        container.addContainerListener(this);   //将这个container对象的container事件的监听设置为当前对象
        container.addLifecycleListener(this);  //设置当前对象来监听状态事件
        for (Container child : container.findChildren()) {  //遍历这个对象的所有的子container对象
            addListeners(child);  //同时将所有的子container的监听设置为当前对象
        }
    }

这里其实递归的来设置container的监听,不光设置当前container对象,还要讲当前container对象的子container的监听都设置了。。这样就保证了当前service整个container的体系都设置了当前MapperListener为监听。。。

好啦,既然要设置监听,那么我们就来看看对于事件他将会做啥吧,首先来看看如何来响应container的事件吧:

    //也就是有child加入,或者valve加入的时候会激活 的事件,这里会将child的listener也设置为当前
    public void containerEvent(ContainerEvent event) {

        if (Container.ADD_CHILD_EVENT.equals(event.getType())) {  //如果是添加child的事件
            Container child = (Container) event.getData();
            addListeners(child);  // 为这个child添加listener,将其的lifecycle和containerlistenre都指定为当前对象
            // If child is started then it is too late for life-cycle listener
            // to register the child so register it here
            if (child.getState().isAvailable()) {
                if (child instanceof Host) {
                    registerHost((Host) child);  //如果这个对象是host,那么需要注册host
                } else if (child instanceof Context) {
                    registerContext((Context) child);  //如果是context那么需要登记
                } else if (child instanceof Wrapper) {  //如果是warpper,登记
                    registerWrapper((Wrapper) child);
                }
            }
        } else if (Container.REMOVE_CHILD_EVENT.equals(event.getType())) {
            Container child = (Container) event.getData();
            removeListeners(child);
            // No need to unregister - life-cycle listener will handle this when
            // the child stops
        } else if (Host.ADD_ALIAS_EVENT.equals(event.getType())) {
            // Handle dynamically adding host aliases
            mapper.addHostAlias(((Host) event.getSource()).getName(),
                    event.getData().toString());
        } else if (Host.REMOVE_ALIAS_EVENT.equals(event.getType())) {
            // Handle dynamically removing host aliases
            mapper.removeHostAlias(event.getData().toString());
        } else if (Wrapper.ADD_MAPPING_EVENT.equals(event.getType())) {  //warper的mapping事件
            // Handle dynamically adding wrappers
            Wrapper wrapper = (Wrapper) event.getSource();  //获取当前warpper对象
            Context context = (Context) wrapper.getParent();  //获取当前warpper所属的context
            String contextPath = context.getPath();  // 获取context的path
            if ("/".equals(contextPath)) {  //如果是根
                contextPath = "";
            }
            String version = ((Context) wrapper.getParent()).getWebappVersion();
            String hostName = context.getParent().getName();  //获取host的名字
            String wrapperName = wrapper.getName();  //获取warpper对象的名字
            String mapping = (String) event.getData();  //要map的路径
            boolean jspWildCard = ("jsp".equals(wrapperName)
                    && mapping.endsWith("/*"));
            mapper.addWrapper(hostName, contextPath, version, mapping, wrapper,  //在engine的mapper上面注册
                    jspWildCard, context.isResourceOnlyServlet(wrapperName));
        } else if (Wrapper.REMOVE_MAPPING_EVENT.equals(event.getType())) {
            // Handle dynamically removing wrappers
            Wrapper wrapper = (Wrapper) event.getSource();

            String contextPath = ((Context) wrapper.getParent()).getPath();
            if ("/".equals(contextPath)) {
                contextPath = "";
            }
            String version = ((Context) wrapper.getParent()).getWebappVersion();
            String hostName = wrapper.getParent().getParent().getName();

            String mapping = (String) event.getData();

            mapper.removeWrapper(hostName, contextPath, version, mapping);
        } else if (Context.ADD_WELCOME_FILE_EVENT.equals(event.getType())) {
            // Handle dynamically adding welcome files
            Context context = (Context) event.getSource();

            String hostName = context.getParent().getName();

            String contextPath = context.getPath();
            if ("/".equals(contextPath)) {
                contextPath = "";
            }

            String welcomeFile = (String) event.getData();

            mapper.addWelcomeFile(hostName, contextPath,
                    context.getWebappVersion(), welcomeFile);
        } else if (Context.REMOVE_WELCOME_FILE_EVENT.equals(event.getType())) {
            // Handle dynamically removing welcome files
            Context context = (Context) event.getSource();

            String hostName = context.getParent().getName();

            String contextPath = context.getPath();
            if ("/".equals(contextPath)) {
                contextPath = "";
            }

            String welcomeFile = (String) event.getData();

            mapper.removeWelcomeFile(hostName, contextPath,
                    context.getWebappVersion(), welcomeFile);
        } else if (Context.CLEAR_WELCOME_FILES_EVENT.equals(event.getType())) {
            // Handle dynamically clearing welcome files
            Context context = (Context) event.getSource();

            String hostName = context.getParent().getName();

            String contextPath = context.getPath();
            if ("/".equals(contextPath)) {
                contextPath = "";
            }

            mapper.clearWelcomeFiles(hostName, contextPath,
                    context.getWebappVersion());
        }
    }

这里很重要吧,首先是对于ADD_CHILD_EVENT事件,如果这里就设对这个加入的子container对象的监听,然后根据不同的类型,进行相应的登记 。。。。。

其实到这里就对整个MapperListener的运行就算比较了解了,通过对container的监控,来进行整个请求的map处理。。。这里就来看看registerHost方法做了啥吧:

    //登记host对象
    private void registerHost(Host host) {

        String[] aliases = host.findAliases();  //获取当前host的别名
        mapper.addHost(host.getName(), aliases, host);  // 将host与其名字对应起来

        for (Container container : host.findChildren()) {  //登记这个host下面的所有的context
            if (container.getState().isAvailable()) {
                registerContext((Context) container);
            }
        }
        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerHost",
                    host.getName(), domain, service));
        }
    }

这个其实就是在service对象中的mapper中建立host的名字,别名与host对象的对象。。这样通过名字就能很快的索引到host了。。。。它用于干啥呢?嗯,其实就是对http请求的host字段进行路由。。。

好啦,接下来来看看registerContext方法,怎么登记context对象吧(其实一个context对象就对应着一个web应用程序)。。

   //登记context对象,在里面要接着登记warrper
    private void registerContext(Context context) {

        String contextPath = context.getPath();  //获取context的path
        if ("/".equals(contextPath)) {
            contextPath = "";
        }
        Host host = (Host)context.getParent();  //这里获取的其实是host

        WebResourceRoot resources = context.getResources();  //获取root
        String[] welcomeFiles = context.findWelcomeFiles();  //获取welcomeFile

        mapper.addContextVersion(host.getName(), host, contextPath,
                context.getWebappVersion(), context, welcomeFiles, resources);  //对添加context的map

        for (Container container : context.findChildren()) {
            registerWrapper((Wrapper) container);  //登记所有的warpper对象
        }

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerContext",
                    contextPath, service));
        }
    }

这里首先获获取了当前context的path,然后获取这个context所属的host对象,接着获取webroot的引用,以及首页啥的,然后将这些属性一起注册到service的mapper上面去,这样就能够很快的对http请求的context的进行定位了。。。最后解释登记Wrapper(这里可以先简单的理解为servlet的一层包装)了:

   //登记warpper对象
    private void registerWrapper(Wrapper wrapper) {

        String wrapperName = wrapper.getName();  //获取名字
        Context context = (Context) wrapper.getParent();  //获取所属的context对象
        String contextPath = context.getPath();  //获取context的path
        if ("/".equals(contextPath)) {
            contextPath = "";
        }
        String version = ((Context) wrapper.getParent()).getWebappVersion();  //webapp的版本
        String hostName = context.getParent().getName();  //

        String[] mappings = wrapper.findMappings();  //获取这个warpper所有的mapping信息,毕竟一个servlet可能可以处理多个请求路径嘛

        for (String mapping : mappings) {  //根据mapping的信息,在service的mapper里面进行mapper
            boolean jspWildCard = (wrapperName.equals("jsp")
                                   && mapping.endsWith("/*"));
            mapper.addWrapper(hostName, contextPath, version, mapping, wrapper,
                              jspWildCard,
                              context.isResourceOnlyServlet(wrapperName));
        }

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerWrapper",
                    wrapperName, contextPath, service));
        }
    }

这里其实跟上面的差不多,只不过注册的时候多了一些信息,到这里位置,host的索引,context索引,直到warpper(servlet)的索引就算都搞定了。。。

那么是不是整个http请求的路由也就差不多啦。。。也就差分析Mapper对象了,这个以后再说吧。。

这里整个MapperListener的工作也都差不多啦,当然也还有一些状态事件的相应,例如启动啥的,其实要做的事情也都差不多,无非是登记,或者基础mapper的登记。。。。

这样对于tomcat是如何维护请求的map信息也就比较的清楚了。。。。

Tomcat源码阅读之StandardService与MapperListener分析,布布扣,bubuko.com

Tomcat源码阅读之StandardService与MapperListener分析

上一篇:[Windows Phone]解锁、注册Windows Phone实体手机为开发机(Windows 8)


下一篇:重新想象 Windows 8 Store Apps (33) - 关联启动: 使用外部程序打开一个文件或uri, 关联指定的文件类型或协议