vlambda博客
学习文章列表

tomcat8源码分析03-核心组件创建

从tomcat的架构图来看,tomcat的核心组件有Server、Service、Connector、Container。

这些组件是如何被创建出来的?

通过源码我们可以看到,tomcat在启动的时候实例化了Catalina类并执行了初始化、启动操作,而Catalina实例在执行load时会读取conf/server.xml,从解析完配置后调用getServer().setCatalina(this)我们可以猜到在配置解析的时候server会被实例化,也就是说tomcat实际是通过控制反转来实例化这些对象。

// BootStrap.java
daemon.setAwait(true);
daemon.load(args);
daemon.start();
// Catalina.java
public void load() {
    ...
    // 读取server.xml配置流
    file = configFile();
    inputStream = new FileInputStream(file);
    ...
    // 创建解析器
    Digester digester = createStartDigester();
    ...
    try {
        inputSource.setByteStream(inputStream);
        digester.push(this);
        digester.parse(inputSource);
    } catch (SAXParseException spe) {
        log.warn("Catalina.start using " + getConfigFile() + ": " +
                spe.getMessage());
        return;
    }
    ...
    // getServer不再是null说明在配置解析后server已经被实例化并注入到catalina实例中
    getServer().setCatalina(this);
    ...
}

那么tomcat是如何实现通过控制反转创建四大件的?从createStartDigester方法可以看出在创建解析器时代码上写了很多解析规则(Rule),比如Server的实现类是org.apache.catalina.core.StandardServer继承于LifecycleMBeanBase类,而LifecycleMBeanBase类继承了LifecycleBase并实现了JmxEnabled接口,这样我们只要查看对应的解析规则和父类就可以看到Server是如何被实例化的。

// Catalina.java
protected Digester createStartDigester() {
    ...
    // 创建实例
    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
    // 注入字符类型成员变量
    digester.addSetProperties("Server");
    // 调用父节点方法为父节点注入特殊成员变量,比如Server的父节点是Catalina
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");
    ...
    return digester;
}

// Digester.java
public void addObjectCreate(String pattern, String className, String attributeName) {
    // 添加一个实例化对象的解析规则
    addRule(pattern, new ObjectCreateRule(className, attributeName));
}
public void addSetProperties(String pattern) {
    // 添加注入字符类型成员变量规则
    addRule(pattern, new SetPropertiesRule());

}
public void startElement(String namespaceURI, String localName, String qName, Attributes list)
        throws SAXException 
{
    ...
    // Fire "begin" events for all relevant rules
    // match初始是空字符串,实际上是我们xml标签的一种表现形式,比如读到server.xml的Server标签时match为Server,读到Listener标签时为Server/Listener
    List<Rule> rules = getRules().match(namespaceURI, match);
    matches.push(rules);
 if ((rules != null) && (rules.size() > 0)) {
     for (Rule value : rules) {
         try {
             Rule rule = value;
             if (debug) {
                 log.debug("  Fire begin() for " + rule);
             }
                    // 执行解析规则
             rule.begin(namespaceURI, name, list);
         } catch (Exception e) {
             log.error("Begin event threw exception", e);
             throw createSAXException(e);
         } catch (Error e) {
             log.error("Begin event threw error", e);
             throw e;
         }
     }
 }
    ...
}

// ObjectCreateRule.java
public void begin(String namespace, String name, Attributes attributes)
        throws Exception 
{
    ...
    // 根据解析规则创建实例
    // Instantiate the new object and push it on the context stack
    Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
    Object instance = clazz.getConstructor().newInstance();
    digester.push(instance);
}

// SetNextRule.java
public void end(String namespace, String name) throws Exception {
    // Identify the objects to be used
    Object child = digester.peek(0);
    Object parent = digester.peek(1);
    if (digester.log.isDebugEnabled()) {
        if (parent == null) {
            digester.log.debug("[SetNextRule]{" + digester.match +
                    "} Call [NULL PARENT]." +
                    methodName + "(" + child + ")");
        } else {
            digester.log.debug("[SetNextRule]{" + digester.match +
                    "} Call " + parent.getClass().getName() + "." +
                    methodName + "(" + child + ")");
        }
    }
    // Call the specified method
    // 这一步是和Catalina实例设置的规则相关的,当规则匹配时父节点的方法将被调用
    // digester.addSetNext("Server",
    //                            "setServer",
    //                            "org.apache.catalina.Server");
    IntrospectionUtils.callMethod1(parent, methodName,
            child, paramType, digester.getClassLoader());
}

我们可以看到SetNextRule解析时需要依赖栈结构(栈顶作为child,栈顶下一个元素作为parent),那么Server作为最顶层标签而且又设置了SetNextRule规则,它要怎么找到它的父节点呢?其实这一步是在代码中完成的,代码在解析server.xml之前先把Catalina实例push进了栈顶,这样在执行Server的SetNextRule时就可以调用Catalina的setServer把Server对象注入到Catalina的server变量中了。

// Catalina.java
public void load() {
    ...
    // 将当前实例设置到解析器栈顶
    digester.push(this);
    ...
}

事实上tomcat用控制反转并不单单在加载四大组件上,通过源码我们可以发现在加载org.apache.catalina.mbeans.GlobalResourcesLifecycleListener时会再次创建一个disgester,这个disgester是配合mbeans一起使用的,从注释来看作用是为了支持JNDI,但原理和上面说的类似,预设好一些Rule然后配合classloader实例化出对象。

// MBeanUtils.java
public static synchronized Registry createRegistry() {
    if (registry == null) {
        registry = Registry.getRegistry(nullnull);
        ClassLoader cl = MBeanUtils.class.getClassLoader();

        // 下面路径的mbeans-descriptors.xml都会被加载
        registry.loadDescriptors("org.apache.catalina.mbeans",  cl);
        registry.loadDescriptors("org.apache.catalina.authenticator", cl);
        registry.loadDescriptors("org.apache.catalina.core", cl);
        registry.loadDescriptors("org.apache.catalina", cl);
        registry.loadDescriptors("org.apache.catalina.deploy", cl);
        registry.loadDescriptors("org.apache.catalina.loader", cl);
        registry.loadDescriptors("org.apache.catalina.realm", cl);
        registry.loadDescriptors("org.apache.catalina.session", cl);
        registry.loadDescriptors("org.apache.catalina.startup", cl);
        registry.loadDescriptors("org.apache.catalina.users", cl);
        registry.loadDescriptors("org.apache.catalina.ha", cl);
        registry.loadDescriptors("org.apache.catalina.connector", cl);
        registry.loadDescriptors("org.apache.catalina.valves",  cl);
        registry.loadDescriptors("org.apache.catalina.storeconfig",  cl);
        registry.loadDescriptors("org.apache.tomcat.util.descriptor.web",  cl);
    }
    return registry;
}
// Registry.java
public void loadDescriptors(String packageName, ClassLoader classLoader) {
    String res = packageName.replace('.''/');
    if (log.isTraceEnabled()) {
        log.trace("Finding descriptor " + res);
    }
    if (searchedPaths.get(packageName) != null) {
        return;
    }
    // 规则描述文件mbeans-descriptors.xml
    String descriptors = res + "/mbeans-descriptors.xml";
    URL dURL = classLoader.getResource(descriptors);
    if (dURL == null) {
        return;
    }
    log.debug("Found " + dURL);
    searchedPaths.put(packageName, dURL);
    try {
        load("MbeansDescriptorsDigesterSource", dURL, null);
    } catch(Exception ex ) {
        log.error("Error loading " + dURL);
    }
}

总结:通过源码我们可以观察到StandardServer、StandardService、StandardConnector及ContainerBase都继承自LifecycleMBeanBase,所以他们实例化的方式其实都和Server大径相同。也就是四大件都是以控制反转的方式(通过解析server.xml)创建,server.xml在解析时层级结构会以栈的形式存储在Digester的stack上,这样遇到SetNextRule规则时就可以通过父子层级关系注入成员变量。