精华内容
下载资源
问答
  • 线程上下文加载器

    2020-04-15 23:28:55
    线程上下文加载器(ThreadContextLoad) 任意一个继承了ClassLoader的类都可以被设置为线程上下文加载器. 通过以下函数 Thread.currentThread().setContextClassLoader(myThreadLoader); 作用 这是最好玩的地方 如果...

    线程上下文加载器(ThreadContextLoad)

    任意一个继承了ClassLoader的类都可以被设置为线程上下文加载器.

    通过以下函数

    Thread.currentThread().setContextClassLoader(myThreadLoader);
    

    作用

    这是最好玩的地方

    如果一个类无法被系统加载器加载,那么就会尝试使用线程上下文加载器进行加载.

    通过反射可以很容易的加载到一个类,但如果这个类依赖了其他不再当前系统加载器加载范围中的类,那么这个类实际上的用处是很单一的.

    通过线程上下文加载器的使用,可以定义一个从网络中获取class的加载器,例如可以专门有一个发现类的服务,可以通过http接口按全限定名找到一个类的字节流.然后用这个接口实现依赖加载,那么程序包甚至可以没有任何非必要的包,甚至可以只要jre的包,然后就可以实现各种业务.而如果升级功能,也许只需要重启应用即可.

    一个实例

    public class ClassLoaderTest {
        static class MyThreadLoader extends ClassLoader {
            String path = "d://clazzs//";//将需要加载的类放在这个位置,包则为文件夹
    
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                name=name.replace(".","/");
                byte[] load = load(name);
                if (load.length == 0)
                    throw new ClassNotFoundException(name);
                return defineClass( name.replace("/", "."), load, 0, load.length);
            }
    
            byte[] load(String name) {
                System.out.println("--------load "+name);
                ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                try (FileInputStream inputStream = new FileInputStream(new File(path + name + ".class"))) {
                    int c;
                    while ((c = inputStream.read()) != -1) {
                        bytes.write(c);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return bytes.toByteArray();
            }
        }
    
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException, InterruptedException {
            Thread thread=new Thread(()->{
                MyThreadLoader myThreadLoader = new MyThreadLoader();
                Thread.currentThread().setContextClassLoader(myThreadLoader);
                Class<?> man = null;
                try {
                    man = myThreadLoader.findClass("bean.Man");//需要加载的类,这个类可以依赖其他也需要加载的类
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                System.out.println(man);
                try {
                    System.out.println(man.newInstance());
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
    
                for (Field field : man.getFields()) {
                    field.setAccessible(true);
                    System.out.println(field.getType());
                }
            });
            thread.start();
            thread.join();
        }
    

    上面定义了一个从特点磁盘位置获取类的加载功能.虽然很简陋,但也可以发现有很多好玩的地方可以做.

    展开全文
  • jvm之线程上下文加载器与SPI

    万次阅读 2020-07-11 19:05:57
    线程上下文加载器 线程上下文类加载器(Thread Context Class Loader,简称TCCL)是从JDK1.2开始引入的。类java.lang.Thread中的方法getContextClassLoader()和setContextClassLoader(ClassLoader cl)用来获取和设置...

    线程上下文加载器

    线程上下文类加载器(Thread Context Class Loader,简称TCCL)是从JDK1.2开始引入的。类java.lang.Thread中的方法getContextClassLoader()和setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。

    如果没有通过setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器,默认为系统类加载器,这点可以从下面的JDK中的源码中得到验证。

    以下代码摘自sun.misc.Launch的无参构造函数Launch():

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
    
        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
    
        Thread.currentThread().setContextClassLoader(this.loader);
    ...
    

    SPI

    Service Provider Interface:服务提供者接口,简称SPI,是Java提供的一套用来被第三方实现或者扩展的API。常见的SPI有JDBC、JNDI、JAXP等,这些SPI的接口由Java核心库实现,而这些SPI的具体实现由第三方jar包实现。

    下面先看一段经典的JDBC获取连接的代码:

    // Class.forName("com.mysql.jdbc.Driver").newInstance();
    Connection conn = java.sql.DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
    

    我们可以Class.forName这一行被注释掉了,但依然可以正常运行,这是为什么呢?

    下面通过跟踪源码一步一步分析原因:

    先看DriverManager这个类,调用该类的静态方法getConnection(),就会导致该类的初始化,也就是会执行这个类的静态代码块,DriverManager的静态代码块如下:

        // 静态代码块
        static {
            loadInitialDrivers();
            println("JDBC DriverManager initialized");
        }
        
        // 加载驱动
        private static void loadInitialDrivers() {
            ...
    
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
    
                    // 读取 META-INF/services
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                    Iterator<Driver> driversIterator = loadedDrivers.iterator();
    
                    try{
                        while(driversIterator.hasNext()) {
                            // next()的时候才会去加载
                            driversIterator.next();
                        }
                    } catch(Throwable t) {
                    // Do nothing
                    }
                    return null;
                }
            });
            ... 
        }
    

    再来看一下ServiceLoader.load方法:

        public static <S> ServiceLoader<S> load(Class<S> service) {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            return ServiceLoader.load(service, cl);
        }
    

    从load方法中可以看出取出了线程上下文加载器也就是系统类加载器传递给了后面的代码。

    一路跟踪下去会发现系统类加载这个参数传给了里面的一个内部类LazyIterator:

        private class LazyIterator
            implements Iterator<S>
        {
    
            Class<S> service;
            ClassLoader loader; // 系统类加载器
            Enumeration<URL> configs = null;
            Iterator<String> pending = null;
            String nextName = null;
    
            private LazyIterator(Class<S> service, ClassLoader loader) {
                this.service = service;
                this.loader = loader;
            }
    

    LazyIterator实现了Iterator接口,后面loadInitialDrivers中获取这个迭代器进行遍历,最终会调用下面的两个方法

            private boolean hasNextService() {
                if (nextName != null) {
                    return true;
                }
                if (configs == null) {
                    try {
                        // META-INF/services/ + java.sql.Driver
                        String fullName = PREFIX + service.getName();
                        if (loader == null)
                            configs = ClassLoader.getSystemResources(fullName);
                        else
                            configs = loader.getResources(fullName);
                    } catch (IOException x) {
                        fail(service, "Error locating configuration files", x);
                    }
                }
                while ((pending == null) || !pending.hasNext()) {
                    if (!configs.hasMoreElements()) {
                        return false;
                    }
                    // 解析META-INF/services/java.sql.Driver文件
                    pending = parse(service, configs.nextElement());
                }
                nextName = pending.next();
                return true;
            }
    
            private S nextService() {
                if (!hasNextService())
                    throw new NoSuchElementException();
                String cn = nextName;
                nextName = null;
                Class<?> c = null;
                try {
                    // 使用线程上下文加载器加载META-INF/services/java.sql.Driver中指定的驱动类
                    c = Class.forName(cn, false, loader);
                } catch (ClassNotFoundException x) {
                    fail(service,
                         "Provider " + cn + " not found");
                }
                if (!service.isAssignableFrom(c)) {
                    fail(service,
                         "Provider " + cn  + " not a subtype");
                }
                try {
                    S p = service.cast(c.newInstance());
                    providers.put(cn, p);
                    return p;
                } catch (Throwable x) {
                    fail(service,
                         "Provider " + cn + " could not be instantiated",
                         x);
                }
                throw new Error();          // This cannot happen
            }
    

    再来看一下mysql-connector-java.jar包下META-INF/services/java.sql.Driver中内容:

    com.mysql.jdbc.Driver
    com.mysql.fabric.jdbc.FabricMySQLDriver
    

    最后看一下com.mysql.jdbc.Driver的静态代码块:

    static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    

    registerDriver方法将driver实例注册到JDk的java.sql.DriverManager类中,其实就是add到它的一个类型为CopyOnWriteArrayList,名为registeredDrivers的静态属性中,到此驱动注册基本完成,

    总结:如果我们使用JDBC时没有主动使用Class.forName加载mysql的驱动时,那么JDBC会使用SPI机制去查找所有的jar下面的META-INF/services/java.sql.Driver文件,使用Class.forName反射加载其中指定的驱动类。

    DriverManager类和ServiceLoader类都是属于rt.jar的,它们的类加载器是根类加载器。而具体的数据库驱动,却属于业务代码,这个根类加载器是无法加载的。而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。

    使用TCCL校验实例的归属

    下面再来看一下java.sql.DriverManager.getConnection()这个方法,这里面有个小细节:

        //  Worker method called by the public getConnection() methods.
        private static Connection getConnection(
            String url, java.util.Properties info, Class<?> caller) throws SQLException {
    
            // callerCL是调用这个方法的类所对应的类加载器
            ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
            synchronized(DriverManager.class) {
                // synchronize loading of the correct classloader.
                if (callerCL == null) {
                    callerCL = Thread.currentThread().getContextClassLoader();
                }
            }
            ...
            // 遍历注册到registeredDrivers里的Driver类
            for(DriverInfo aDriver : registeredDrivers) {
                // 使用线程上下文类加载器检查Driver类有效性,重点在isDriverAllowed中,方法内容在后面
                if(isDriverAllowed(aDriver.driver, callerCL)) {
                    try {
                        println("    trying " + aDriver.driver.getClass().getName());
                        Connection con = aDriver.driver.connect(url, info);
                        if (con != null) {
                            // Success!
                            println("getConnection returning " + aDriver.driver.getClass().getName());
                            return (con);
                        }
                    } catch (SQLException ex) {
                        if (reason == null) {
                            reason = ex;
                        }
                    }
    
                } else {
                    println("    skipping: " + aDriver.getClass().getName());
                }
    
            }
            ...
        }
    
        private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
            boolean result = false;
            if(driver != null) {
                Class<?> aClass = null;
                try {
                    // 传入的classLoader为调用getConnetction的线程上下文类加载器,使用这个类加载器再次加载驱动类
                    aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
                } catch (Exception ex) {
                    result = false;
                }
                // 这里只有aClass和driver.getClass()是由同一个类加载器加载才会相等
                 result = ( aClass == driver.getClass() ) ? true : false;
            }
    
            return result;
        }
    

    isDriverAllowed这个方法的意义:例如在tomcat中,多个webapp都有自己的Classloader,如果它们都自带mysql-connect.jar包,那底层Classloader的DriverManager里将注册多个不同类加载器加载的Driver实例,webapp想要从DriverManager中获得连接,只有通过线程上下文加载器区分了。

    更多精彩内容关注本人公众号:架构师升级之路
    在这里插入图片描述

    展开全文
  • java多线程-线程上下文加载器 获取线程上下文加载器 public class App { public static void main(String[] args) { System.out.println(Thread.currentThread().getContextClassLoader()); } } 结果: jdk...

    java多线程-线程上下文加载器

    获取线程上下文加载器

    public class App {
        
        public static void main(String[] args) {
    
            System.out.println(Thread.currentThread().getContextClassLoader());
        }
    
    }
    
    

    结果:

    
    jdk.internal.loader.ClassLoaders$AppClassLoader@659e0bfd
    

    为什么需要线程上下文加载器?

    这与JVM类加载器双亲委托机制自身的缺陷有关。JDK的核心库存中提供了很多 SPI(Service Provider Interface), 常见的SPI 包括JDBC、JNDI…,但是没有提供具体的实现,具体的实现需要由第三方来提供。举例来说,JDBC的实现,JDK在 java.lang.sql中的所有接口都是由JDK提供的,加载用品喜欢接口的类加载器是根加载器(BootClassLoader),第三方厂商提供的类库驱动则是由系统加载器(AppClassLoader)加载的。由于JVM加载器的双亲委托机制,第三方的JDBC驱动包的实现不会被加载。

    所以解决这个问题的核心就是线程上下文加载器,有了线程上下文加载器,根加载器就可以依托子类加载器去加载厂商提供的SPI的具体实现, 线程上下文加载方式打破了双亲委托机制的模型

    展开全文
  • 线程上下文加载器(ThreadContextClassLoader,TCCL) Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。...

     

    线程上下文类加载器(ThreadContextClassLoader,TCCL)

    Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器

    JDBC案例分析

    平时使用mysql获取数据库连接的:

    // 加载Class到AppClassLoader(系统类加载器),然后注册驱动类
    // Class.forName("com.mysql.jdbc.Driver").newInstance(); 
    String url = "jdbc:mysql://localhost:3306/testdb";    
    // 通过java库获取数据库连接
    Connection conn = java.sql.DriverManager.getConnection(url, "name", "password"); 

    以上就是mysql注册驱动及获取connection的过程,各位可以发现经常写的Class.forName被注释掉了,但依然可以正常运行,这是为什么呢?这是因为从Java1.6开始自带的jdbc4.0版本已支持SPI服务加载机制,只要mysql的jar包在类路径中,就可以注册mysql驱动。

    那到底是在哪一步自动注册了mysql driver的呢?重点就在DriverManager.getConnection()中。我们都是知道调用类的静态方法会初始化该类,进而执行其静态代码块,DriverManager的静态代码块就是:

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    初始化方法loadInitialDrivers()的代码如下:

    private static void loadInitialDrivers() {
        String drivers;
        try {
            // 先读取系统属性
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // 通过SPI加载驱动类
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                    // Do nothing
                }
                return null;
            }
        });
        // 继续加载系统属性中的驱动类
        if (drivers == null || drivers.equals("")) {
            return;
        }
        
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                // 使用AppClassloader加载
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

    从上面可以看出JDBC中的DriverManager的加载Driver的步骤顺序依次是:(1)通过SPI方式,读取 META-INF/services 下文件中的类名,使用TCCL加载;(2)通过System.getProperty("jdbc.drivers")获取设置,然后通过系统类加载器加载。

    SPI机制简介

    SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
    SPI具体约定:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要在代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。

    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    Iterator<Driver> driversIterator = loadedDrivers.iterator();
    
    try{
        while(driversIterator.hasNext()) {
            driversIterator.next();
        }
    } catch(Throwable t) {
    // Do nothing
    }

    注意driversIterator.next()最终就是调用Class.forName(DriverName, false, loader)方法,也就是最开始我们注释掉的那一句代码。好,那句因SPI而省略的代码现在解释清楚了,那我们继续看给这个方法传的loader是怎么来的。

    因为这句Class.forName(DriverName, false, loader)代码所在的类在java.util.ServiceLoader类中,而ServiceLoader.class又加载在BootrapLoader中,因此传给 forName 的 loader 必然不能是BootrapLoader 。这时候只能使用TCCL了,也就是说把自己加载不了的类加载到TCCL中(通过Thread.currentThread()获取)。TCCL默认使用当前执行的是代码所在应用的系统类加载器AppClassLoader。再看下看ServiceLoader.load(Class)的代码,的确如此:

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    ContextClassLoader默认存放了AppClassLoader的引用,由于它是在运行时被放在了线程中,所以不管当前程序处于何处(BootstrapClassLoader或是ExtClassLoader等),在任何需要的时候都可以用Thread.currentThread().getContextClassLoader()取出应用程序类加载器来完成需要的操作。

    到这儿差不多把SPI机制解释清楚了。直白一点说就是,我(JDK)提供了一种帮你(第三方实现者)加载服务(如数据库驱动、日志库)的便捷方式,只要你遵循约定(把类名写在/META-INF里),那当我启动时我会去扫描所有jar包里符合约定的类名,再调用forName加载,但我的ClassLoader是没法加载的,那就把它加载到当前执行线程的TCCL里,后续你想怎么操作(驱动实现类的static代码块)就是你的事了。

    好,刚才说的驱动实现类就是com.mysql.jdbc.Driver.Class,它的静态代码块里头又写了什么呢?是否又用到了TCCL呢?

    com.mysql.jdbc.Driver加载后运行的静态代码块:

    static {
        try {
            // Driver已经加载到TCCL中了,此时可以直接实例化
            java.sql.DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    registerDriver方法将driver实例注册到系统的java.sql.DriverManager类中,其实就是add到它的一个名为registeredDrivers的静态成员CopyOnWriteArrayList中 。

    到此驱动注册基本完成,接下来我们回到最开始的那段样例代码:java.sql.DriverManager.getConnection()。它最终调用了以下方法:

    private static Connection getConnection(
         String url, java.util.Properties info, Class<?> caller) throws SQLException {
         /* 传入的caller由Reflection.getCallerClass()得到,该方法
          * 可获取到调用本方法的Class类,这儿获取到的是当前应用的类加载器
          */
         ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
         synchronized(DriverManager.class) {
             if (callerCL == null) {
                 callerCL = Thread.currentThread().getContextClassLoader();
             }
         }
    
         if(url == null) {
             throw new SQLException("The url cannot be null", "08001");
         }
    
         SQLException reason = null;
         // 遍历注册到registeredDrivers里的Driver类
         for(DriverInfo aDriver : registeredDrivers) {
             // 检查Driver类有效性
             if(isDriverAllowed(aDriver.driver, callerCL)) {
                 try {
                     println("    trying " + aDriver.driver.getClass().getName());
                     // 调用com.mysql.jdbc.Driver.connect方法获取连接
                     Connection con = aDriver.driver.connect(url, info);
                     if (con != null) {
                         // Success!
                         return (con);
                     }
                 } catch (SQLException ex) {
                     if (reason == null) {
                         reason = ex;
                     }
                 }
    
             } else {
                 println("    skipping: " + aDriver.getClass().getName());
             }
    
         }
         throw new SQLException("No suitable driver found for "+ url, "08001");
     }

    由于TCCL本质就是当前应用类加载器,所以之前的初始化就是加载在当前的类加载器中,这一步就是校验存放的driver是否属于调用者的Classloader。例如在下文中的tomcat里,多个webapp都有自己的Classloader,如果它们都自带 mysql-connect.jar包,那底层Classloader的DriverManager里将注册多个不同类加载器的Driver实例,想要区分只能靠TCCL了。

    Tomcat与spring的类加载器

    Tomcat中的类加载器

    在Tomcat目录结构中,有三组目录(“/common/*”,“/server/*”和“shared/*”)可以存放公用Java类库,此外还有第四组Web应用程序自身的目录“/WEB-INF/*”,把java类库放置在这些目录中的含义分别是:

    放置在common目录中:类库可被Tomcat和所有的Web应用程序共同使用。
    放置在server目录中:类库可被Tomcat使用,但对所有的Web应用程序都不可见。
    放置在shared目录中:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。
    放置在/WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。

    为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现。而 CommonClassLoader、CatalinaClassLoader、SharedClassLoader 和 WebAppClassLoader 则是 Tomcat 自己定义的类加载器,它们分别加载 /common/*、/server/*、/shared/* 和 /WebApp/WEB-INF/* 中的 Java 类库。其中 WebApp 类加载器和 Jsp 类加载器通常会存在多个实例,每一个 Web 应用程序对应一个 WebApp 类加载器,每一个 JSP 文件对应一个 Jsp 类加载器。

    从图中的委派关系中可以看出,CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,而 CatalinaClassLoader 和 SharedClassLoader 自己能加载的类则与对方相互隔离。WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 实例之间相互隔离。而 JasperLoader 的加载范围仅仅是这个 JSP 文件所编译出来的那一个 Class,它出现的目的就是为了被丢弃:当服务器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 Jsp 类加载器来实现 JSP 文件的 HotSwap 功能。

    Spring加载问题

    Tomcat 加载器的实现清晰易懂,并且采用了官方推荐的“正统”的使用类加载器的方式。这时作者提一个问题:如果有 10 个 Web 应用程序都用到了spring的话,可以把Spring的jar包放到 common 或 shared 目录下让这些程序共享。Spring 的作用是管理每个web应用程序的bean,getBean时自然要能访问到应用程序的类,而用户的程序显然是放在 /WebApp/WEB-INF 目录中的(由 WebAppClassLoader 加载),那么在 CommonClassLoader 或 SharedClassLoader 中的 Spring 容器如何去加载并不在其加载范围的用户程序(/WebApp/WEB-INF/)中的Class呢?

    答案呼之欲出:spring根本不会去管自己被放在哪里,它统统使用TCCL来加载类,而TCCL默认设置为了WebAppClassLoader,也就是说哪个WebApp应用调用了spring,spring就去取该应用自己的WebAppClassLoader来加载bean

    源码具体实现:在web.xml中定义的listener为org.springframework.web.context.ContextLoaderListener,它最终调用了org.springframework.web.context.ContextLoader类来装载bean,具体方法如下(删去了部分不相关内容):

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        try {
            // 创建WebApplicationContext
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            // 将其保存到该webapp的servletContext中        
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
            // 获取线程上下文类加载器,默认为WebAppClassLoader
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            // 如果spring的jar包放在每个webapp自己的目录中
            // 此时线程上下文类加载器会与本类的类加载器(加载spring的)相同,都是WebAppClassLoader
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                // 如果不同,也就是上面说的那个问题的情况,那么用一个map把刚才创建的WebApplicationContext及对应的WebAppClassLoader存下来
                // 一个webapp对应一个记录,后续调用时直接根据WebAppClassLoader来取出
                currentContextPerThread.put(ccl, this.context);
            }
            
            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            throw err;
        }
    }

    spring考虑到了自己可能被放到其他位置,所以直接用TCCL来解决所有可能面临的情况。

    总结

    通过上面的两个案例分析,我们可以总结出线程上下文类加载器的适用场景:(1)当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。(2)当使用本类托管类加载,然而加载本类的ClassLoader未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载器代为托管。
     

    展开全文
  • 假设我们都知道,我们程序运行时,线程上下文加载器默认是系统类加载器, 所以当我们main 方法执行时,执行 DriverManager.getConnection(....),DriverManager 最终会交给启动加载器来加载,而Driver 接口...
  • Java应用运行时的初始线程上下文加载器是系统类加载器,在线程中运行的代码可以通过该类加载器来加载类与资源 线程上下文类加载器的重要性: SPI(service provider interface) 父ClassLoader可以使用当前线程Thread...
  • } myMethod里面调用了Thread.currentThread().getContextClassLoader(),获取当前线程上下文加载器做某些事情。 如果一个类由加载器A加载,那么这个类的依赖类也是由相同的类加载器加载的(如果该依赖类之前没有...
  • 1 线程上下文加载器 2 何时使用Thread.getContextClassLoader()? 3 类加载器与Web容器 4 类加载器与OSGi 总结 1 线程上下文加载器  线程上下文加载器(context class loader)是从 JDK 1.2 开始引入的...
  • 线程上下文加载器

    2019-12-02 23:25:30
    当前类加载器 (Current Classloader) 每个类都会使用自己的类加载器(即加载自身的类加载器)尝试去加载其他类(指的是所依赖类)。...线程上下文加载器是从JDK1.2开始引入的,类Thread中的getC...
  • 为什么叫作“线程上下文类加载器”呢,因为这个类加载器保存在线程私有数据里,只要是同一个线程,一旦设置了线程上下文加载器,在线程后续执行过程中就能把这个类加载器取出来用。 线程上下文加载器其实是线程私有...
  • 线程上下文加载器是从JDK1.2开始引入的,类Thread中的getContextClassloader()和setContextClassloader()方法分别是获取当前线程上下文加载器和设置线程上下文加载器。 如果没有通过setContextClassloader...
  • Java应用启动时默认的线程上下文加载器是系统类加载器,在线程中运行的代码可以通过该类加载器来加载类 应用范围;SPI服务广泛使用(Service Provider Interface) 传统双亲委托模型下,SPI无法实现,而使用线程...
  • 真正理解线程上下文加载器(多案例分析)

    万次阅读 多人点赞 2016-09-25 13:31:36
    线程上下文加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使上层代码可以逆向使用下层的系统类加载器。本文通过JDBC和Tomcat两个案例分析,详细解释了其中的原理。
  • 线程上下文加载器,正常的双亲委派模型中,下层的类加载器可以使用上层父加载器加载的对象,但是上层父类的加载器不可以使用子类加载的对象。而有些时候程序的确需要上层调用下层,这时候就需要线程上下文加载器来...
  • 文章目录目标引入问题线程上下文加载器服务提供者接口(SPI)JDBC 源码分析代码实践工程一SPI 服务接口基础调用类打包工程二导入base.jar包SPI 服务实现测试代码测试测试1:测试2: 目标 了解线程上下文类加载的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 133,516
精华内容 53,406
关键字:

线程上下文加载器