ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Tomcat源码分析--类加载器

2022-08-21 10:31:23  阅读:131  来源: 互联网

标签: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,加载
来源: https://www.cnblogs.com/yishi-san/p/16609553.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有