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

来源:互联网 发布:app个性化推荐算法 编辑:程序博客网 时间:2024/05/17 23:26

Bootstrap

通过Tomcat的启动脚本可以看到启动的入口是在Bootstrap,来看下Bootstrapmain方法,

    /**     * 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 (daemon == null) {            ////////////////////////////////////////            // 1. Bootstrap初始化            ////////////////////////////////////////            // 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;        } else {            // When running as a service the call to stop will be on a new            // thread so make sure the correct class loader is used to prevent            // a range of class not found exceptions.            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);        }        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);                ////////////////////////////////////////                // 2. org.apache.catalina.startup.Catalina#load                ////////////////////////////////////////                daemon.load(args);                ////////////////////////////////////////                // 3. org.apache.catalina.startup.Catalina#start                ////////////////////////////////////////                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) {            // Unwrap the Exception for clearer error reporting            if (t instanceof InvocationTargetException &&                    t.getCause() != null) {                t = t.getCause();            }            handleThrowable(t);            t.printStackTrace();            System.exit(1);        }    }

来看下init方法,

    /**     * Initialize daemon.     */    public void init()        throws Exception    {        ////////////////////////////////////////        // 1. 设置catalina.home与catalina.base        ////////////////////////////////////////        // Set Catalina path        setCatalinaHome();        setCatalinaBase();        ////////////////////////////////////////        // 2. classloader初始化        ////////////////////////////////////////        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");        ////////////////////////////////////////        // 3. 使用catalinaLoader加载Catalina        ////////////////////////////////////////        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");        ////////////////////////////////////////        // 4. 将Catalina的parentClassLoader设置为sharedLoader        ////////////////////////////////////////        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;    }

可以看到需要先初始化ClassLoader,然后才去加载最重要的服务器类,也就是org.apache.catalina.startup.Catalina。有三个ClassLoader,

    protected ClassLoader commonLoader = null;    protected ClassLoader catalinaLoader = null;    protected ClassLoader sharedLoader = null;

下面来看下ClassLoader初始化的代码,

    private void initClassLoaders() {        try {            ////////////////////////////////////////            // commonLoader的parent置为null            ////////////////////////////////////////            commonLoader = createClassLoader("common", null);            ////////////////////////////////////////            // 如果没有配置common.loader,则commonLoader就是AppClassLoader            ////////////////////////////////////////            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 {        ////////////////////////////////////////        // 从catalina.base/conf/catalina.properties读取配置        ////////////////////////////////////////        String value = CatalinaProperties.getProperty(name + ".loader");        ////////////////////////////////////////        // 默认情况下catalinaLoader与sharedLoader配置为空        // 所以三个ClassLoader其实是同一个引用        ////////////////////////////////////////        if ((value == null) || (value.equals("")))            return parent;        value = replace(value);        ////////////////////////////////////////        // 添加配置的classpath        ////////////////////////////////////////        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));            }        }        ////////////////////////////////////////        // 差不多是new URLClassLoader        ////////////////////////////////////////        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;    }

ClassLoader的配置放在catalina.properties

### List of comma-separated paths defining the contents of the "common"# classloader. Prefixes should be used to define what is the repository type.# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.# If left as blank,the JVM system loader will be used as Catalina's "common"# loader.# Examples:#     "foo": Add this folder as a class repository#     "foo/*.jar": Add all the JARs of the specified folder as class#                  repositories#     "foo/bar.jar": Add bar.jar as a class repositorycommon.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar## List of comma-separated paths defining the contents of the "server"# classloader. Prefixes should be used to define what is the repository type.# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.# If left as blank, the "common" loader will be used as Catalina's "server"# loader.# Examples:#     "foo": Add this folder as a class repository#     "foo/*.jar": Add all the JARs of the specified folder as class#                  repositories#     "foo/bar.jar": Add bar.jar as a class repositoryserver.loader=## List of comma-separated paths defining the contents of the "shared"# classloader. Prefixes should be used to define what is the repository type.# Path may be relative to the CATALINA_BASE path or absolute. If left as blank,# the "common" loader will be used as Catalina's "shared" loader.# Examples:#     "foo": Add this folder as a class repository#     "foo/*.jar": Add all the JARs of the specified folder as class#                  repositories#     "foo/bar.jar": Add bar.jar as a class repository# Please note that for single jars, e.g. bar.jar, you need the URL form# starting with file:.shared.loader=

可以看到只有commonLoader才有配置,所以createClassLoader("server", commonLoader);createClassLoader("shared", commonLoader);返回的都会是commonLoader

还有一点值得注意的是,commonLoaderparentnull,也就是说当commonLoader进行类加载要委托给父类加载器时,将会绕过AppClassLoaderExtClassLoader,直接交给BootstrapClassLoader(不明白的同学可以参考这里)。

Catalina

使用catalinaLoader加载了Catalina之后,会调用Catalina#load方法,

    public void load() {        long t1 = System.nanoTime();        ////////////////////////////////////////        // 设置catalina.home与catalina.base        ////////////////////////////////////////        initDirs();        // Before digester - it may be needed        initNaming();        ////////////////////////////////////////        // 创建server.xml的解析器        ////////////////////////////////////////        // Create and execute our Digester        Digester digester = createStartDigester();        ////////////////////////////////////////        // 读取catalina.base/conf/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;        }        ////////////////////////////////////////        // 解析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();        ////////////////////////////////////////        // org.apache.catalina.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");        }    }

load方法做的事情就是通过server.xml的配置去初始化Server。随后的Catalina#start其实就是去启动Server

    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();        ////////////////////////////////////////        // 启动org.apache.catalina.Server        ////////////////////////////////////////        // 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;        }        long t2 = System.nanoTime();        if(log.isInfoEnabled()) {            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");        }        // 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);            }        }        if (await) {            await();            stop();        }    }

alright,今天就先到这吧。

参考资料

  • https://tomcat.apache.org/tomcat-7.0-doc/building.html
  • https://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html
  • https://tomcat.apache.org/tomcat-7.0-doc/architecture/overview.html
  • https://tomcat.apache.org/tomcat-7.0-doc/architecture/startup.html
  • https://tomcat.apache.org/tomcat-7.0-doc/architecture/requestProcess.html
0 0
原创粉丝点击