精华内容
下载资源
问答
  • 如何自定义ClassLoader

    2020-08-21 00:22:29
    自定义ClassLoader之前要对ClassLoader的源码进行一些了解。当ClassLoader进行加载一个类时,会调用ClassLoader的loadClass方法。代码如下: /** * Loads the class with the specified <a href="#name">...

            在自定义ClassLoader之前要对ClassLoader的源码进行一些了解。当ClassLoader进行加载一个类时,会调用ClassLoader的loadClass方法。代码如下:

        /**
         * Loads the class with the specified <a href="#name">binary name</a>.
         * This method searches for classes in the same manner as the {@link
         * #loadClass(String, boolean)} method.  It is invoked by the Java virtual
         * machine to resolve class references.  Invoking this method is equivalent
         * to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,
         * false)</tt>}.
         *
         * @param  name
         *         The <a href="#name">binary name</a> of the class
         *
         * @return  The resulting <tt>Class</tt> object
         *
         * @throws  ClassNotFoundException
         *          If the class was not found
         */
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
        }
    
        /**
         * Loads the class with the specified <a href="#name">binary name</a>.  The
         * default implementation of this method searches for classes in the
         * following order:
         *
         * <ol>
         *
         *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
         *   has already been loaded.  </p></li>
         *
         *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
         *   on the parent class loader.  If the parent is <tt>null</tt> the class
         *   loader built-in to the virtual machine is used, instead.  </p></li>
         *
         *   <li><p> Invoke the {@link #findClass(String)} method to find the
         *   class.  </p></li>
         *
         * </ol>
         *
         * <p> If the class was found using the above steps, and the
         * <tt>resolve</tt> flag is true, this method will then invoke the {@link
         * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
         *
         * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
         * #findClass(String)}, rather than this method.  </p>
         *
         * <p> Unless overridden, this method synchronizes on the result of
         * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
         * during the entire class loading process.
         *
         * @param  name
         *         The <a href="#name">binary name</a> of the class
         *
         * @param  resolve
         *         If <tt>true</tt> then resolve the class
         *
         * @return  The resulting <tt>Class</tt> object
         *
         * @throws  ClassNotFoundException
         *          If the class could not be found
         */
        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 (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;
            }
        }
    
        /**
         * Finds the class with the specified <a href="#name">binary name</a>.
         * This method should be overridden by class loader implementations that
         * follow the delegation model for loading classes, and will be invoked by
         * the {@link #loadClass <tt>loadClass</tt>} method after checking the
         * parent class loader for the requested class.  The default implementation
         * throws a <tt>ClassNotFoundException</tt>.
         *
         * @param  name
         *         The <a href="#name">binary name</a> of the class
         *
         * @return  The resulting <tt>Class</tt> object
         *
         * @throws  ClassNotFoundException
         *          If the class could not be found
         *
         * @since  1.2
         */
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            throw new ClassNotFoundException(name);
        }

    由上面代码我们可以看到,当加载一个类时,会首先从已经加载的类里面去查找这个类。如果类未加载,且如果父加载器不为空,则调用父加载器的loadClass方法进行加载,如果父加载器为空,则调用BootStrap class loader加载。如果依然没有加载到类,则调用findClass方法。而findClass方法是需要子类重写的。所以我们只需要继承classLoader重写findClass方法就可以实现自定义ClassLoader。

    自定义ClassLoader实现代码:

    package com.qingcheng.classloader;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class CustomerClassLoader extends ClassLoader{
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            File file = new File("C:\\Users\\qingcheng\\Desktop\\", name.replace(".", "\\").concat(".class"));
            try (FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
               byte[] buffer = new byte[1024];
               int b = 0;
                while ((b = fis.read(buffer)) != -1) {
                    bos.write(buffer,0, b);
                }
                byte[] bytes = bos.toByteArray();
                return defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return super.findClass(name);
        }
    
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            CustomerClassLoader customerClassLoader = new CustomerClassLoader();
            Class clazz = customerClassLoader.loadClass("com.qingcheng.CustomerClass");
    
            Method method = clazz.getMethod("testMethod");
            method.invoke(clazz.newInstance(), null);
        }
    }
    

            这里需要注意的一点:我们需要调用defineClass方法将字节数组转化为class对象。

    展开全文
  • 文章目录1、Java虚拟机的类加载机制概述2、Java虚拟机中的类加载器2.1、查看类加载器加载的路径2.1.1、查看启动类加载器2.1.2...ClassLoader4、双亲委派模式4.1、重要方法4.1.1、loadClass()4.1.2、findClass()4.1.3...

    1、Java虚拟机的类加载机制概述

    虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是Java虚拟机的类加载机制

    类从被加载虚拟机内存中开始,到卸载出内存中为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。

    加载是类加载过程中的一个阶段,不要混淆这两个看起很相似的名词,在加载阶段虚拟机会采用类加载器来加载Class文件。

    2、Java虚拟机中的类加载器

    站在Java虚拟机的角度来讲,只存在两种不同的类加载器:

    • 一种是启动类加载器(Bootstrap ClassLoader),这个类加载器是使用C++语言实现,是虚拟机自身的一部分。
    • 另外一种就是其他的类加载器,这些加载器都是由Java语言实现,独立于虚拟机外部,并且全部都继承于抽象类java.lang.ClassLoader。

    站在Java开发人员角度来讲, 类加载器其实还可以分得更细致一些,绝大部分的Java程序都会使用到下面3种系统提供的类加载器。

    • 启动类加载器(Bootstrap
      ClassLoader),前面介绍过,这个类加载器负责将存放在<JAVA_HOME>\lib目录中的类库加载到虚拟机内存中
    • 扩展类加载器(Extension ClassLoader),这个类加载器负责加载<JAVA_HOME>\lib\ext目录中的类库。
    • 应用程序类加载器(Application
      ClassLoader),这个类加载器是ClassLoader中的getSystemLoader()方法的返回值,所以也称为系统类加载器。它负责加载ClassPath上所指定的类库。

    2.1、查看类加载器加载的路径

    通过下面代码可以看到类加载器加载的路径

    2.1.1、查看启动类加载器

    System.out.println(System.getProperty("sun.boot.class.path"));
    

    结果:

    D:\Program Files\Java\jdk\jre\lib\resources.jar;
    D:\Program Files\Java\jdk\jre\lib\rt.jar;
    D:\Program Files\Java\jdk\jre\lib\sunrsasign.jar;
    D:\Program Files\Java\jdk\jre\lib\jsse.jar;
    D:\Program Files\Java\jdk\jre\lib\jce.jar;
    D:\Program Files\Java\jdk\jre\lib\charsets.jar;
    D:\Program Files\Java\jdk\jre\lib\jfr.jar;
    D:\Program Files\Java\jdk\jre\classes
    

    2.1.2、查看扩展类加载器

    System.out.println(System.getProperty("java.ext.dirs"));
    

    结果:

    D:\Program Files\Java\jdk\jre\lib\ext;
    C:\Windows\Sun\Java\lib\ext
    

    3、类加载器之间的关系

    接下来探讨它们的加载顺序,先创建一个 ClassLoaderTest 的Java文件。
    先看看这个ClassLoaderTest类是被谁加载的:

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

    结果:

    sun.misc.Launcher$AppClassLoader@42a57993
    

    也就是说明 ClassLoaderTest.class文件是由AppClassLoader加载的。

    由于这个 ClassLoaderTest 类是我们自己编写的,那么int.class或者是String.class的加载是由谁完成的呢?
    我们可以在代码中尝试:

    public class ClassLoaderTest {
        public static void main(String[] args) {
        
            System.out.println(String.class.getClassLoader());
            System.out.println(int.class.getClassLoader());
            
        }
    }
    

    结果:

    null
    null
    

    结果打印的是空null,意思是int.class和String.class这类基础类没有类加载器加载?

    当然不是!
    int.class和String.class这类基础类是由Bootstrap ClassLoader加载的,最直观的解释就是String类是在java.lang包里,这个包在rt.jar里面,而这个jar是由Bootstrap ClassLoader加载的。要彻底明白这些,我们首先得知道一个前提。

    3.1、每个类加载器都有一个父加载器

    每个类加载器都有一个父加载器,比如加载 ClassLoaderTest.class是由AppClassLoader完成,那么AppClassLoader也有一个父加载器,怎么样获取呢?很简单,通过getParent方法。就像这样:

    public class ClassLoaderTest {
        public static void main(String[] args) {
        
            ClassLoader c =  ClassLoaderTest.class.getClassLoader();
            System.out.println(c.getParent());
            
        }
    }
    

    结果:

    sun.misc.Launcher$ExtClassLoader@28d93b30
    

    这个说明,AppClassLoader的父加载器是ExtClassLoader。那么ExtClassLoader的父加载器又是谁呢?

    public class ClassLoaderTest {
        public static void main(String[] args) {
        
            ClassLoader c =  ClassLoaderTest.class.getClassLoader();
            System.out.println(c.getParent().getParent());
            
        }
    }
    

    结果:

    null
    

    又是一个空,这表明ExtClassLoader没有父加载器。那么,为什么标题又是每一个加载器都有一个父加载器呢?这不矛盾吗?为了解释这一点,我们还需要看下面的一个基础前提。

    3.2、父加载器不是父类

    我们我们可以看一下ExtClassLoader和AppClassLoader的源码。(源码是精简过的,只展示了关键部分)

    ...
    static class AppClassLoader extends URLClassLoader{...}
    ...
    static class ExtClassLoader extends URLClassLoader{...}
    ...
    

    可以看见ExtClassLoader和AppClassLoader同样继承自URLClassLoader,但上面一小节代码中,为什么调用AppClassLoader的getParent()代码会得到ExtClassLoader的实例呢?

    可以看sun.misc.Launcher.class的部分关键源码:

    public Launcher() {
     ...
    	Launcher.ExtClassLoader var1;
    	try {
    		// 创建ExtClassLoader 实例。没有传参数,注意这个细节。下面解释
    		var1 = Launcher.ExtClassLoader.getExtClassLoader();
    	} catch (IOException var10) {
    		throw new InternalError("Could not create extension class loader", var10);
    	}
    
    	try {
    		// 创建AppClassLoader实例。将ExtClassLoader 实例作为参数,注意这个细节。下面解释
    		this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    	} catch (IOException var9) {
    		throw new InternalError("Could not create application class loader", var9);
    	}
    ...
    

    AppClassLoader.class的部分关键源码:

     static class AppClassLoader extends URLClassLoader {
            final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
    
            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() {
                    public Launcher.AppClassLoader run() {
                        URL[] var1x = var1 == null?new URL[0]:Launcher.pathToURLs(var2);
                        // 创建AppClassLoader时,传进来的参数var0,继续往AppClassLoader方法传。
                        return new Launcher.AppClassLoader(var1x, var0);
                    }
                });
            }
            // 传进来的 参数 ClassLoader var2。继续传往父类构造器,也就是URLClassLoader类的构造器
            AppClassLoader(URL[] var1, ClassLoader var2) {
                super(var1, var2, Launcher.factory);
                this.ucp.initLookupCache(this);
            }
            ...
    

    ExtClassLoader.class的部分关键源码:

      static class ExtClassLoader extends URLClassLoader {
            private static volatile Launcher.ExtClassLoader instance;
            // 在创建ExtClassLoader 是调用了父类构造器,也就是URLClassLoader类的构造器,第二个参数直接写了为null
            // 这是为什么呢?下面 URLClassLoader 源码中揭晓
            public ExtClassLoader(File[] var1) throws IOException {
                super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
                SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
            }	
            ...
    

    java.net.URLClassLoader的部分关键源码:

    // 注意看这个构造器的第二个参数,传进来的是 parent!
     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);
        }	
        ...
    

    需要注意的是sun.misc.Launcher.class的部分关键源码中的这几句话:

    Launcher.ExtClassLoader var1;
    
    var1 = Launcher.ExtClassLoader.getExtClassLoader();
    
    this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    

    结合AppClassLoader.class的部分关键源码和java.net.URLClassLoader的部分关键源码得知:

    Launcher.AppClassLoader.getAppClassLoader(var1)这个方法中的var1参数最终会传到URLClassLoder的构造方法中去,这个参数就表示了parent。

    代码已经说明了问题,AppClassLoader的parent是一个ExtClassLoader实例。

    ExtClassLoader并没有直接找到对parent的赋值。它调用了它的父类也就是URLClassLoder的构造方法并传递了3个参数。和AppClassLoader的方法一样,在创建ExtClassLoader 时,并没有传任何参数,而在调用了它的父类也就是URLClassLoder的构造方法并传递了3个参数时,第二个参数显示的写明了为(ClassLoader)null。看下方代码

    源码两个关键部分:
    ExtClassLoader.class的源码:

    public ExtClassLoader(File[] var1) throws IOException {
                super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
                SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
            }
           ...
    

    java.net.URLClassLoader的源码:

    public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
            super(parent);
               ...
    

    答案已经很明了了,ExtClassLoader的parent为null。

    上面张贴这么多代码也是为了说明AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是null。这符合我们之前编写的测试代码。

    3.3、再讲Bootstrap ClassLoader

    Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后呢,我们前面已经分析了,JVM初始化sun.misc.Launcher并创建Extension ClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。

    4、双亲委派模式

    双亲委派模式的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时(它搜索的范围没有找到所需的类),子加载器才会尝试自己取加载。

    类加载器双亲委派模型:
    在这里插入图片描述

    上面已经详细介绍了加载过程,但具体为什么是这样加载,我们还需要了解几个个重要的方法loadClass()、findLoadedClass()、findClass()、defineClass()。

    4.1、重要方法

    4.1.1、loadClass()

    通过指定的全限定类名加载class,它通过重载的loadClass(String,boolean)方法,它的源码是这样的:

    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 (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. 执行 findLoadedClass(name) 方法,检查这个class有没有被加载过,如果加载过了,直接返回,如果没有就进行加载。
    2. 判断父加载器是否为null,如果不是,就交给父加载去尝试加载。如果为null就交给Bootstrap ClassLoader去尝试加载。

    填坑:这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器的原因。

    另外代码解释了双亲委托的加载机制。

    4.1.2、findClass()

    根据名称或位置加载.class字节码

    4.1.3、defineClass()

    这个方法在编写自定义classloader的时候非常重要,它能将class二进制内容转换成Class对象,如果不符合要求的会抛出各种异常。

    5、自定义ClassLoader

    如果在某种情况下,我们需要动态加载一些东西,比如从D盘某个文件夹加载一个class文件,或者从网络上下载class主内容然后再进行加载,这样可以吗?

    如果要这样做的话,需要我们自定义一个ClassLoader。

    步骤描述

    1. 编写一个类继承自ClassLoader抽象类。
    2. 复写它的findClass()方法。
    3. 在findClass()方法中调用defineClass()。

    注意:一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。

    5.1、普通Java类

    我们写编写一个测试用的类文件,Test.java

    public class Test {
    	
    	public void say(){
    		System.out.println("Hello World");
    	}
    
    }
    

    然后将它编译过年class文件Test.class放到F:\这个路径下。

    5.2、自定义的ClassLoader

    我们编写自定义的ClassLoader的代码。

    public class CustomClassLoader extends ClassLoader {
    
        private String mLibPath;
    
        public CustomClassLoader(String path) {
            mLibPath = path;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            String fileName = getFileName(name);
    
            File file = new File(mLibPath,fileName);
    
            try {
                FileInputStream is = new FileInputStream(file);
    
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                int len = 0;
                try {
                    while ((len = is.read()) != -1) {
                        bos.write(len);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
                byte[] data = bos.toByteArray();
                is.close();
                bos.close();
    
                return defineClass(name,data,0,data.length);
    
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            return super.findClass(name);
        }
    
        //获取要加载 的class文件名
        private String getFileName(String name) {
            int index = name.lastIndexOf('.');
            if(index == -1){
                return name+".class";
            }else{
                return name.substring(index+1)+".class";
            }
        }
    }
    

    5.3、编写测试类

    现在编写测试代码。如果调用Test对象的say方法,它会输出"Say Hello"这条字符串。但现在是我们把Test.class放置在应用工程所有的目录之外,我们需要加载它,然后执行它的方法。具体效果如何呢?

    public class ClassLoaderTest {
    
        public static void main(String[] args) {
    
            //创建自定义classloader对象。
            CustomClassLoader diskLoader = new CustomClassLoader("F:\\");
            try {
                //加载class文件
                Class c = diskLoader.loadClass("com.learn.classLoader.Test");
    
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        Method method = c.getDeclaredMethod("say");
                        //通过反射调用Test类的say方法
                        method.invoke(obj);
                    } catch (InstantiationException | IllegalAccessException
                            | NoSuchMethodException
                            | SecurityException |
                            IllegalArgumentException |
                            InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    

    结果:

    Say Hello
    

    可以看到,Test类的say方法正确执行,也就是我们自定义写的CustomClassLoader 编写成功。

    6、另外一个类加载器:线程上下文类加载器

    6.1、概念

    线程上下文类加载器(Thread Context ClassLoader),这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个。Java 应用运行的初始线程的上下文类加载器是应用程序类加载器(AppClassLoader)。

    6.2、绕过双亲委派模式

    有了这个类加载器,父类加载器就可以请求子类加载器去完成类加载的动作,这种行为实际上就是打破了双亲委派模型的层次结构来逆向使用类加载器
    最典型的例子就是:JNDI、JDBC。

    7、总结

    • Java虚拟机类加载机制
    • Java的几种类加载器
    • 各个加载器扫描的范围
    • 各个加载器之间的关系
    • 如何自定义一个ClassLoader类加载器

    8、拓展之Class解密类加载器

    常见的用法是将Class文件按照某种加密手段进行加密,然后按照规则编写自定义的ClassLoader进行解密,这样我们就可以在程序中加载特定了类,并且这个类只能被我们自定义的加载器进行加载,提高了程序的安全性。
    详情请参照该文章:Java实现自定义classLoader类加载器动态解密class文件


    技 术 无 他, 唯 有 熟 尔。
    知 其 然, 也 知 其 所 以 然。
    踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。


    展开全文
  • class文件 被 ClassLoader装载到内存 -------------> 调用JIT编译器和字节码解释器 -------> 执行引擎(调用 os硬件)双亲委派先来说双亲委派:双亲委派从下到上是在缓存里查找是否已经加载了,如果加载了,...

    542cb12064f2d33abe8a63edb7f44d28.png

    x . java ------> javac -------> x . class -------> class文件 被 ClassLoader装载到内存 -------------> 调用JIT编译器和字节码解释器 -------> 执行引擎(调用 os硬件)

    双亲委派

    先来说双亲委派:双亲委派从下到上是在缓存里查找是否已经加载了,如果加载了,就return

    如果没有则,从上到下是尝试加载,bootstrap是加载rt.jar里的类,ext是加载ext下的*.jar里的类,AppClassLoader是加载自己写的在classpath下的 .class

    为什么要使用双亲委派?

    为了安全。假如不使用双亲委派这个模型的话,自己写一个java.lang.String的类。然后自己写一个类加载器把自己写的java.lang.String加载到内存的话,然后把自己写的类加载器和自己写的java.lang.String打包成一个类库,交给客户,客户在输入密码的时候,密码会存成String类型的对象,但是这个java.lang.String是我自己写的,那完全有可能在这个里面加一点什么东西,把用户输入的密码拿到,发个邮件发给我自己,这样我就可以拿到用户的密码了。

    当使用了双亲委派的时候,就会从下往上进行在缓存里检查类是否已经加载,而不是一下子就直接用自己写的类加载器去直接加载,而是从下往上进行在缓存里检查类是否已经加载。这时候程序看见java.lang.String就会警惕,检查到rt.jar里有一个一模一样的java.lang.String,那就一直往上委派,一直到 BootStrap,BootStrap一看,就会去加载rt.jar里的那个Oracle写的java.lang.String,把真的String加载到内存然后返回,而不是把自己写的String加载到内存。


    如何自定义classloader和怎样打破双亲委派?

    假如有看classloader的源码的话,都知道双亲委派的逻辑就在loadClass这个方法里,那么自定义自己的classloader的关键就是重写这个findClass方法。

    而打破双亲委派只需要重写loadClass就可以了。重写的逻辑不应该有双亲委派的逻辑。

    Spring和tomcat都有自己的ClassLoader,热部署原理也是一样。

    展开全文
  • 使用自定义 classloader 的正确姿势 详细的原理就不多说了,...到底如何正确自定义ClassLoader, 需要注意什么 ExtClassLoader 是什么鬼 自定义ClassLoader具体是如何加载 类的。。 直接上代码...

    详细的原理就不多说了,网上一大把, 但是, 看了很多很多, 即使看了jdk 源码, 说了罗里吧嗦, 还是不很明白:

     

    到底如何正确自定义ClassLoader, 需要注意什么

    ExtClassLoader 是什么鬼

    自定义ClassLoader具体是如何加载 类的。。

     

    直接上代码:

    import java.io.ByteArrayOutputStream;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.nio.ByteBuffer;
    import java.nio.channels.Channels;
    import java.nio.channels.FileChannel;
    import java.nio.channels.WritableByteChannel;
    import java.util.Date;
    
    import com.lk.AbcBean;
    
    
    public class ClassLoaderLK extends ClassLoader {
        /**
         * @param args
         */
        public static void main(String[] args) {
    //        this.class.getSystemClassLoader();
            
            String ext = "java.ext.dirs";
            System.out.println("java.ext.dirs :\n" + System.getProperty(ext));
            String cp = "java.class.path";
            System.out.println("java.class.path :\n" + System.getProperty(cp));
            
            ClassLoader currentClassloader = ClassLoaderLK.class.getClassLoader();
            
            String pp = "d:\\testcl\\";
            ClassLoaderLK cl = new ClassLoaderLK(currentClassloader, pp);
            
            System.out.println();
            System.out.println("currentClassloader is " + currentClassloader);
            System.out.println();
            String name = "com.lk.AbcBean.class";
            name = "com.lk.AbcBean";
            try {
                Class<?> loadClass = cl.loadClass(name);
                
                Object object = loadClass.newInstance();
                
    //            AbcBean ss = (AbcBean) object; // 无法转换的 (1)
    //            ss.greeting();  (1)
                
                System.out.println();
                System.out.println(" invoke some method !");
                System.out.println();
                
                Method method = loadClass.getMethod("greeting");
                method.invoke(object);
                
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
        
        private ClassLoader parent = null; // parent classloader
        private String path;
    
        public ClassLoaderLK(ClassLoader parent, String path) {
            super(parent);
            this.parent = parent; // 这样做其实是无用的
            this.path = path;
        }
    
        public ClassLoaderLK(String path) {
            this.path = path;
        }
        
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
    //        return super.loadClass(name);
            Class<?> cls = findLoadedClass(name);
            if (cls == null) {
    //            cls = getSystemClassLoader().loadClass(name); (2)// SystemClassLoader 会从classpath下加载
    //            if (cls == null) {(2)
                // 默认情况下, 当前cl的parent是 SystemClassLoader, 
                // 而当前cl的parent的parent 才是ExtClassLoader
                    ClassLoader parent2 = getParent().getParent();
    //                System.out.println("Classloader is : " + parent2); 
                    
                    try {
                        System.out.println("try to use ExtClassLoader to load class : " + name); 
                        cls = parent2.loadClass(name);
                    } catch (ClassNotFoundException e) {
                        System.out.println("ExtClassLoader.loadClass :" + name + " Failed"); 
                    }
    //            }(2)
                
                if (cls == null) {
                    System.out.println("try to ClassLoaderLK load class : " + name); 
                    cls = findClass(name);
                    
                    if (cls == null) {
                        System.out.println("ClassLoaderLK.loadClass :" + name + " Failed"); 
                    } else {
                        System.out.println("ClassLoaderLK.loadClass :" + name + " Successful"); 
                    }
                    
                } else {
                    System.out.println("ExtClassLoader.loadClass :" + name + " Successful"); 
                }
            }
            return cls;
        }
        
        @Override
        @SuppressWarnings("rawtypes")
        protected Class<?> findClass(String name) throws ClassNotFoundException {
    //        return super.findClass(name);
            System.out.println( "try findClass " + name);
            InputStream is = null;
            Class class1 = null;
            try {
                String classPath = name.replace(".", "\\") + ".class";
    //            String[] fqnArr = name.split("\\."); // split("."); 是不行的, 必须split("\\.")
    //            if (fqnArr == null || fqnArr.length == 0) {
    //                System.out.println("ClassLoaderLK.findClass()");
    //                fqnArr = name.split("\\.");
    //            } else {
    //                System.out.println( name  +  fqnArr.length);
    //            }
                
                String classFile = path + classPath;
                byte[] data = getClassFileBytes(classFile );
                
                class1 = defineClass(name, data, 0, data.length);
                if (class1 == null) {
                    System.out.println("ClassLoaderLK.findClass() ERR ");
                    throw new ClassFormatError();
                }
                
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return class1;
        }
    
        private byte[] getClassFileBytes(String classFile) throws Exception {
            FileInputStream fis = new FileInputStream(classFile );
            FileChannel fileC = fis.getChannel();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            WritableByteChannel outC = Channels.newChannel(baos);
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            while (true) {
                int i = fileC.read(buffer);
                if (i == 0 || i == -1) {
                    break;
                }
                buffer.flip();
                outC.write(buffer);
                buffer.clear();
            }
            fis.close();
            return baos.toByteArray();
        }
        
    }

     

    随便的一个java 类, 简单起见,就写一个bean吧

    package com.lk;
    
    import java.util.Date;
    
    public class AbcBean {
        
        @Override
        public String toString() {
            return "AbcBean [name=" + name + ", age=" + age + "]";
        }
        
        String name;
        int age;
        Date birthDay;
        
        public Date getBirthDay() {
            return birthDay;
        }
        public void setBirthDay(Date birthDay) {
            this.birthDay = birthDay;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        
        public void greeting() {
            System.out.println("AbcBean.greeting()");
        }
    }

     

    直接执行,结果:

    currentClassloader is sun.misc.Launcher$AppClassLoader@513cf0
    
    try to use ExtClassLoader to load class : com.lk.AbcBean
    ExtClassLoader.loadClass :com.lk.AbcBean Failed
    try to ClassLoaderLK load class : com.lk.AbcBean
    try findClass com.lk.AbcBean
    java.io.FileNotFoundException: d:\testcl\com\lk\AbcBean.class (系统找不到指定的路径。)
        at java.io.FileInputStream.open(Native Method)
        at java.io.FileInputStream.<init>(FileInputStream.java:138)
        at java.io.FileInputStream.<init>(FileInputStream.java:97)
        at ClassLoaderLK.getClassFileBytes(ClassLoaderLK.java:172)
        at ClassLoaderLK.findClass(ClassLoaderLK.java:145)
        at ClassLoaderLK.loadClass(ClassLoaderLK.java:112)
        at ClassLoaderLK.main(ClassLoaderLK.java:38)
    Exception in thread "main" java.lang.NullPointerException
        at ClassLoaderLK.main(ClassLoaderLK.java:40)
    ClassLoaderLK.loadClass :com.lk.AbcBean Failed

     

    将com.lk 目录全部复制到 d:\\testcl\\ 下,

    currentClassloader is sun.misc.Launcher$AppClassLoader@513cf0
    
    try to use ExtClassLoader to load class : com.lk.AbcBean
    ExtClassLoader.loadClass :com.lk.AbcBean Failed
    try to ClassLoaderLK load class : com.lk.AbcBean
    try findClass com.lk.AbcBean
    try to use ExtClassLoader to load class : java.lang.Object
    ExtClassLoader.loadClass :java.lang.Object Successful
    ClassLoaderLK.loadClass :com.lk.AbcBean Successful
    
     invoke some method !
    
    try to use ExtClassLoader to load class : java.lang.String
    ExtClassLoader.loadClass :java.lang.String Successful
    try to use ExtClassLoader to load class : java.lang.System
    ExtClassLoader.loadClass :java.lang.System Successful
    try to use ExtClassLoader to load class : java.io.PrintStream
    ExtClassLoader.loadClass :java.io.PrintStream Successful
    AbcBean.greeting()

     

     

    将AbcBean打包成 jar 放置到 jdk 下的jre 的ext目录 ( 打包成 zip 也是可行的! 但是rar是不行的!!!  why ? 估计zip和jar都是使用的java 的zip流, 而rar是后面产生的新格式,故没有被支持。另外, 仅仅拷贝class 过去也是不行的! )

    执行结果:

    currentClassloader is sun.misc.Launcher$AppClassLoader@513cf0
    
    try to use ExtClassLoader to load class : com.lk.AbcBean
    ExtClassLoader.loadClass :com.lk.AbcBean Successful
    
     invoke some method !
    
    AbcBean.greeting()

     

    可见ExtClassLoader 是如何作用了的吧!!

     

    总结,

    1 从 invoke some method 前后的日志,可见 类加载 的大致过程。

    2 代码中 (1), 的部分是注释了的, 因为 不同类加载器加载的类是 不能直接cast的。。   但把(1),(2) 同时解开注释, 又可以了, 这是因为他们都是使用的系统类加载器, 自定义的类加载器相当于没有生效。。( 这个当然不是我们需要的结果。)

    3 loadClass, findClass 两个方法的复写是必须的。 上面代码中的loadClass 的写法其实有点问题, 参照classloader 源码, 应该还需要一步: parent加载不上了, 使用bootstrap 加载, 不过感觉一般应该是用不上的—— 谁需要去替换 jdk 的rt.jar 的类 ??

    4 ExtClassLoader 是去加载 jdk 下 jre  ext 目录的类似jar 的文件——  后缀是不是jar 不要紧, 内容是jar就行了。

     

    posted on 2016-07-04 15:21 CanntBelieve 阅读(...) 评论(...) 编辑 收藏

    转载于:https://www.cnblogs.com/FlyAway2013/p/5640586.html

    展开全文
  • 自定义classLoader思考

    2018-11-22 20:47:00
    jvm对于类实例的区分 基于完全限定名+classLoader 不同的classLoader可以加载...spring boot jar中的lib包如何完全使用自定义classLoader加载? ==> 在main函数入口, 默认使用的是appClassLoader, 如果不做特...
  • 不过,有些场景下,我们想要从自定义的类加载器中加载类时(比如自定义加载器和父加载器加载的是磁盘上两个不同目录下的类文件时,如果此时二者目录下都放置着同名的类文件,就会发生覆盖),这个机制就会给我们带来...
  • 需要自定义classloader。 ClassLoader:加载各种class文件到JVM中。ClassLoader是抽象类。 类的加载过程分为加载阶段、连接阶段、初始化。 加载阶段:寻找class文件。 连接阶段:验证class...
  • 可是一圈下来,新手们依然不知道如何自定义一个类加载器,来生动的展现什么是类加载器。首先我们在E:upload下新建一个a/BB.java文件。代码如下:packagea;public classBB {privateString a;publicBB(){}publicBB...
  • 1.有这样一个变态需求,有两个不同版本的dubbo.jar包,我们需要在项目中动态指定要运行哪个版本,改如何做? 不同版本jar包准备 package classloader; public class Dubbo { public void invoke() { System.err....
  •  JVM是如何知道java.lang包中的类的?JVM又是如何知道我们应用中的类的?我们的应用中明明是有某个类, 但是JVM却抛出ClassNotFoundException,这是为什么?XxxImpl类已经实现了接口Xxx,但是却抛出XxxImpl does ...
  • 其中,在加载阶段,虚拟机需要完成以下三件事情(也就是自定义ClassLoader需要完成的内容): 通过一个类的全限定名(包名+类名)来获取定义此类的二进制字节流。 将这个字节流所代表的静态存储结构...
  • 如何保护我们的源代码,实际上,应该有几种方法可以使用:1、使用代码混淆器 2、重载应用服务器的classloader 使用代码混淆器proguard进行代码混淆 1.首先下载proGuard.zip到本地: proguard4.5beta4.tar.zip解压...
  • 如何实现自定义ClassLoader

    千次阅读 2012-09-14 11:21:55
    原文链接:... 作者:Kert 本文选自:开放系统世界——赛迪网 2002年09月23日   ClassLoader,顾名思义是用来Load Class的,即加载Java类。ClassLoader读入一个字节数组,并且经过
  • 自定义ClassLoader 如何自定义 自己的 classLoader,其实很简单;只需要继承ClassLoader,然后重写一些方法实现自己的业务就可以了。 下面以 加载 ASM(等其他方式)产生的 class 的 byte数组 生成 Class 对象 为...
  • 如何实现自定义ClassLoader

    千次阅读 2005-07-01 11:07:00
    来源: ...ClassLoader读入一个字节数组,并且经过处理返回一个JVM内部可以识别的Class实例。Java虚拟机使用一套复杂但有效的方式来进行这一个至关重要的过程处理,并且提供了许多灵
  • 如何配置自定义的System ClassLoader

    千次阅读 2007-09-19 21:40:00
    ClassLoader.getSystemClassLoader() 第一次被调用的时候,虚拟机检查系统属性java.system.class.loader是否已经定义。如果已经定义,则使用java.system.class.loader定义的类作为system class loader. 因此,只要...
  • 如果又是个需要远程通讯的OSGi应用的话,那么反序列化的classloader问题几乎可以肯定是会碰到的,来看看在如今流行的两种序列化、反序列化协议:java/hessian中如何使用自定义classloader。 java/hessian...
  • 接着看tomcat如何对此内存泄漏进行监控的,要判断WebappClassLoader会不会导致内存泄漏 只需判断WebappClassLoader有没有被GC回收即可 。 在Java中有一种引用叫 弱引用 ,它能很好判断WebappClassLoader有没有被GC...
  • 参考:如何实现自定义ClassLoader http://blog.csdn.net/LongDick/archive/2007/11/09/1875644.aspx import java.io.BufferedInputStream;import java.io.ByteArrayOutputStream;import java.io.File;import ...

空空如也

空空如也

1 2 3 4 5 ... 12
收藏数 230
精华内容 92
关键字:

如何自定义classloader