Tomcat9源代码分析(二)-初始化

来源:互联网 发布:python 绝对路径 编辑:程序博客网 时间:2024/06/06 05:06

Tomcat9源代码分析(二)-初始化


回顾

  前面一篇文章提到,当Tomecat启动(startup.sh)时,会调用org.apache.catalina.startup.Bootstrap.main()方法。

正文

  通过《Tomcat9源代码分析(一)-源码切入点》中的Tomcate9源代码下载地址我们获取到了apache-tomcat-9.0.2-src.zip文件,直接解压即可得到apache-tomcat-9.0.2源代码。用IDEA打开后如下图
image
  什么都不说,咱们直奔主题来看看Bootstrap.main()做了些什么。

代码2-1

org.apache.catalina.startup.Bootstrap#main

    /**     * 通过提供的启动Tomcat时的主要方法和入口点     *     * @param args命令行参数进行处理     */    public static void main(String args[]) {        if (daemon == null) {            // 创建Bootstrap对象            Bootstrap bootstrap = new Bootstrap();            try {                //bootstrap初始化                bootstrap.init();            } catch (Throwable t) {                handleThrowable(t);                t.printStackTrace();                return;            }            daemon = bootstrap;        } else {            //重置当前线程类加载器            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";                //Bootstrap加载信息                //内部操作:用反射调用catalinaDaemon的load方法加载和解析server.xml配置文件                daemon.load(args);                //Bootstrap启动                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) {            // Unwrap the Exception for clearer error reporting            if (t instanceof InvocationTargetException &&                    t.getCause() != null) {                t = t.getCause();            }            handleThrowable(t);            t.printStackTrace();            System.exit(1);        }    }

  通过代码,我们可以看到该方法的执行情况

  • 验证当前Bootstrap(引导程序)对象是否存在
  • 不存在则创建Bootstrap对象并初始化bootstrap.init();
  • 存在则将当前的Bootstrap对象的类加载器赋给当前线程的类加载器
  • 根据不同参数执行不同的操作

  本章讲Tomcat服务启动我们直接看start做了些什么。

  • Bootstrap加载基础信息(用反射调用catalinaDaemon的load方法加载和解析server.xml配置文件,往下看)
  • Bootstrap启动

  我们逐一来了解各步骤做了些什么。(异常情况本系列文章暂不做探究)我们先来看看初始化(==bootstrap.init();==)做了些什么。

代码2-2

org.apache.catalina.startup.Bootstrap#init()

    /**     * 初始化程序     *     * @throws 抛出异常致命初始化错误     */    public void init() throws Exception {        //初始化类加载器        initClassLoaders();        //设置Tomcat私有加载器到当前线程        Thread.currentThread().setContextClassLoader(catalinaLoader);        //加载Tomcat容器所需的class        SecurityClassLoad.securityClassLoad(catalinaLoader);        // 加载我们的启动类并调用它的process()方法        if (log.isDebugEnabled())            log.debug("Loading startup class");        //通过反射找到启动类Catalina        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");        Object startupInstance = startupClass.getConstructor().newInstance();        // 设置共享类加载器        if (log.isDebugEnabled())            log.debug("Setting startup class properties");        //反射找到父类加载器        String methodName = "setParentClassLoader";        Class<?> paramTypes[] = new Class[1];        //反射找到类加载器ClassLoader        paramTypes[0] = Class.forName("java.lang.ClassLoader");        Object paramValues[] = new Object[1];        paramValues[0] = sharedLoader;        Method method =                startupInstance.getClass().getMethod(methodName, paramTypes);        //调用ClassLoader.setParentClassLoader方法(执行加载器)        method.invoke(startupInstance, paramValues);        catalinaDaemon = startupInstance;    }

  通过代码2-2我们可以看到引导程序(Bootstrap)初始化的步骤:
- 初始化类加载器
- 将类加载器设置到当前线程
- 加载Tomcat容器所需class
- 通过反射加载Tomcat启动类Catalina并调用process()类

  在看初始化类加载的实现之前,我们需要先了解Java类加载器都有哪些,分别是做什么用的。

  Java虚拟机规范中提到的主要类加载器;

  • Bootstrap Loader:加载lib目录下或者System.getProperty(“sun.boot.class.path”)、或者-XBootclasspath所指定的路径或jar。
  • Extended Loader:加载lib\ext目录下或者System.getProperty(“java.ext.dirs”) 所指定的 路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld。
  • AppClass Loader:加载System.getProperty(“java.class.path”)所指定的 路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld。

  根据java虚拟机的双亲委派模式的原则,类加载器在加载一个类时,首先交给父类加载器加载,层层往上直到Bootstrap Loader。也就是一个类最先由Bootstrap Loader加载,如果没有加载到,则交给下一层的类加载器加载,如果没有加载到,则依次层层往下,直到最下层的类加载器。这也就是说,凡是能通过父一级类加载器加载到的类,对于子类也是可见的。因此可以利用双亲委派模式的特性,使用类加载器对不同路径下的jar包或者类进行环境隔离。

然后用一张图片来展示Tomcat的类加载体系:
image
  结合之前对双亲委派模式的类加载过程的描述,对上图所示类加载体系进行介绍:

  • ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器需要继承实现
  • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;
代码2-3

org.apache.catalina.startup.Bootstrap#initClassLoaders

    /**     * 初始化类加载器     */    private void initClassLoaders() {        try {            //创建Tomcat基本类加载器            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();            }            //创建Tomcat容器私有类加载器            catalinaLoader = createClassLoader("server", commonLoader);            //创建WebApp共享类加载器            sharedLoader = createClassLoader("shared", commonLoader);        } catch (Throwable t) {            handleThrowable(t);            log.error("Class loader creation threw exception", t);            System.exit(1);        }    }

  ==代码2-3==我们了解initClassLoaders顺序创建各个类加载器。

代码2-4

org.apache.catalina.startup.Bootstrap#createClassLoader

    /**     * 创建类加载器     * @param name 加载器名称     * @param parent 加载器     * @return 加载器对象     * @throws Exception 议持仓     */    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<>();        //获取资源配置文件路径        String[] repositoryPaths = getPaths(value);        for (String repository : repositoryPaths) {            // 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            }            // 加载资源文件            if (repository.endsWith("*.jar")) {                //全局资源文件(*.jar)                repository = repository.substring                        (0, repository.length() - "*.jar".length());                repositories.add(                        new Repository(repository, RepositoryType.GLOB));            } else if (repository.endsWith(".jar")) {                //JAR包                repositories.add(                        new Repository(repository, RepositoryType.JAR));            } else {                //文件                repositories.add(                        new Repository(repository, RepositoryType.DIR));            }        }        //创建加载器对象        return ClassLoaderFactory.createClassLoader(repositories, parent);    }

  ==代码2-4==则是获取创建类加载器所需参数(资源文件),调用==ClassLoaderFactory.createClassLoader==方法创建类加载器对象并返回。

代码2-5
  • org.apache.catalina.startup.ClassLoaderFactory#createClassLoader(List, ClassLoader)
    /**     * 基于配置(默认值和指定的目录路径)创建和返回一个新的类加载器     *     * @param 类目录、JAR文件、JAR目录的存储库列表,或者应该添加到存储库中的URL,类加载器     * @param 新类加载器对应的父类加载器     * @return 新类加载器     *     * @exception Exception if an error occurs constructing the class loader     */    public static ClassLoader createClassLoader(List<Repository> repositories,                                                final ClassLoader parent)        throws Exception {        if (log.isDebugEnabled())            log.debug("Creating new class loader");        // 为类加载器构建包路径        Set<URL> set = new LinkedHashSet<>();        if (repositories != null) {            //遍历资源仓库列表            for (Repository repository : repositories)  {                //如果资源类型为包路径                if (repository.getType() == RepositoryType.URL) {                    //根据路径构建类加载器包路径                    URL url = buildClassLoaderUrl(repository.getLocation());                    if (log.isDebugEnabled())                        log.debug("  Including URL " + url);                    set.add(url);                } else if (repository.getType() == RepositoryType.DIR) {//如果资源文件类型为文件                    File directory = new File(repository.getLocation());                    directory = directory.getCanonicalFile();                    //验证资源文件                    if (!validateFile(directory, RepositoryType.DIR)) {                        continue;                    }                    //根据文件构建类加载器包路径                    URL url = buildClassLoaderUrl(directory);                    if (log.isDebugEnabled())                        log.debug("  Including directory " + url);                    set.add(url);                } else if (repository.getType() == RepositoryType.JAR) {//如果资源文件路径为JAR包                    File file=new File(repository.getLocation());                    file = file.getCanonicalFile();                    //验证JAR包                    if (!validateFile(file, RepositoryType.JAR)) {                        continue;                    }                    //根据JAR包构建类加载器包路径                    URL url = buildClassLoaderUrl(file);                    if (log.isDebugEnabled())                        log.debug("  Including jar file " + url);                    set.add(url);                } else if (repository.getType() == RepositoryType.GLOB) {//如果资源文件路径为全局资源文件                    File directory=new File(repository.getLocation());                    directory = directory.getCanonicalFile();                    //验证文件夹                    if (!validateFile(directory, RepositoryType.GLOB)) {                        continue;                    }                    if (log.isDebugEnabled())                        log.debug("  Including directory glob "                            + directory.getAbsolutePath());                    String filenames[] = directory.list();                    if (filenames == null) {                        continue;                    }                    //循环构建类加载器包路径                    for (int j = 0; j < filenames.length; j++) {                        String filename = filenames[j].toLowerCase(Locale.ENGLISH);                        if (!filename.endsWith(".jar"))                            continue;                        File file = new File(directory, filenames[j]);                        file = file.getCanonicalFile();                        if (!validateFile(file, RepositoryType.JAR)) {                            continue;                        }                        if (log.isDebugEnabled())                            log.debug("    Including glob jar file "                                + file.getAbsolutePath());                        URL url = buildClassLoaderUrl(file);                        set.add(url);                    }                }            }        }        // 将构造的包放入类加载器中        final URL[] array = set.toArray(new URL[set.size()]);        if (log.isDebugEnabled())            for (int i = 0; i < array.length; i++) {                log.debug("  location " + i + " is " + array[i]);            }        //类加载器授权        return AccessController.doPrivileged(                new PrivilegedAction<URLClassLoader>() {                    @Override                    public URLClassLoader run() {                        if (parent == null)                            return new URLClassLoader(array);                        else                            return new URLClassLoader(array, parent);                    }                });    }

  ==代码2-5==可我们可以看出来,类加载器的创建即将资源文件添加到加载器中并授权,到此处我们可以想想,如果我们想Tomcat加载我们自己的类,是否可以在此处做功夫呢?理论可行,有兴趣的同学自己自己尝试下
  此处本人还有个疑问的地方,老版本的类加载器,创建完毕后会将创建的类加载器注册到JMX服务中,但在Tomcat9中此处不再注册,那移动到哪去了呢?有了解的同学欢迎帮忙解答。
  看到此处我们继续深入看看类加载器资源配置文件都写了些什么

代码2-6

conf/catalina.properties

# 多个服务资源文件路径使用“,”分隔# 加载器的前缀用来定义加载器类型(common,server,shared)# 路径不许是相对与catalina.base或catalina.home或绝对路径# “common.loader”如果不设置值则将作为Catalina服务的资源文件# “foo”:添加整个文件夹中的内容# "foo/*.jar" 添加文件夹中所有的.jar文件# "foo/bar.jar" 添加指定的jar文件# 设置值使用“...”的情况下catalina.base或catalina.base路径包含一个逗号,因为“”用于赋值,路径可能会不存在common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"server.loader=shared.loader=

  看到==代码2-6==我们会发现基本类加载器加载了Tomcat目录下lib文件夹下的所有文件
  通过==代码2-2==我们知道下一步操作为:加载Tomcat容器所需的class

代码2-7

org.apache.catalina.security.SecurityClassLoad#securityClassLoad(ClassLoader)

    /**     * 加载Tomcat容器所需的class     *     * @param loader                 加载器(CatalinaLoader)     * @param requireSecurityManager 空     * @throws Exception     */    static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager)            throws Exception {        if (requireSecurityManager && System.getSecurityManager() == null) {            return;        }        //加载Tomcat 核心class(org.apache.catalina.core)        loadCorePackage(loader);        loadCoyotePackage(loader);        //WebappClassLoade基础class(org.apache.catalina.loader)        loadLoaderPackage(loader);        loadRealmPackage(loader);        //Tomcat有关Servlet的class(org.apache.catalina.servlets)        loadServletsPackage(loader);        //Tomcat有关session的class(org.apache.catalina.session)        loadSessionPackage(loader);        //Tomcat工具类的class(org.apache.catalina.util)        loadUtilPackage(loader);        //Tomcat处理Cookie的        loadJavaxPackage(loader);        //Tomcat处理请求的class(org.apache.catalina.connector)        loadConnectorPackage(loader);        loadTomcatPackage(loader);    }

  到这里,我们Tomcat初始化就完成了。下一篇文件我们一起来看看初始化后Bootstrap.load做了些什么

原创粉丝点击