Tomcat源码阅读#2:Server配置

Tomcat Startup Sequence

Sequence 1. Start from Command Line
Class: org.apache.catalina.startup.Bootstrap
What it does:
    a) Set up classloaders
    b) Load startup class (reflection)
    c) Bootstrap.daemon.init() complete

Sequence 2. Process command line argument (start, stop)
Class: org.apache.catalina.startup.Bootstrap (assume command->start)
What it does:
    a) Catalina.setAwait(true);
    b) Catalina.load()
    c) Catalina.start()
    d) Tomcat receives a request on an HTTP port
    e) Invocation of the servlet class
<Server ...>
    <Listener .../>
    <Service ...>
        <Executor .../>
        <Connector .../>
        <Engine ...>
            <Realm .../>
            <Host .../>
        </Engine>
    </Service>
</Server>

上一篇文章我们讲到了Catalina#start方法,今天我们来继续往下看看。

Digester


start方法主要做的就是Server的初始化,启动还有关闭。

 /**
     * Start a new server instance.
     */
    public void load() {

        long t1 = System.nanoTime();

        initDirs();

        // Before digester - it may be needed

        initNaming();

        ////////////////////////////////////////
        // 1. 创建解析server.xml的JAXP Handler
        ////////////////////////////////////////
        // Create and execute our Digester
        Digester digester = createStartDigester();

        ////////////////////////////////////////
        // 2. 读取server.xml
        ////////////////////////////////////////
        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            file = configFile();
            inputStream = new FileInputStream(file);
            inputSource = new InputSource(file.toURI().toURL().toString());
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("catalina.configFail", file), e);
            }
        }
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                    .getResourceAsStream(getConfigFile());
                inputSource = new InputSource
                    (getClass().getClassLoader()
                     .getResource(getConfigFile()).toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail",
                            getConfigFile()), e);
                }
            }
        }

        // This should be included in catalina.jar
        // Alternative: don't bother with xml, just create it manually.
        if( inputStream==null ) {
            try {
                inputStream = getClass().getClassLoader()
                        .getResourceAsStream("server-embed.xml");
                inputSource = new InputSource
                (getClass().getClassLoader()
                        .getResource("server-embed.xml").toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail",
                            "server-embed.xml"), e);
                }
            }
        }

        if (inputStream == null || inputSource == null) {
            if  (file == null) {
                log.warn(sm.getString("catalina.configFail",
                        getConfigFile() + "] or [server-embed.xml]"));
            } else {
                log.warn(sm.getString("catalina.configFail",
                        file.getAbsolutePath()));
                if (file.exists() && !file.canRead()) {
                    log.warn("Permissions incorrect, read permission is not allowed on the file.");
                }
            }
            return;
        }

        ////////////////////////////////////////
        // 3. 解析server.xml
        ////////////////////////////////////////
        try {
            inputSource.setByteStream(inputStream);
            digester.push(this);
            digester.parse(inputSource);
        } catch (SAXParseException spe) {
            log.warn("Catalina.start using " + getConfigFile() + ": " +
                    spe.getMessage());
            return;
        } catch (Exception e) {
            log.warn("Catalina.start using " + getConfigFile() + ": " , e);
            return;
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                // Ignore
            }
        }

        getServer().setCatalina(this);

        // Stream redirection
        initStreams();

        ////////////////////////////////////////
        // 4. Server初始化
        ////////////////////////////////////////
        // Start the new server
        try {
            getServer().init();
        } catch (LifecycleException e) {
            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
                throw new java.lang.Error(e);
            } else {
                log.error("Catalina.start", e);
            }

        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
        }

    }

Digester是用于解析server.xmlhandler(参考JAXP的使用),

  /**
     * Create and configure the Digester we will be using for startup.
     */
    protected Digester createStartDigester() {
        long t1=System.currentTimeMillis();
        // Initialize the digester
        Digester digester = new Digester();
        digester.setValidating(false);
        digester.setRulesValidation(true);
        HashMap<Class<?>, List<String>> fakeAttributes =
            new HashMap<Class<?>, List<String>>();
        ArrayList<String> attrs = new ArrayList<String>();
        attrs.add("className");
        fakeAttributes.put(Object.class, attrs);
        digester.setFakeAttributes(fakeAttributes);
        digester.setUseContextClassLoader(true);

        // Configure the actions we will be using
        digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");
        digester.addSetProperties("Server");
        digester.addSetNext("Server",
                            "setServer",
                            "org.apache.catalina.Server");

        digester.addObjectCreate("Server/GlobalNamingResources",
                                 "org.apache.catalina.deploy.NamingResources");
        digester.addSetProperties("Server/GlobalNamingResources");
        digester.addSetNext("Server/GlobalNamingResources",
                            "setGlobalNamingResources",
                            "org.apache.catalina.deploy.NamingResources");

        digester.addObjectCreate("Server/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Listener");
        digester.addSetNext("Server/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");

        digester.addObjectCreate("Server/Service",
                                 "org.apache.catalina.core.StandardService",
                                 "className");
        digester.addSetProperties("Server/Service");
        digester.addSetNext("Server/Service",
                            "addService",
                            "org.apache.catalina.Service");

        digester.addObjectCreate("Server/Service/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Service/Listener");
        digester.addSetNext("Server/Service/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");

        //Executor
        digester.addObjectCreate("Server/Service/Executor",
                         "org.apache.catalina.core.StandardThreadExecutor",
                         "className");
        digester.addSetProperties("Server/Service/Executor");

        digester.addSetNext("Server/Service/Executor",
                            "addExecutor",
                            "org.apache.catalina.Executor");

        digester.addRule("Server/Service/Connector",
                         new ConnectorCreateRule());
        digester.addRule("Server/Service/Connector",
                         new SetAllPropertiesRule(new String[]{"executor"}));
        digester.addSetNext("Server/Service/Connector",
                            "addConnector",
                            "org.apache.catalina.connector.Connector");

        digester.addObjectCreate("Server/Service/Connector/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties("Server/Service/Connector/Listener");
        digester.addSetNext("Server/Service/Connector/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");

        // Add RuleSets for nested elements
        digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
        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/");

        long t2=System.currentTimeMillis();
        if (log.isDebugEnabled()) {
            log.debug("Digester for server.xml created " + ( t2-t1 ));
        }
        return (digester);

    }

接下来看看Digester的几个方法,看看是怎么玩的。

addObjectCreate

首先是addObjectCreate方法,

   /**
     * Add an "object create" rule for the specified parameters.
     *
     * @param pattern Element matching pattern
     * @param className Java class name to be created
     * @see ObjectCreateRule
     */
    public void addObjectCreate(String pattern, String className) {

        addRule(pattern,
                new ObjectCreateRule(className));

    }
    /**
     * <p>Register a new Rule matching the specified pattern.
     * This method sets the <code>Digester</code> property on the rule.</p>
     *
     * @param pattern Element matching pattern
     * @param rule Rule to be registered
     */
    public void addRule(String pattern, Rule rule) {

        rule.setDigester(this);
        getRules().add(pattern, rule);

    }

也就是增加了一个ObjectCreateRule规则,这些规则在Digester#startElement中使用,这些涉及到JAXP的使用就不细说了。来看看ObjectCreateRule这个规则做了啥,

    /**
     * Process the beginning of this element.
     *
     * @param namespace the namespace URI of the matching element, or an
     *   empty string if the parser is not namespace aware or the element has
     *   no namespace
     * @param name the local name if the parser is namespace aware, or just
     *   the element name otherwise
     * @param attributes The attribute list for this element
     */
    @Override
    public void begin(String namespace, String name, Attributes attributes)
            throws Exception {

        // Identify the name of the class to instantiate
        String realClassName = className;
        if (attributeName != null) {
            String value = attributes.getValue(attributeName);
            if (value != null) {
                realClassName = value;
            }
        }
        if (digester.log.isDebugEnabled()) {
            digester.log.debug("[ObjectCreateRule]{" + digester.match +
                    "}New " + realClassName);
        }

        if (realClassName == null) {
            throw new NullPointerException("No class name specified for " +
                    namespace + " " + name);
        }

        // Instantiate the new object and push it on the context stack
        Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
        Object instance = clazz.newInstance();
        digester.push(instance);
    }

    /**
     * Process the end of this element.
     *
     * @param namespace the namespace URI of the matching element, or an
     *   empty string if the parser is not namespace aware or the element has
     *   no namespace
     * @param name the local name if the parser is namespace aware, or just
     *   the element name otherwise
     */
    @Override
    public void end(String namespace, String name) throws Exception {

        Object top = digester.pop();
        if (digester.log.isDebugEnabled()) {
            digester.log.debug("[ObjectCreateRule]{" + digester.match +
                    "} Pop " + top.getClass().getName());
        }

    }

OK,就是用Digester的ClassLoader反射出来我们规则中指定的class,并且push到Digester。举个栗子,最开始的digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className");,并且在server.xml中有这样的配置,

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

当遇到<Server>时就会创建一个org.apache.catalina.core.StandardServer

addSetProperties

类似的可以看到addSetProperties所做的事情,SetPropertiesRule会先拿到Digester#stack最顶端的对象(digester.peek();),并对它调用setter方法(IntrospectionUtils.setProperty)。还是上面的栗子,

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

解析到portshutdown这两个attribute时,就会调用StandarServer#setPortStandarServer#setShutdown方法。

addSetNext

addSerNext增加的是SetNextRule,它没有start方法,只有end

   /**
     * Process the end of this element.
     *
     * @param namespace the namespace URI of the matching element, or an
     *   empty string if the parser is not namespace aware or the element has
     *   no namespace
     * @param name the local name if the parser is namespace aware, or just
     *   the element name otherwise
     */
    @Override
    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
        IntrospectionUtils.callMethod1(parent, methodName,
                child, paramType, digester.getClassLoader());

    }

也就是对次顶的对象调用methodName所指定的方法将栈顶对象传进去,上面的栗子,比较恶心的一个地方是,在Catalina#load()方法中,有一个digester.push的调用,digester.push(this);(非常的ugly!代码耦合度太高),也就是说一开始的栈顶元素是Catalina,所以当遇到</Server>就会调用Catalina#setServer方法。

接下来看看其他几个规则。

Connector

Connector解析,

 digester.addRule("Server/Service/Connector",
                         new ConnectorCreateRule());
        digester.addRule("Server/Service/Connector",
                         new SetAllPropertiesRule(new String[]{"executor"}));
        digester.addSetNext("Server/Service/Connector",
                            "addConnector",
                            "org.apache.catalina.connector.Connector");

看看ConnectorCreateRule做了啥,

    @Override
    public void begin(String namespace, String name, Attributes attributes)
            throws Exception {
        Service svc = (Service)digester.peek();
        Executor ex = null;
        if ( attributes.getValue("executor")!=null ) {
            ex = svc.getExecutor(attributes.getValue("executor"));
        }
        Connector con = new Connector(attributes.getValue("protocol"));
        if ( ex != null )  _setExecutor(con,ex);

        digester.push(con);
    }

看看server.xmlConnector的配置,

 <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

所以这里会使用new Connector("HTTP/1.1");来创建一个Connector

   public Connector(String protocol) {
        setProtocol(protocol);
        // Instantiate protocol handler
        try {
            Class<?> clazz = Class.forName(protocolHandlerClassName);
            this.protocolHandler = (ProtocolHandler) clazz.newInstance();
        } catch (Exception e) {
            log.error(sm.getString(
                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);
        }
    }
   public void setProtocol(String protocol) {

        if (AprLifecycleListener.isAprAvailable()) {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpAprProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            } else {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            }
        } else {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11Protocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            }
        }

    }

所以默认情况下的protocolHandlerHttp11Protocol

Engine

Engine解析,

  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做的事情比较简单,我们来看看HostRuleSet

 @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 CopyParentClassLoaderRule());
        digester.addRule(prefix + "Host",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.HostConfig",
                          "hostConfigClass"));
        digester.addSetNext(prefix + "Host",
                            "addChild",
                            "org.apache.catalina.Container");

        digester.addCallMethod(prefix + "Host/Alias",
                               "addAlias", 0);

        //Cluster configuration start
        digester.addObjectCreate(prefix + "Host/Cluster",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties(prefix + "Host/Cluster");
        digester.addSetNext(prefix + "Host/Cluster",
                            "setCluster",
                            "org.apache.catalina.Cluster");
        //Cluster configuration end

        digester.addObjectCreate(prefix + "Host/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties(prefix + "Host/Listener");
        digester.addSetNext(prefix + "Host/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");

        digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));

        digester.addObjectCreate(prefix + "Host/Valve",
                                 null, // MUST be specified in the element
                                 "className");
        digester.addSetProperties(prefix + "Host/Valve");
        digester.addSetNext(prefix + "Host/Valve",
                            "addValve",
                            "org.apache.catalina.Valve");

    }

Host配置,

   <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="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"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t "%r" %s %b" />

      </Host>

所以会创建一个StandarHost,根据LifecycleListenerRule规则,如果配置了hostConfigClass就创建,否则就使用org.apache.catalina.startup.HostConfig,并且将这个hostConfigClass设置成StandarHostLifecycleListener。接下来会对StandarEngine调用addChild方法,将StandarHost设置成StandarEngine的子容器。对于Valve节点会根据配置的className生成不同的Valve并调用StandarHostaddValve方法。

参考资料

时间: 2024-08-18 05:52:39

Tomcat源码阅读#2:Server配置的相关文章

Tomcat源码阅读#3:Server初始化与启动

b) Catalina.load() b1) initDirs() -> set properties like catalina.home catalina.base == catalina.home (most cases) b2) initNaming setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY, org.apache.naming.java.javaURLContextFactory ->default) b3) c

Tomcat源码阅读#1:classloader初始化

Bootstrap 通过Tomcat的启动脚本可以看到启动的入口是在Bootstrap,来看下Bootstrap的main方法, /** * Main method and entry point when starting Tomcat via the provided * scripts. * * @param args Command line arguments to be processed */ public static void main(String args[]) { if

怎么样阅读tomcat源码

   最近在阅读tomcat 源码,但是进展很慢,一个测试用例有时候要跑很多天也弄不明其中的原理,每跑一边测试用例被虐的怀疑人生,市面的资料很少有详细讲解tomcat 源码阅读系列,例如tomcat中的JMX不少资料都是一笔带过 所以很纠结,不知道各位大咖有没有什么好的建议和方法,望不吝共享

java-tomcat4.1.40源码阅读HttpProcessor疑点求解?

问题描述 tomcat4.1.40源码阅读HttpProcessor疑点求解? 1.首先:org.apache.catalina.connector.http.HttpConnector作为守护线程启动之 前,创建一个了Stack(org.apache.catalina.connector.http.HttpProcessor)用来准备 处理Socket. HttpConnector的Start()方法: public void start() throws LifecycleException

Tomcat源码分析——server.xml文件的解析

前言 在<Tomcat源码分析--server.xml文件的加载>一文中我们介绍了server.xml的加载,本文基于Tomcat7.0的Java源码,接着对server.xml文件是如何解析的进行分析. 概要 规则 Tomcat将server.xml文件中的所有元素上的属性都抽象为Rule,以Server元素为例,在内存中对应Server实例,Server实例的属性值就来自于Server元素的属性值.通过对规则(Rule)的应用,最终改变Server实例的属性值. Rule是一个抽象类,其中

Tomcat源码分析——server.xml文件的加载

前言 作为Java程序员,对于tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载的进行分析. 源码分析 Bootstrap的load方法用于加载tomcat的server.xml,实际是通过反射调用Catalina的load方法,代码如下: /** * Load daemon. */ private void load(String[] arguments) throws Exception { // Call the

《Spark大数据分析实战》——2.4节配置Spark源码阅读环境

2.4 配置Spark源码阅读环境 由于Spark使用SBT作为项目管理构建工具,SBT的配置文件中配置了依赖的jar包网络路径,在编译或者生成指定类型项目时需要从网络下载jar包.需要用户预先安装git.在Linux操作系统或者Windows操作系统上(用户可以下载Git Shell,在Git Shell中进行命令行操作)通过"sbt/sbt gen-idea"命令,生成Intellij项目文件,然后在Intellij IDE中直接通过"Open Project"

Tomcat源码分析----初始化与启动

1.前言 1.1 问题思考 在阅读tomcat源码前,我们一般都会有如下几个疑问: web容器和servlet容器的区别是什么: 在springMVC中的web.xml是什么时候加载到tomcat中的: tomcat是怎么加载我们的web服务的: tomcat是怎么实现的热部署: 一个http请求是怎么被tomcat监听到的,会有哪些处理: 为什么请求可以有需要通过nginx的,也可以不需要nginx的直接请求到tomcat上? -- 如果你想知道答案,那么接下来的文章会告诉你. 1.2 基本姿

tomcat源码分析-Bootstrap操作Catalina

1.前言 1.1 问题思考 在阅读tomcat源码前,我们一般都会有如下几个疑问: web容器和servlet容器的区别是什么: 在springMVC中的web.xml是什么时候加载到tomcat中的: tomcat是怎么加载我们的web服务的: tomcat是怎么实现的热部署: 一个http请求是怎么被tomcat监听到的,会有哪些处理: 为什么请求可以有需要通过nginx的,也可以不需要nginx的直接请求到tomcat上? -- 如果你想知道答案,那么接下来的文章会告诉你. 1.2 基本姿