类加载器的双亲委派模型_java mock 模拟接口

2022-09-22 14:35:47 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

JVM类加载器

JVM主要有以下几种类加载器:

  1. 引导类加载器 主要加载JVM运行核心类库,位于JRE的lib目录下,如rt.jar中的类。
  2. 扩展类加载器 主要加载JVM中扩展类,位于JRE的ext目录下。
  3. 应用程序类加载器 主要负责加载ClassPath路径下的类,也就是业务类。
  4. 自定义加载器 负责加载用户自定义路径下的类。

类加载器关系

源码解析

ExtClassLoader和AppClassLoader的创建流程

先看下Launcher的构造方法:

代码语言:javascript复制
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就是默认的类加载器:即AppClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) { 
   
            throw new InternalError("Could not create application class loader", var9);
        }
        //设置默认classLoader
        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
    }
ExtClassLoader

看下ExtClassLoader的获取方法getExtClassloader(): 可以看到ExtClassLoader是Launcher的一个内部类,继承的是URLClassLoader。

代码语言:javascript复制
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { 
   
			//获取要加载的类文件
            final File[] var0 = getExtDirs();

            try { 
   
                return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() { 
   
                    public Launcher.ExtClassLoader run() throws IOException { 
   
                        int var1 = var0.length;

                        for(int var2 = 0; var2 < var1;   var2) { 
   
                            MetaIndex.registerDirectory(var0[var2]);
                        }
						//new一个ExtClassLoader
                        return new Launcher.ExtClassLoader(var0);
                    }
                });
            } catch (PrivilegedActionException var2) { 
   
                throw (IOException)var2.getException();
            }
        }

查看getExtDirs()方法:可以看到要加载的类文件都是位于ext文件夹下的。

代码语言:javascript复制
private static File[] getExtDirs() { 
   
            String var0 = System.getProperty("java.ext.dirs");
            File[] var1;
            if (var0 != null) { 
   
                StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
                int var3 = var2.countTokens();
                var1 = new File[var3];

                for(int var4 = 0; var4 < var3;   var4) { 
   
                    var1[var4] = new File(var2.nextToken());
                }
            } else { 
   
                var1 = new File[0];
            }

            return var1;
        }

继续看ExtClassLoader的构造方法:

代码语言:javascript复制
  public ExtClassLoader(File[] var1) throws IOException { 
   
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }

调用了父类的构造方法: 可以看到ExtClassLoader的parent赋值为null,因为引导类加载器是C 语言写的,没有实际java对象。

代码语言:javascript复制
public URLClassLoader(URL[] urls, ClassLoader parent,
                          URLStreamHandlerFactory factory) { 
   
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) { 
   
            security.checkCreateClassLoader();
        }
        acc = AccessController.getContext();
        ucp = new URLClassPath(urls, factory, acc);
    }

这样一个ExtClassLoader就创建好了。

AppClassLoader

AppClassLoader同样也是继承了URLClassLoader类 看下getAppClassLoader方法:

代码语言:javascript复制
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { 
   
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() { 
   
                public Launcher.AppClassLoader run() { 
   
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
        }

可以看到,getAppClassLoader主要加载工程classPath下的类文件。 继续看getAppClassLoader构造方法:

代码语言:javascript复制
AppClassLoader(URL[] var1, ClassLoader var2) { 
   
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }

从一开始的Launcher构造方法中可以看到参数var2就是先初始化的extClassLoader。 同样调用了父类URLClassLoader的构造,将extClassLoader设置为parent,所以appClassLoader的parent是extClassLoader。

由此三个主要类加载器之间的关系弄清楚了,各自要加载的范围也弄清楚。我们再看看自定义类加载器的实现。

自定义类加载器

自定义类加载器要继承ClassLoader方法,只需要重写findClass方法就行了:

代码语言:javascript复制
package classload;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
/** * @author zhw * @description * @date 2021-07-15 14:36 */
public class MyClassLoader extends ClassLoader{ 
   

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException { 
   
        File file = new File("C:/Users/hiwei/Desktop/hiwei/test/Person.class");
        try{ 
   
            byte[] bytes = getClassBytes(file);
            //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
            Class<?> c = defineClass(name, bytes, 0, bytes.length);
            return c;
        } catch (Exception e) { 
   
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    private byte[] getClassBytes(File file) throws Exception
    { 
   
        FileInputStream inputStream = new FileInputStream(file);//原始输入流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inputStream.read(buffer)) != -1 ) { 
   
            baos.write(buffer, 0, len);
        }
        baos.flush();
        return baos.toByteArray();
    }
}

关于自定义类加载器的parent是谁,可以查看:

代码语言:javascript复制
    protected ClassLoader() { 
   
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

继续看getSystemClassLoader():

代码语言:javascript复制
public static ClassLoader getSystemClassLoader() { 
   
        initSystemClassLoader();
        if (scl == null) { 
   
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) { 
   
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

private static synchronized void initSystemClassLoader() { 
   
        if (!sclSet) { 
   
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) { 
   
                Throwable oops = null;
                scl = l.getClassLoader();
            
            }
            sclSet = true;
        }
    }

 public ClassLoader getClassLoader() { 
   
        return this.loader;
    }

返回的是this.loader。上面已经知道loader就是AppClassLoader。所以自定义类加载器的默认parent就是AppClassLoader。

双亲委派

在类加载流程中,首先调用的是Launcher.loader.loadClass()方法。

代码语言:javascript复制
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就是默认的类加载器:即AppClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) { 
   
            throw new InternalError("Could not create application class loader", var9);
        }
        //设置默认classLoader
        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
    }

loader就是AppClassLoader。所以继续看AppClassLoader.loadClass方法:

代码语言:javascript复制
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException { 
   
            int var3 = var1.lastIndexOf(46);
            if (var3 != -1) { 
   
                SecurityManager var4 = System.getSecurityManager();
                if (var4 != null) { 
   
                    var4.checkPackageAccess(var1.substring(0, var3));
                }
            }

            if (this.ucp.knownToNotExist(var1)) { 
   
                Class var5 = this.findLoadedClass(var1);
                if (var5 != null) { 
   
                    if (var2) { 
   
                        this.resolveClass(var5);
                    }

                    return var5;
                } else { 
   
                    throw new ClassNotFoundException(var1);
                }
            } else { 
   
            	//调用父类的loadClass方法
                return super.loadClass(var1, var2);
            }
        }

继续看super.loadClass(var1, var2):双亲委派机制的核心代码来了

代码语言:javascript复制
protected 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 { 
   
                	//如果父加载器不为null,则交给父加载器加载。
                    if (parent != null) { 
   
                        c = parent.loadClass(name, false);
                    } else { 
    //如果父加载器为null,则交给引导类加载器加载。
                        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;
        }
    }

看完上面的代码后,是不是觉得双亲委派机制的实现很简单?

双亲委派的作用:

  1. 沙箱安全,保证JVM核心代码不被用户自定义类覆盖。
  2. 保证了类加载的唯一性。

如何打破双亲委派?

看双亲委派机制的源码,可以看到主要实现实在loadClass方法中,那么,只需要重写loadClass(String name, boolean resolve)方法即可:

代码语言:javascript复制
package classload;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
/** * @author zhw * @description * @date 2021-07-15 14:36 */
public class MyClassLoader extends ClassLoader{ 
   

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException { 
   
        File file = new File("C:/Users/hiwei/Desktop/hiwei/test/Person.class");
        try{ 
   
            byte[] bytes = getClassBytes(file);
            //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
            Class<?> c = defineClass(name, bytes, 0, bytes.length);
            return c;
        } catch (Exception e) { 
   
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    protected 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("hiwei.test.Person".equals(name)){ 
   
                    c = findClass(name);
                }else{ 
   
                    c = getParent().loadClass(name);
                }
                
                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;
        }
    }

    private byte[] getClassBytes(File file) throws Exception
    { 
   
        FileInputStream inputStream = new FileInputStream(file);//原始输入流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inputStream.read(buffer)) != -1 ) { 
   
            baos.write(buffer, 0, len);
        }
        baos.flush();
        return baos.toByteArray();
    }
}

测试类:

代码语言:javascript复制
package classload;
/** * @author zhw * @description * @date 2021-07-15 15:09 */
public class ClassLoadTest { 
   
    public static void main(String[] args) throws Exception { 
   
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> clazz = Class.forName("hiwei.test.Person", true, myClassLoader);
        Object o = clazz.newInstance();
        System.out.println(o.toString());
        System.out.println(clazz.getClassLoader());
    }
}

测试: 目标文件夹和classPath都存在Person.class

  1. 测试一: 结果:使用自定义加载器加载。
  1. 测试二:不覆盖loadClass方法。 结果:使用AppClassLoader

破坏双亲委派的应用

tomcat破环双亲委派

在tomcat中不同的应用可能依赖同一个jar的不同版本,如果共用一个类加载器,会导致无法进行环境隔离。所以tomcat自定义类加载器,每个应用都有自己的类加载器,负责加载自己应用下的类,打破了双亲委派机制,不在让父加载器先加载。

源码分析

tomcat的Bootstrap.initClassLoaders()方法中会初始化tomcat核心类的类加载器:

代码语言:javascript复制
	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);
        }
    }

这三个类加载器并未破坏双亲委派模型,这三个都是URLClassLoader的实例。 真正破坏双亲委派模型的是WebappClassLoader类加载器,WebappClassLoader继承了WebappClassLoaderBase,而WebappClassLoaderBase重写了loadClass方法:

代码语言:javascript复制
@Override
    //todo 此处破坏了双亲委派模型
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 
   

        synchronized (getClassLoadingLock(name)) { 
   
            if (log.isDebugEnabled())
                log.debug("loadClass("   name   ", "   resolve   ")");
            Class<?> clazz = null;

            // Log access to stopped class loader
            checkStateForClassLoading(name);

            // (0) Check our previously loaded local class cache
            clazz = findLoadedClass0(name);
            if (clazz != null) { 
   
                if (log.isDebugEnabled())
                    log.debug(" Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
		//省略,,,,
    }

可以看到,重写的loadClass方法破坏了双亲委派模型。

JDBC破坏双亲委派

原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的jar中的Driver类具体实现的。 以以下版本为例:

代码语言:javascript复制
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
</dependency>

Driver实现类:

代码语言:javascript复制
public class Driver extends NonRegisteringDriver implements java.sql.Driver { 
   
    static { 
   
        try { 
   
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) { 
   
            throw new RuntimeException("Can't register driver!");
        }
    }

可以看到,使用了DriverManager类。在DriverManager类中有静态代码块:

代码语言:javascript复制
	static { 
   
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

继续看loadInitialDrivers()

代码语言:javascript复制
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;
        }
        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;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = "   drivers);

        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);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) { 
   
                println("DriverManager.Initialize: load failed: "   ex);
            }
        }
    }

看下面方法:

代码语言:javascript复制
 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
代码语言:javascript复制
public static <S> ServiceLoader<S> load(Class<S> service) { 
   
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

使用了当前线程的classLoader。

代码语言:javascript复制
	private ServiceLoader(Class<S> svc, ClassLoader cl) { 
   
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

回到loadInitialDrivers()方法,继续往下看:

代码语言:javascript复制
AccessController.doPrivileged(new PrivilegedAction<Void>() { 
   
            public Void run() { 
   

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

进入loadedDrivers.iterator():

代码语言:javascript复制
public Iterator<S> iterator() { 
   
        return new Iterator<S>() { 
   

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();
            public boolean hasNext() { 
   
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }
            public S next() { 
   
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }
            public void remove() { 
   
                throw new UnsupportedOperationException();
            }
        };
    }

可以看到返回了一个重写了hasNext()和next()方法的匿名Iterator类。

代码语言:javascript复制
try{ 
   
        while(driversIterator.hasNext()) { 
   
            driversIterator.next();
           }
	 } 

在这里调用的都是重写方法。 由调用关系,最终可以看到下面的方法:

代码语言:javascript复制
		private boolean hasNextService() { 
   
            if (nextName != null) { 
   
                return true;
            }
            if (configs == null) { 
   
                try { 
   
                    String fullName = PREFIX   service.getName();
                    if (loader == null)
                    	//找到Driver.calss
                        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;
                }
                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 { 
   
            	//加载Driver.calss
                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
        }

可以看到,Driver.class是在hasNextService()中取到,nextService()中加载的:

代码语言:javascript复制
c = Class.forName(cn, false, loader);

这里的类加载器loader就是上面的

代码语言:javascript复制
ClassLoader cl = Thread.currentThread().getContextClassLoader();

现在真相大白了,在使用spi机制时,会使用当前线程的类加载器加载”META-INF/services/”下面的Driver.class。 在双亲委派模型下,类的加载是由下至上委托的,jdk无法加载其它文件夹下的类文件。但是在jdbc中,Driver要由供应商实现,所以需要进行加载,在spi使用方法中,使用线程上下文类加载器加载指定路径下的Driver.class文件,解决了这个问题。 JDBC破坏双亲委派的实现是使用父加载器加载指定路径下的class文件。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/169724.html原文链接:https://javaforall.cn

0 人点赞