首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

tomcat源码分析系列之起步

2012-07-02 
tomcat源码分析系列之启动?????? 对于tomcat这么一个庞大的东西,要去分析它,一开始有种不知从和入手好,原

tomcat源码分析系列之启动

?????? 对于tomcat这么一个庞大的东西,要去分析它,一开始有种不知从和入手好,原因如下:可切入的点太多了,随便捡个角落都能说半天,但是任何一个东西都不是孤立存在的,要说清楚一个东西,必定会引入其他的东西,这样错综复杂,要把一个点介绍完整,终究不是件容易的事情。

??????? 既然无从入手,我们就按平时的使用过程来介绍吧,从启动开始,一步一步的展开我们的旅程。当我们从启动开始,逐步往下行进,最后回到停止时,我们的旅程也就结束了。

??????? 好吧,开始吧!

??????? 我们一般都是通过运行tomcat/bin下的startup.bat(或者startup.sh)来启动tomcat的,startup.bat脚本的执行不是我们分析的重点,在此简单带过。在startup里会调用catalina脚本,在catalina里运行bootstrap.jar,而bootstrap.jar的启动类是org.apache.catalina.startup.Bootstrap,这是一个普通的java类,我们看看它的main方法:

public static void main(String args[]) {        if (daemon == null) {            // Don't set daemon until init() has completed            Bootstrap bootstrap = new Bootstrap();            try {                bootstrap.init();            } catch (Throwable t) {                handleThrowable(t);                t.printStackTrace();                return;            }            daemon = bootstrap;        }        try {            String command = "start";            if (args.length > 0) {                command = args[args.length - 1];            }            if (command.equals("startd")) {                args[args.length - 1] = "start";                daemon.load(args);                daemon.start();            } else if (command.equals("stopd")) {                args[args.length - 1] = "stop";                daemon.stop();            } else if (command.equals("start")) {                daemon.setAwait(true);                daemon.load(args);                daemon.start();            } else if (command.equals("stop")) {                daemon.stopServer(args);            } else if (command.equals("configtest")) {                daemon.load(args);                if (null==daemon.getServer()) {                    System.exit(1);                }                System.exit(0);            } else {                log.warn("Bootstrap: command "" + command + "" does not exist.");            }        } catch (Throwable t) {            handleThrowable(t);            // Unwrap the Exception for clearer error reporting            if (t instanceof InvocationTargetException &&                    t.getCause() != null) {                t = t.getCause();            }            t.printStackTrace();            System.exit(1);        }    }

??? 第一次运行生成一个BootStrap的实例,调用bootstrap.init();我们看看这个init方法主要做了一下几件事:

??? 1)初始化classLoader

??? 2)将catalinaClassLoader设置为当前上下文classLoader和安全了classLoader

??? 3)生成一个org.apache.catalina.startup.Catalina的实例

??? 4)设置Catalina的实例的parentClassLoader为java.lang.ClassLoader

    /**     * Initialize daemon.     */    public void init() throws Exception {              // Set Catalina path        setCatalinaHome();//如果没有设置catalina.home,就将当前目录复制给catalina.home        setCatalinaBase();//如果没有设置catalina.base,就将当前目录复制给catalina.base        /*          *初始化了3个classLoader:commonLoader,catalinaLoader,sharedLoader         */        initClassLoaders();        Thread.currentThread().setContextClassLoader(catalinaLoader);        SecurityClassLoad.securityClassLoad(catalinaLoader);        // Load our startup class and call its process() method        if (log.isDebugEnabled())            log.debug("Loading startup class");        Class<?> startupClass =            catalinaLoader.loadClass            ("org.apache.catalina.startup.Catalina");        Object startupInstance = startupClass.newInstance();        // Set the shared extensions class loader        if (log.isDebugEnabled())            log.debug("Setting startup class properties");        String methodName = "setParentClassLoader";        Class<?> paramTypes[] = new Class[1];        paramTypes[0] = Class.forName("java.lang.ClassLoader");        Object paramValues[] = new Object[1];        paramValues[0] = sharedLoader;        Method method =            startupInstance.getClass().getMethod(methodName, paramTypes);        method.invoke(startupInstance, paramValues);        catalinaDaemon = startupInstance;    }

? ?? 我们有必要看一下initClassLoaders();方法:

private void initClassLoaders() {        try {            commonLoader = createClassLoader("common", null);            if( commonLoader == null ) {                // no config file, default to this loader - we might be in a 'single' env.                commonLoader=this.getClass().getClassLoader();            }            catalinaLoader = createClassLoader("server", commonLoader);            sharedLoader = createClassLoader("shared", commonLoader);        } catch (Throwable t) {            handleThrowable(t);            log.error("Class loader creation threw exception", t);            System.exit(1);        }    }private ClassLoader createClassLoader(String name, ClassLoader parent)        throws Exception {        String value = CatalinaProperties.getProperty(name + ".loader");        if ((value == null) || (value.equals("")))            return parent;        value = replace(value);        List<Repository> repositories = new ArrayList<Repository>();        StringTokenizer tokenizer = new StringTokenizer(value, ",");        while (tokenizer.hasMoreElements()) {            String repository = tokenizer.nextToken().trim();            if (repository.length() == 0) {                continue;            }            // Check for a JAR URL repository            try {                @SuppressWarnings("unused")                URL url = new URL(repository);                repositories.add(                        new Repository(repository, RepositoryType.URL));                continue;            } catch (MalformedURLException e) {                // Ignore            }            // Local repository            if (repository.endsWith("*.jar")) {                repository = repository.substring                    (0, repository.length() - "*.jar".length());                repositories.add(                        new Repository(repository, RepositoryType.GLOB));            } else if (repository.endsWith(".jar")) {                repositories.add(                        new Repository(repository, RepositoryType.JAR));            } else {                repositories.add(                        new Repository(repository, RepositoryType.DIR));            }        }        ClassLoader classLoader = ClassLoaderFactory.createClassLoader            (repositories, parent);        // Retrieving MBean server        MBeanServer mBeanServer = null;        if (MBeanServerFactory.findMBeanServer(null).size() > 0) {            mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);        } else {            mBeanServer = ManagementFactory.getPlatformMBeanServer();        }        // Register the server classloader        ObjectName objectName =            new ObjectName("Catalina:type=ServerClassLoader,name=" + name);        mBeanServer.registerMBean(classLoader, objectName);        return classLoader;    }

??? 实际上initClassLoaders构造了三个classLoader:commonClassLoader,catalinaClassLoader,sharedClassLoder,他们各自负责加载的类在配置文件/conf/catalina.properties文件里定义了。

common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar?

commonClassLoader主要负责一些基础依赖类和jar包的加载

server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
?

catalinaClassLoader主要负责tomcat自身的一些类和jar加载

shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar
?

sharedClassLoder主要负责一些共享类和jar包的加载

commonClassLoader作为catalinaClassLoader和sharedClassLoder的父加载类。上面说过catalinaClassLoader作为当前上下文classLoader也就是说,后续没有什么意外的话,运用程序的类都是用catalinaClassLoader来加载的。

??????? 当做完这几个初始化动作后,main方法里接着就来解析命令了:

?

String command = "start";            if (args.length > 0) {                command = args[args.length - 1];            }            if (command.equals("startd")) {                args[args.length - 1] = "start";                daemon.load(args);                daemon.start();            } else if (command.equals("stopd")) {                args[args.length - 1] = "stop";                daemon.stop();            } else if (command.equals("start")) {                daemon.setAwait(true);                daemon.load(args);                daemon.start();            } else if (command.equals("stop")) {                daemon.stopServer(args);            } else if (command.equals("configtest")) {                daemon.load(args);                if (null==daemon.getServer()) {                    System.exit(1);                }                System.exit(0);            } else {                log.warn("Bootstrap: command "" + command + "" does not exist.");            }

??startd命令执行动作:daemon.load(args),实际上就是执行Catalina的load方法,代码如下:

/**     * Load daemon.     */    private void load(String[] arguments)        throws Exception {        // Call the load() method        String methodName = "load";        Object param[];        Class<?> paramTypes[];        if (arguments==null || arguments.length==0) {            paramTypes = null;            param = null;        } else {            paramTypes = new Class[1];            paramTypes[0] = arguments.getClass();            param = new Object[1];            param[0] = arguments;        }        Method method =            catalinaDaemon.getClass().getMethod(methodName, paramTypes);        if (log.isDebugEnabled())            log.debug("Calling startup class " + method);        method.invoke(catalinaDaemon, param);    }

??? 我们看看Catalina的load做了什么工作:

?

public void load() {        long t1 = System.nanoTime();        initDirs();//初始化几个目录        // Before digester - it may be needed        initNaming();//初始化几个跟naming相关的变量        //Digester是干嘛的?它就是用来解析xml的,        //从其代码来看,个人感觉createStartDigester方法写得还是比较笨拙,不过这样的好处是代码一目了然        //好吧,就到这吧,不想太深入的介入 Digester,我们只要对它有个印象就可以了。        //在这里它主要定义了server.xml 文件的解析规则       ?// Create and execute our Digester        Digester digester = createStartDigester();        InputSource inputSource = null;        InputStream inputStream = null;        File file = null;        try {            file = configFile();            inputStream = new FileInputStream(file);            inputSource = new InputSource("file://" + file.getAbsolutePath());        } 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;        }        try {            inputSource.setByteStream(inputStream);            digester.push(this);            digester.parse(inputSource);            inputStream.close();        } catch (SAXParseException spe) {            log.warn("Catalina.start using " + getConfigFile() + ": " +                    spe.getMessage());            return;        } catch (Exception e) {            log.warn("Catalina.start using " + getConfigFile() + ": " , e);            return;        }        //到上面为止就是加载并解析了server.xml文件        getServer().setCatalina(this);        // Stream redirection        initStreams();        // 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");    }

?? 上面主要就是加载并解析了server.xml文件,初始化了server。

??? 我们看一下tomcat按什么规则去解析server.xml:

 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.setClassLoader(StandardServer.class.getClassLoader());        // 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/"));        digester.addRuleSet(new ClusterRuleSet("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));        digester.addRuleSet(new ClusterRuleSet("Server/Service/Engine/Cluster/"));        long t2=System.currentTimeMillis();        if (log.isDebugEnabled())            log.debug("Digester for server.xml created " + ( t2-t1 ));        return (digester);    }
?

?? 接下来我们回到main函数里的daemon.start();Bootstrap的方法:

?

public void start()        throws Exception {        if( catalinaDaemon==null ) init();        Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);        method.invoke(catalinaDaemon, (Object [])null);    }

?? 我们看到,实际上就是调用了Catalina的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.error("Catalina.start: ", e);        }        long t2 = System.nanoTime();        if(log.isInfoEnabled())            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");        try {            // Register shutdown hook            if (useShutdownHook) {                if (shutdownHook == null) {                    shutdownHook = new CatalinaShutdownHook();                }                Runtime.getRuntime().addShutdownHook(shutdownHook);                // If JULI is being used, disable JULI's shutdown hook since                // shutdown hooks run in parallel and log messages may be lost                // if JULI's hook completes before the CatalinaShutdownHook()                LogManager logManager = LogManager.getLogManager();                if (logManager instanceof ClassLoaderLogManager) {                    ((ClassLoaderLogManager) logManager).setUseShutdownHook(                            false);                }            }        } catch (Throwable t) {            ExceptionUtils.handleThrowable(t);            // This will fail on JDK 1.2. Ignoring, as Tomcat can run            // fine without the shutdown hook.        }        if (await) {            await();            stop();        }    }

?? 在start方法里,调用了LifecycleBase的start()方法,然后构造了一个CatalinaShutdownHook实例,在JVM退出时关闭tomcat。CatalinaShutdownHook是Catalina的一个内部类,它的实现很简单:

protected class CatalinaShutdownHook extends Thread {        @Override        public void run() {            try {                if (getServer() != null) {                    Catalina.this.stop();                }            } catch (Throwable ex) {                ExceptionUtils.handleThrowable(ex);                log.error(sm.getString("catalina.shutdownHookFail"), ex);            } finally {                // If JULI is used, shut JULI down *after* the server shuts down                // so log messages aren't lost                LogManager logManager = LogManager.getLogManager();                if (logManager instanceof ClassLoaderLogManager) {                    ((ClassLoaderLogManager) logManager).shutdown();                }            }        }    }

??? 就是关闭server。再回去看下LifecycleBase的start()方法:

?

 public final synchronized void start() throws LifecycleException {                if (LifecycleState.STARTING_PREP.equals(state) ||                LifecycleState.STARTING.equals(state) ||                LifecycleState.STARTED.equals(state)) {                        if (log.isDebugEnabled()) {                Exception e = new LifecycleException();                log.debug(sm.getString("lifecycleBase.alreadyStarted",                        toString()), e);            } else if (log.isInfoEnabled()) {                log.info(sm.getString("lifecycleBase.alreadyStarted",                        toString()));            }                        return;        }                if (state.equals(LifecycleState.NEW)) {            init();        } else if (!state.equals(LifecycleState.INITIALIZED) &&                !state.equals(LifecycleState.STOPPED)) {            invalidTransition(Lifecycle.BEFORE_START_EVENT);        }        setStateInternal(LifecycleState.STARTING_PREP, null, false);        try {            startInternal();        } catch (LifecycleException e) {            setStateInternal(LifecycleState.FAILED, null, false);            throw e;        }        if (state.equals(LifecycleState.FAILED) ||                state.equals(LifecycleState.MUST_STOP)) {            stop();        } else {            // Shouldn't be necessary but acts as a check that sub-classes are            // doing what they are supposed to.            if (!state.equals(LifecycleState.STARTING)) {                invalidTransition(Lifecycle.AFTER_START_EVENT);            }                        setStateInternal(LifecycleState.STARTED, null, false);        }    }

??? 这个方法是同步的(synchronized),第一次启动的时候,state的值为NEW,会调用init()方法:

public final synchronized void init() throws LifecycleException {        if (!state.equals(LifecycleState.NEW)) {            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);        }        setStateInternal(LifecycleState.INITIALIZING, null, false);        try {            initInternal();        } catch (LifecycleException e) {            setStateInternal(LifecycleState.FAILED, null, false);            throw e;        }        setStateInternal(LifecycleState.INITIALIZED, null, false);    }

? 这个方法主要是注册了Lifecycle的事件监听。

?

?? 到这里一个server就启动起来了,总结一下,tomcat的启动主要就是设置变量、生成3个类加载器并加载各自负责的类,加载解析配置文件,初始化server、注册监听事件。

?

?

热点排行