.实现 linkedlist 类java_Java - Thread Context类加载器的实现

本文非原创,转载自:https://www.cnblogs.com/duke2016/p/9164058.html

---------------------------------------------------------------------------------------

疑惑

  以前在看源码的时候,总是会遇到框架里的代码使用Thread.currentThread.getContextClassLoader()获取当前线程的Context类加载器,通过这个Context类加载器去加载类。

  我们平时在程序中写代码的时候,遇到要动态加载类的时候,一般使用Class.forName()的方式加载我们需要的类。比如最常见的,当我们进行JDBC编程的时候,我们通过Class.forName()去加载JDBC的驱动。

try {    return Class.forName("oracle.jdbc.driver.OracleDriver");} catch (ClassNotFoundException e) {    // skip}

  那么为什么当我们使用Class.forName()的方式去加载类的时候,如果类找不到,我们还要尝试用Thread.currentThread.getContextLoader()获取的类加载器去加载类呢?比如我们可能会碰到下面这种代码:

try {    return Class.forName(className);} catch (ClassNotFoundException e) {    // skip}ClassLoader ctxClassLoader = Thread.currentThread().getContextClassLoader();if (ctxClassLoader != null) {    try {    clazz = ctxClassLoader.loadClass(className);    } catch (ClassNotFoundException e) {      // skip    }}

  这里加粗的部分,就是使用了Thread.currentThread.getContextLoader()获取的加载器去加载类。显然,Class.forName()加载类的时候使用的类加载器可能和Thread.currentThread.getContextLoader()获取的类加载器是不同的。那么为什么会出现不同呢?

JAVA的类加载器

  要理解为什么会用到Thread.currentThread.getContextLoader()获取的这个类加载器之前,我们先来了解下JVM里使用的类加载器(ClassLoader)。

  JVM默认有三种类加载器:

  • Bootstrap Class Loader
  • Extension Class Loader
  • System Class Loader

Bootstrap Class Loader

  Bootstrap Class Loader类加载器是JDK自带的一款类加载器,用于加载JDK内部的类。Bootstrap类加载器用于加载JDK中$JAVA_HOME/jre/lib下面的那些类,比如rt.jar包里面的类。Bootstrap类加载器是JVM的一部分,一般采用native代码编写。

Extension Class Loader

  Extension Class Loader类加载器主要用于加载JDK扩展包里的类。一般$JAVA_HOME/lib/ext下面的包都是通过这个类加载器加载的,这个包下面的类基本上是以javax开头的。

System Class Loader

  System Class Loader类加载器也叫应用程序类加载器(AppClassLoader)。顾名思义,这个类加载器就是用来加载开发人员自己平时写的应用代码的类的。System类加载器是用于加载存放在classpath路径下的那些应用程序级别的类的。

下面的代码列举出了这三个类加载器:

public class MainClass {    public static void main(String[] args) {        System.out.println(Integer.class.getClassLoader());        System.out.println(Logging.class.getClassLoader());        System.out.println(MainClass.class.getClassLoader());    }}

其中获取Bootstrap类加载器永远返回null值

null # Bootstrap类加载器sun.misc.Launcher$ExtClassLoader@5e2de80c # Extension类加载器sun.misc.Launcher$AppClassLoader@18b4aac2 # System类加载器

双亲委派模型

  上面介绍的三种类加载器,并不是孤立的,他们之间有一个层次关系:

50f5057a5e3c7de9f4fb66eb8d56f45f.png

  三个类加载器之间通过这个层次关系协同工作,一起负责类的加载工作。上面的这种层次模型称为类加载器的“双亲委派”模型。双亲委派模型要求,除了最顶层的Bootstrap类加载器之外,所有的类加载器都必须有一个parent加载器。当类加载器加载类的时候,首先检查缓存中是否有已经被加载的类。如果没有,则优先委托它的父加载器去加载这个类,父加载器执行和前面子加载器一样的工作,直到请求达到顶层的Bootstrap类加载器。如果父加载器不能加载需要的类,那么这个时候才会让子加载器自己去尝试加载这个类。工作原理类似于下面这种方式:

e6971540c28e5634bda64838b7db207d.png

  我们可以通过JDK里ClassLoader里面的代码一窥双亲委派机制的实现方式,代码实现在ClassLoader.loadClass()里面

rotected Class> loadClass(String name, boolean resolve)   throws ClassNotFoundException   synchronized (getClassLoadingLock(name)) {       // First, check if the class has already been loaded       Class> c = findLoadedClass(name);       if (c == null) {           long t0 = System.nanoTime();           try {               if (parent != null) {                   c = parent.loadClass(name, false);               } else {                   c = findBootstrapClassOrNull(name);               }           } catch (ClassNotFoundException e) {               // ClassNotFoundException thrown if class not found               // from the non-null parent class loader           }           if (c == null) {               // If still not found, then invoke findClass in order               // to find the class.               long t1 = System.nanoTime();               c = findClass(name);               // this is the defining class loader; record the stats               sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);               sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);               sun.misc.PerfCounter.getFindClasses().increment();           }       }       if (resolve) {           resolveClass(c);       }       return c;   }

  采用双亲委派的方式组织类加载器,一个好处是为了安全。如果我们自己定义了一个String类,希望将这个String类替换掉默认Java中的java.lang.String的实现。

  我们将自己实现的String类的class文件放到classpath路径下,当我们使用类加载器去加载我们实现的String类的时候,首先,类加载器会将请求委托给父加载器,通过层层委派,最终由Bootstrap类加载器加载rt.jar包里的String类型,然后一路返回给我们。在这个过程中,我们的类加载器忽略掉了我们放在classpath中自定义的String类。

  如果没有采用双亲委派机制,那么System类加载器可以在classpath路径中找到String的class文件并加载到程序中,导致JDK中的String实现被覆盖。所以类加载器的这种工作方式,在一定程度上保证了Java程序可以安全稳定的运行。

线程上下文类加载器

  上面讲了那么多类加载器相关的内容,可还是没有讲到今天的主题,线程上下文类加载器。

  到这里,我们已经知道Java提供了三种类加载器,并且按照严格的双亲委派机制协同工作。表面上,似乎很完美,但正是这种严格的双亲委派机制导致在加载类的时候,存在一些局限性。

  当我们更加基础的框架需要用到应用层面的类的时候,只有当这个类是在我们当前框架使用的类加载器可以加载的情况下我们才能用到这些类。换句话说,我们不能使用当前类加载器的子加载器加载的类。这个限制就是双亲委派机制导致的,因为类加载请求的委派是单向的。

  虽然这种情况不多,但是还是会有这种需求。比较典型的,JNDI服务。JNDI提供了查询资源的接口,但是具体实现由不同的厂商实现。这个时候,JNDI的代码是由JVM的Bootstrap类加载器加载,但是具体的实现是用户提供的JDK之外的代码,所以只能由System类加载器或者其他用户自定义的类加载器去加载,在双亲委派的机制下,JNDI获取不到JNDI的SPI的实现。

  为了解决这个问题,引入了线程上下文类加载器。通过java.lang.Thread类的setContextClassLoader()设置当前线程的上下文类加载器(如果没有设置,默认会从父线程中继承,如果程序没有设置过,则默认是System类加载器)。有了线程上下文类加载器,应用程序就可以通过java.lang.Thread.setContextClassLoader()将应用程序使用的类加载器传递给使用更顶层类加载器的代码。比如上面的JNDI服务,就可以利用这种方式获取到可以加载SPI实现的类加载器,获取需要的SPI实现类。

7a5d354e5e8a37fa08dbab721f4adaa6.png

  可以看到,引入线程类加载器实际是对双亲委派机制的破坏,但是却提供了类加载的灵活性。

下面是java.lang.Thread.java 中关于contextClassLoader的代码:

 /* The context ClassLoader for this thread */    private ClassLoader contextClassLoader;private Thread(ThreadGroup g, Runnable target, String name,                   long stackSize, AccessControlContext acc,                   boolean inheritThreadLocals) {       ......       Thread parent = currentThread();        ......        if (security == null || isCCLOverridden(parent.getClass()))            this.contextClassLoader = parent.getContextClassLoader();//默认设置为父线程的context class loader        else            this.contextClassLoader = parent.contextClassLoader;//默认设置为父线程的context class loader        .....    }/**     * Returns the context {@code ClassLoader} for this thread. The context     * {@code ClassLoader} is provided by the creator of the thread for use     * by code running in this thread when loading classes and resources.     * If not {@linkplain #setContextClassLoader set}, the default is the     * {@code ClassLoader} context of the parent thread. The context     * {@code ClassLoader} of the     * primordial thread is typically set to the class loader used to load the     * application.     *     *     * @return  the context {@code ClassLoader} for this thread, or {@code null}     *          indicating the system class loader (or, failing that, the     *          bootstrap class loader)     *     * @throws  SecurityException     *          if a security manager is present, and the caller's class loader     *          is not {@code null} and is not the same as or an ancestor of the     *          context class loader, and the caller does not have the     *          {@link RuntimePermission}{@code ("getClassLoader")}     *     * @since 1.2     */    @CallerSensitive    public ClassLoader getContextClassLoader() {        if (contextClassLoader == null)            return null;        SecurityManager sm = System.getSecurityManager();        if (sm != null) {            ClassLoader.checkClassLoaderPermission(contextClassLoader,                                                   Reflection.getCallerClass());        }        return contextClassLoader;    }   public void setContextClassLoader(ClassLoader cl) {        SecurityManager sm = System.getSecurityManager();        if (sm != null) {            sm.checkPermission(new RuntimePermission("setContextClassLoader"));        }        contextClassLoader = cl;    }

下面是java.lang.System.java 关于如何初始化主线程的Context ClassLoader:

        // initializing the system class loader        VM.initLevel(3);        // system class loader initialized        ClassLoader scl = ClassLoader.initSystemClassLoader();        // set TCCL        Thread.currentThread().setContextClassLoader(scl);        // system is fully initialized        VM.initLevel(4);    }

解惑

  回到开头,框架的代码为了加载框架之外用户实现的类,由于这些类可能没法通过框架使用的类加载器进行加载,为了绕过类加载器的双亲委派模型,采用Thread.getContextClassLoader()的方式去加载这些类。

作者:Duke2016  

出处:http://www.cnblogs.com/duke2016/  

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值