首页 > 编程语言 >Tomcat源码分析--类加载器

Tomcat源码分析--类加载器

时间:2022-08-21 10:33:23浏览次数:59  
标签:Tomcat -- 源码 new null 方法 Class 加载

Tomcat类加载器结构

上图是Tomcat文档中所展示的Tomcat类加载结构。在这个结构中Bootstartap和System的类加载器由java虚拟机实现。common类加载器由Tomcat容器实现,它对 Tomcat 内部类和所有 Web 应用程序都是可见的。此类加载器搜索的位置$CATALINA_BASE/conf/catalina.properties 中的common.loader属性定义。在catalina.properties文件也定义了server.loader和shared.loader属性,这两个属性分别由Server和Shared两个类加载器加载。

接下来让我们看一下Tomcat如何实现common类加载器。首先我们需要找到Bootstrap类的main方法。在main方中有一段代码如下,这段代码的大意是先判断Bootstrap是否为null,不为null,直接将Catalina ClassLoader设置到当前线程,用于加载服务器相关类,为null则进入bootstrap的init方法。

......
synchronized (daemonLock) {
            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;
            } 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);
            }
        }
......

init方法会调用initClassLoaders()方法,在该方法中会调用createClassLoader方法创建commonLoader、catalinaLoader、sharedLoader三种类加载器。与上文中所介绍的三种类加载器一一对应。

 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);
        }
    }

createClassLoader方法终会调用如下代码,通过这段代码可以知到commonLoader是一个URLClassLoader。

public static ClassLoader createClassLoader(List<Repository> repositories,
                                                final ClassLoader parent)
        throws Exception {
......
return AccessController.doPrivileged(
                new PrivilegedAction<URLClassLoader>() {
                    @Override
                    public URLClassLoader run() {
                        if (parent == null)
                            return new URLClassLoader(array);
                        else
                            return new URLClassLoader(array, parent);
                    }
                });
}

Common|Catalina|Shared的使用

在Bootstartap中的init方法中调用完initClassLoaders方法后,就开始了对类加载器的使用。Tomcat用catalinaLoader来加载Catalina类,这个类就是我们经常说的容器。加载完Catalina后会将sharedLoader作为参数传递给Catalina类,以便于后续给Webappx设置父加载器。

public void init() throws Exception {

        initClassLoaders();

        ......
         // 加载Catalina类
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();

        // 设置sharedLoad加载器   
        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;
    }

WebappX 加载器

上文介绍了common类加载器的创建和使用,那么Webappx类加载器又是如何被创建和使用的呢?

创建WebappX 加载器

在看Tomcat源码是如何创建webappx类加载器之前,让我们来做一个实验。假设我们已经编译好了一个全限定名为com.example.WebAppClassLoader.class文件,那么应该如何来加载这个类文件呢?一种方法是自定义一个类加载器。

public class MyClassLoader extends URLClassLoader {
    public MyClassLoader() {
        super(new URL[0]);
    }

    @Override
    protected Class<?> findClass(String name) {
        String myPath = "file:///D:/www/tomact-test-war/tomcat-test-case008/src/main/webapp/WEB-INF/" + name.replace(".","/") + ".class";
        byte[] cLassBytes = null;
        Path path = null;
        try {
            path = Paths.get(new URI(myPath));
            cLassBytes = Files.readAllBytes(path);
        } catch (IOException | URISyntaxException e) {
            e.printStackTrace();
        }
        Class clazz = defineClass(name, cLassBytes, 0, cLassBytes.length);
        return clazz;
    }
}

创建一个名为MyClassLoader的类并继承URLClassLoader类,重写findClass方法,一个简单的类加载器就创建完成了。

public static void main(String[] args) throws Exception {
        MyClassLoader cl = new MyClassLoader();
        Class<?> wacl = cl.findClass("com.example.WebAppClassLoader");
        try {
            Object obj = wacl.newInstance();
            Method method = wacl.getMethod("test_1");
            method.invoke(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
package com.example;
public class WebAppClassLoader {
    public WebAppClassLoader() {
    }
    public void test_1() {
        System.out.println("自定类加载器");
    }
}

在main方法中创建MyClassLoader对象,并调用findClass方法,至此就将一个class文件加载到了虚拟机中。

好了,实验做完后,让我们回过头来看看Tomcat是如何创建WebappX类加载器的。

在StandardContext的startInternal方法中有这样一段代码

if (getLoader() == null) {
            WebappLoader webappLoader = new WebappLoader();
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }

它会创建WebappLoader对象,并通过setLoader(webappLoader)赋值到一个实例变量中,然后会调用WebappLoader的start方法:

......
classLoader = createClassLoader();
            classLoader.setResources(context.getResources());
            classLoader.setDelegate(this.delegate);
......

进入createClassLoader方法:

private WebappClassLoaderBase createClassLoader()
        throws Exception {

        Class<?> clazz = Class.forName(loaderClass);
        WebappClassLoaderBase classLoader = null;

        if (parentClassLoader == null) {
            parentClassLoader = context.getParentClassLoader();
        } else {
            context.setParentClassLoader(parentClassLoader);
        }
        Class<?>[] argTypes = { ClassLoader.class };
        Object[] args = { parentClassLoader };
        Constructor<?> constr = clazz.getConstructor(argTypes);
        classLoader = (WebappClassLoaderBase) constr.newInstance(args);

        return classLoader;
    }

该方法会实例化一个ParallelWebappClassLoader实例,并且传递了sharedLoader作为其父亲加载器。

ParallelWebappClassLoader继承了WebappClassLoaderBase抽象类,WebappClassLoaderBase继承了URLClassLoader。在WebappClassLoaderBase类中重写了findClass方法。至此WebappX类加载器 就创建完成了。

那Webappx类加载器又是被如何使用的呢?

还记得在Tomcat动态部署一章介绍的那个webConfig方法吗?这个方法非常复杂。在这个方法的第四步中会调用populateJavaClassCache方法

private void populateJavaClassCache(String className,
            Map<String,JavaClassCacheEntry> javaClassCache) {
    ......
         try (InputStream is = context.getLoader().getClassLoader().getResourceAsStream(name)) {
                if (is == null) {
                    return;
                }
                ClassParser parser = new ClassParser(is);
                JavaClass clazz = parser.parse();
                populateJavaClassCache(clazz.getClassName(), clazz, javaClassCache);
            } catch (ClassFormatException | IOException e) {
                log.debug(sm.getString("contextConfig.invalidSciHandlesTypes",
                        className), e);
            }
    ......
 }

现在总结如下: 在Tomcat存在common、cataina、shared三个公共的classloader,默认情况下,这三个classloader其实是同一个,都是common classloader,而针对每个webapp,也就是context(对应代码中的StandardContext类),都有自己的WebappClassLoader实例来加载每个应用自己的类,该类加载的父类加载器就是是Shared ClassLoader。这样前面关于tomcat的类加载层次应该就清楚起来了。

delegate属性与双亲委派

在context.xml文件中可以配置delegate属性,以用来控制Webappx类加载器的类加载机制。delegate属性默认是false。

当delegate为true时webappx的类加载顺序如下:

  • JVM 的引导类
  • 系统类加载器类
  • 通用类加载器类
  • /WEB-INF/ Web 应用程序的类
  • /WEB-INF/lib/*.jar您的 Web 应用程序

当delegate为false时webappx的类加载顺序如下:

  • JVM 的引导类
  • /WEB-INF/ Web 应用程序的类
  • /WEB-INF/lib/*.jar您的 Web 应用程序
  • 系统类加载器类(如上所述)
  • 通用类加载器类(如上所述)

让我再来简单回忆一下JVM的双亲委派模型,在JVM中一个类的加载首先使用其父类加载器去加载,如果加载不到在使用自身的加载器去加载。

 

 

我们以Tomcat的类加载器结构为例,当delegate属性是true时,加载一个自定义servlet是从根加载器,然后是系统类加载器一步步找下来的,这一过程与JVM的双亲委派模型是一致的。但是当delegate为false时却不然,当delegate为fasle时首先依旧从根加载器加载类文件,但是第二步是从webappx类加载器中加载类文件,然后是系统类加载器,最后才是通用类加载器,这与标准的JVM模型并不一致,我们也可以说此时Tomcat打破了双亲委派模型。

Tomcat默认打破了双亲委派,这样的好处之一是当我们在Tomcat中部署多个应用时,即使这些应用程序依赖同一个第三方类库,虽然其版本不同但并不会相互影响。

 

标签:Tomcat,--,源码,new,null,方法,Class,加载
From: https://www.cnblogs.com/yishi-san/p/16609553.html

相关文章

  • apue02 - UNIX标准与实现
    C语言标准头文件UNIX标准头文件 数据类型的限制:<limits.h>基本系统数据类型<sys/types.h> ......
  • AJAX_概念和AJAX原生js方式
    AJAX_概念概念:ASynchronousJavaScriptAndXML异步的JavaScript和XML异步和同步:客户端和服务器端相互通信的基础上客户端必须等待服务器端的响应在等待期间客户端......
  • 多线程.总结
    packageoop.dxcgaoji;importcom.sun.org.apache.xpath.internal.functions.FuncTrue;importjava.util.concurrent.Callable;importjava.util.concurrent.ExecutionExce......
  • mysql-运算符
    1.算数运算符2.比较运算符安全等于运算符<=>可以用来对NULL进行判断,左右两值均为NULL则结果为13.非符号运算符(关键字运算符)ISNULL判断值,字符串或表达式是否为......
  • 介词
    1.in在...里面(具体/抽象)范围,颜色at:点概念,确切位置/概念2.on:开,在...上(具体/抽象),关于,主题,持续off:关,不在原处(位置)3.from:起点,来源,远离to;终点,......
  • JSON_概念和JSON_语法_定义
    JSON:概念:JavaScriptObjectNotationJavaScript对象表示法Personperson=newPerson();person.setName("张三");person.setAge(23);person.setGender("男");v......
  • JS 的继承有几种方式 ?是怎么实现的?
    js继承的目的是重复利用另一个对象的属性和方法原型链继承让一个构造函数A的原型是另一个构造函数B的实例对象;那么A构造函数new出来的实例就拥有B的属性和方法优点:父......
  • JSON解析器的Jackson_java对象转json_List&Map和JSON解析器的Jackson_json转Java对象
    JSON解析器的Jackson_java对象转json_List&Map复杂java对象转换1.list:数组@Testpublicvoidtest3()throwsException{//创建Person对象......
  • Java概述
    从项目到代码找工作前的整个学习体系(学会这些东西去解决问题,不单单去学这些东西)JavaSE知识图Java语言跨平台原理Java语言特点完全面向对象:Java支持封装,继承,多态,面......
  • 设计模式09 - 设计模式 - 装饰器模式(结构型)
    一、定义装饰器(Decorator)模式:指不改变现有对象结构的情况下,动态地给该对象增加额外功能。它是继承方式的一种替代方案。这种模式创建了一个装饰类,用来包装原有......