• tomcat类加载-源码解析


    上文:tomcat热加载、热部署-源码解析


    背景

        继上文,那么你可能跟我开始一样,tomcat的类加载与我们的java有什么区别?是一样的还是有哪些区别?其次tomcat项目怎么隔离?其三tomat如何打破双亲委派机制?

    相关基础

    java类如何加载的?

    这个问题建议阅读我以往的文章,有这个基础会更好了解如下。文章:

    类的加载时机

    jvm的类加载器(classloader)及类的加载过程

    eeac90b6c5033c73b3c2c06d6615f301.jpeg

    以前画的图,比较丑,别介意哈~

    打破双亲委派机制

    那么如何打破双亲委派机制,可以参考另一个文章:如何打破双亲委派机制?

    7374e9a020da7fec36c082b4f7d4815b.png

    看到这里你很明显清楚的知道tomcat其实就基于jdk的类加载机制进行重写Classloader,然后通过classloader进行重写findClass实现的。

    tomcat为什么要打破双亲委派机制?

        项目之间互相隔离:不同的项目,相同的路径起到互相隔离,方便多部署项目,保证项目之间互不影响(由于一个tomcat可能需要部署多套系统需要);

    共享java类库:不同的项目之间可以共享java的类库,不需要重装安装;

    支持热部署:新项目加载或部署不会影响正在运行的项目;

    tomcat打破双亲委派机制的实现源码

    f2aa1935090380b6681504d183e10623.png

    代码位置:org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String, boolean)

    1. @Override
    2. public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
    3.     //同步锁 这里是调用ClassLoader的方法获取
    4.     synchronized (getClassLoadingLock(name)) {
    5.         if (log.isDebugEnabled()) {
    6.             log.debug("loadClass(" + name + ", " + resolve + ")");
    7.         }
    8.         Class clazz = null;
    9.         // Log access to stopped class loader 检查当前的classloader是事已经停止,没有就跑出异常
    10.         checkStateForClassLoading(name);
    11.         // (0) Check our previously loaded local class cache
    12.         // 本地检查缓存中是否存在加载过了。这是指的是webappcloassloader
    13.         clazz = findLoadedClass0(name);
    14.         if (clazz != null) {
    15.             if (log.isDebugEnabled()) {
    16.                 log.debug(" Returning class from cache");
    17.             }
    18.             if (resolve) {
    19.                 resolveClass(clazz);
    20.             }
    21.             return clazz;
    22.         }
    23.         // (0.1) Check our previously loaded class cache
    24.         //从系统类加载器里面判断是否加载过了,如果是则进行返回 SystemClassLoader
    25.         clazz = findLoadedClass(name);
    26.         if (clazz != null) {
    27.             if (log.isDebugEnabled()) {
    28.                 log.debug(" Returning class from cache");
    29.             }
    30.             //如果为直,调用classloader本地方法进行加载类;
    31.             if (resolve) {
    32.                 resolveClass(clazz);
    33.             }
    34.             return clazz;
    35.         }
    36.         // (0.2) Try loading the class with the bootstrap class loader, to prevent
    37.         // the webapp from overriding Java SE classes. This implements
    38.         // SRV.10.7.2
    39.         //用于防止系统的类覆盖到jdk默认的lib
    40.         String resourceName = binaryNameToPath(name, false);
    41.         //通过ExtClassloader 查看是否被加载过了
    42.         ClassLoader javaseLoader = getJavaseClassLoader();
    43.         boolean tryLoadingFromJavaseLoader;
    44.         try {
    45.             // Use getResource as it won't trigger an expensive
    46.             // ClassNotFoundException if the resource is not available from
    47.             // the Java SE class loader. However (see
    48.             // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
    49.             // details) when running under a security manager in rare cases
    50.             // this call may trigger a ClassCircularityError.
    51.             // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
    52.             // details of how this may trigger a StackOverflowError
    53.             // Given these reported errors, catch Throwable to ensure any
    54.             // other edge cases are also caught
    55.             URL url;
    56.             if (securityManager != null) {
    57.                 PrivilegedAction dp = new PrivilegedJavaseGetResource(resourceName);
    58.                 url = AccessController.doPrivileged(dp);
    59.             } else {
    60.                 url = javaseLoader.getResource(resourceName);
    61.             }
    62.             tryLoadingFromJavaseLoader = (url != null);
    63.         } catch (Throwable t) {
    64.             // Swallow all exceptions apart from those that must be re-thrown
    65.             ExceptionUtils.handleThrowable(t);
    66.             // The getResource() trick won't work for this class. We have to
    67.             // try loading it directly and accept that we might get a
    68.             // ClassNotFoundException.
    69.             tryLoadingFromJavaseLoader = true;
    70.         }
    71.         //默认返回是成功,因为检查一般是通过,不通过会抛出异常
    72.         if (tryLoadingFromJavaseLoader) {
    73.             try {
    74.                 //通过Classloader进行加载
    75.                 clazz = javaseLoader.loadClass(name);
    76.                 //不为空,进行解析并加载到缓存中后返回
    77.                 if (clazz != null) {
    78.                     if (resolve) {
    79.                         resolveClass(clazz);
    80.                     }
    81.                     return clazz;
    82.                 }
    83.             } catch (ClassNotFoundException e) {
    84.                 // Ignore
    85.             }
    86.         }
    87.         // (0.5) Permission to access this class when using a SecurityManager
    88.         //安全检查 主要指包路劲
    89.         if (securityManager != null) {
    90.             int i = name.lastIndexOf('.');
    91.             if (i >= 0) {
    92.                 try {
    93.                     securityManager.checkPackageAccess(name.substring(0,i));
    94.                 } catch (SecurityException se) {
    95.                     String error = sm.getString("webappClassLoader.restrictedPackage", name);
    96.                     log.info(error, se);
    97.                     throw new ClassNotFoundException(error, se);
    98.                 }
    99.             }
    100.         }
    101.         //判断是否使用委托方式(父类加载)
    102.         boolean delegateLoad = delegate || filter(name, true);
    103.         // (1) Delegate to our parent if requested
    104.         //delegateLoad用于判断是否委托加载
    105.         if (delegateLoad) {
    106.             if (log.isDebugEnabled()) {
    107.                 log.debug(" Delegating to parent classloader1 " + parent);
    108.             }
    109.             try {
    110.                 //通过父类加载
    111.                 clazz = Class.forName(name, false, parent);
    112.                 if (clazz != null) {
    113.                     if (log.isDebugEnabled()) {
    114.                         log.debug(" Loading class from parent");
    115.                     }
    116.                     if (resolve) {
    117.                         resolveClass(clazz);
    118.                     }
    119.                     return clazz;
    120.                 }
    121.             } catch (ClassNotFoundException e) {
    122.                 // Ignore
    123.             }
    124.         }
    125.         // (2) Search local repositories
    126.         if (log.isDebugEnabled()) {
    127.             log.debug(" Searching local repositories");
    128.         }
    129.         try {
    130.             //这里是核心的重写方法,因为这个是重写了classloader进行实现的。(webapp)
    131.             clazz = findClass(name);
    132.             if (clazz != null) {
    133.                 if (log.isDebugEnabled()) {
    134.                     log.debug(" Loading class from local repository");
    135.                 }
    136.                 if (resolve) {
    137.                     resolveClass(clazz);
    138.                 }
    139.                 return clazz;
    140.             }
    141.         } catch (ClassNotFoundException e) {
    142.             // Ignore
    143.         }
    144.         // (3) Delegate to parent unconditionally
    145.         //不是委托加载,通过父类进行加载
    146.         if (!delegateLoad) {
    147.             if (log.isDebugEnabled()) {
    148.                 log.debug(" Delegating to parent classloader at end: " + parent);
    149.             }
    150.             try {
    151.                 //通过父类加载
    152.                 clazz = Class.forName(name, false, parent);
    153.                 if (clazz != null) {
    154.                     if (log.isDebugEnabled()) {
    155.                         log.debug(" Loading class from parent");
    156.                     }
    157.                     if (resolve) {
    158.                         resolveClass(clazz);
    159.                     }
    160.                     return clazz;
    161.                 }
    162.             } catch (ClassNotFoundException e) {
    163.                 // Ignore
    164.             }
    165.         }
    166.     }
    167.     //都打不到抛出找不到类异常
    168.     throw new ClassNotFoundException(name);
    169. }

    最后

        通过了解java的类加载机制再来学习tomcat的类加载机制,特别是阅读过源码后你会发现非常简单,只是针对Classloader进行重新,根据自已的需求进行判断路劲是通过双亲委派机制进行加载,还是通过自定类加载器进行加载,所以这块建议学习的同学通过源码来学习会快很多,当然在学习之前特别建议把相关的基础一定要扎实。

    参考文章:

    https://www.jianshu.com/p/9b2d43c9a09a

    https://blog.51cto.com/tiantianzaixian/5621528

    视频:https://www.bilibili.com/video/BV1o34y197fz?p=8&vd_source=7d0e42b081e08cb3cefaea55cc1fa8b7

  • 相关阅读:
    vueRouter的$route和$router的解析和运用场景
    湖南源点调研 为什么中小企业产品上市前一定要做市场调研?
    【JavaSE】类和对象(一)
    LeetCode每日一题——324. 摆动排序 II
    Netty源码学习4——服务端是处理新连接的&netty的reactor模式
    Day34——K次取反后最大化的数组和、加油站、分发糖果
    如何设计测试用例
    Docker容器的应用部署
    ElementUI之CUD+表单验证
    信号源是什么 都有什么功能
  • 原文地址:https://blog.csdn.net/qq_16498553/article/details/126825521