精华内容
下载资源
问答
  • 本篇文章主要给大家讲述了Java中ClassLoader类加载的原理以及用法总结,一起学习下。
  • java中ClassLoader

    2014-03-07 04:47:35
    1.java classloader 的概述 2.java classloader 的分类 3.自定义 java classloader
  • Enumeration<URL> getResources(String ...api上说第一个获得name的所有资源是指的是当前classpath路径下的所有name的资源,但是我测试的时候只能获得classpath根路径下的name的url,路径下的目录name的url获取不了。
  • 一看你就懂,超详细java中ClassLoader详解

    万次阅读 多人点赞 2017-02-10 19:26:54
    ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLoader的具体作用就是将class文件加载...

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

    ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了。但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。想想也是的,一次性加载那么多jar包那么多class,那内存不崩溃。本文的目的也是学习ClassLoader这种加载机制。

    备注:本文篇幅比较长,但内容简单,大家不要恐慌,安静地耐心翻阅就是

    Class文件的认识

    我们都知道在Java中程序是运行在虚拟机中,我们平常用文本编辑器或者是IDE编写的程序都是.java格式的文件,这是最基础的源码,但这类文件是不能直接运行的。如我们编写一个简单的程序HelloWorld.java

    public class HelloWorld{
       
    
    	public static void 
    展开全文
  • 浅析java中ClassLoader如何加载Class

    千次阅读 2018-04-06 14:02:17
    我的博客地址:https://blog.csdn.net/qq_41907991 ClassLoader是...本文试图以最浅显易懂的方式来讲解ClassLoader,希望不对该机制了解的朋友能起一点点作用。 首先来说,要了解ClassLoader,就要知道它是用来做什...

                       我的博客地址:https://blog.csdn.net/qq_41907991

                     ClassLoader是一个经常出现又让很多人望而却步的词。本文试图以最浅显易懂的方式来讲解ClassLoader,希望不对该机制了解的朋友能起一点点作用。

                    首先来说,要了解ClassLoader,就要知道它是用来做什么的,顾名思义,他就是用来加载Class文件到JVM,已供程序使用的。我们指定,java程序可以动态加载类定义,而这个动态机制就是通过ClassLoader来实现的,所以,可想而知ClassLoder的重要性。

                    看到这里,可能有的朋友会问ClassLoader是用来加载类到JVM中去的,那么ClassLoader又是如何被加载的呢?难道它不是java的类?没错,这里确实有一个ClassLoader不是java语言所编写的,而是JVM实现的一部分,这个ClassLoader就是bootstrapclassloader(启动类加载器),这个ClassLoader在JVM运行的时候加载java核心API以满足java程序最基本的需求,其中就包括用户自定义的ClassLoader,这里所谓的用户自定义是指java程序编写的ClassLoader。

                    主要分为以下两个:一个是ExtClassLoader,另一个是AppClassLoader。ExtClassLoader是用来加载java的扩展API的,AppClassLoader是用来加载用户机器上CLASSPATH设置目录中的Class。通常在没有指定ClassLoader的情况下,程序自定义的类就由该ClassLoader进行加载。

                    当运行一个程序是,JVM启动,bootstrap ClassLoader 加载java的核心API(ExtClassLoader跟AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载用户的自定义类。这就是一个程序最基本的加载流程。

                    ClassLoader在加载类时,使用了双亲委托模式,下面对这种模式进行简单阐述。

                    每一个自定义的ClassLoader都必须继承ClassLoader这个抽象类,而每个ClassLoader都会由一个父类ClassLoader,我们可以看以下ClassLoader这个抽象类中由一个getParent()方法,注意,这个parent不是指的被继承的类,而是在实例化该ClassLoader时指定的一个ClassLoader,如果这个parent为null,那么就默认该ClassLoader的parent是bootstrapclassloader,这个parent有什么用呢?

                    我们可以考虑这种情况,假设我们自定义了一个ClassLoader,我们使用它去加载String类,那么这里的String是否会被我们自定义的ClassLoader加载呢?事实上并不会,而是由bootstrapclassloader进行加载,为什么会这样?实际上这就是双亲委托模式的原因,因为任何一个自定义的bootstrapclassloader在对类进行加载前,都会委托它的parenClassLoader进行加载,只有当parenClassLoade无法加载时,才会由自己加载。上面讲过,ClassLoade的parent为null时,ClassLoader的parent就是bootstrapclassloader,因此最终委托到bootstrapclassloader时,就是返回String的Class。

                    为什么要使用这种双亲委托模式呢?

                    1:避免重复加载,当父亲已经加载过该类的时候,就没有必要再加载一次了

                    2:考虑到安全因素,试想一下,如果不使用这种模式,我们可以随时的用自定的String来动态替代java核心API中定义类型,这样存在非常大的安全隐患

                    

                    

    展开全文
  • ClassLoader简称类加载器,主要用于加载和校验编译后的Java文件(即:以.class结尾的文件); 有哪些类加载器(ClassLoader) AppClassLoader(应用类加载器) ExtClassLoader(扩展类加载器注意:JDK1.8后被修改为平台类...

    什么是ClassLoader

    当前环境JDK1.8、eclipse

    ClassLoader简称类加载器,主要用于加载和校验编译后的Java文件(即:以.class结尾的文件);

    有哪些类加载器(ClassLoader)

    1. AppClassLoader(应用类加载器)
    2. ExtClassLoader(扩展类加载器注意:JDK1.8后被修改为平台类加载器)
    3. BootstrapClassLoader(启动类加载器)

    获取类加载器

    通过简单的demo来得到类加载器

    /**
     * @description User类主要用于测试当前的类加载器
     * @author hy
     * @date 2019-08-08
     */
    public class User {
    	public static void main(String[] args) {
    		User user = new User();
    		Class<? extends User> userClass = user.getClass();
    		System.out.println(userClass.getClassLoader());
    		System.out.println(userClass.getClassLoader().getParent());
    		System.out.println(userClass.getClassLoader().getParent().getParent());
    	}
    }
    

    结果显示:
    sun.misc.Launcher$AppClassLoader@73d16e93

    sun.misc.Launcher$ExtClassLoader@15db9742
    null//这里涉及到底层所以返回null

    类加载器的加载顺序

    由于当前的类加载器使用双亲模式

    1. 首先加载系统类加载器,此时系统类加载器会判断当前类是否已近是当前系统已定的类,如果是加载系统类,不会初始化被加载的类,不存在则由ExtClassLoader加载
    2. ExtClassLoader检测加载,一般都是lib包中,不存在则交给AppClassLoader加载
    3. AppClassLoader检测加载类,当前应用加载器会从当前应用中(就是启动类或者整个程序中)查找需要加载的类,存在即加载程序的类,不存在交给用户定义的类加载器处理

    使用双亲模式的好处,可以保护Java程序的安全,防止非法的加载Class

    用户可以使用自定义类加载器用来实现对不同位置的类的加载和调用

    自定义类加载器

    
    public class MyClassLoader extends ClassLoader {
    	/**
    	 * @description 解析类文件获得当前解析后的类
    	 * @param fileName 档期类的完全限定名
    	 * @return 使用类加载器后获取的类
    	 * @throws Exception 解析错误
    	 */
    	public Class<?> transClassFile(String fileName) throws Exception {
    		byte[] classBytes = this.loadBinaryClassFile();
    		//主要通过父类来解析当前的class二进制文件
    		Class<?> clazz = super.defineClass(fileName, classBytes, 0, classBytes.length);
    		return clazz;
    	}
    
    	/**
    	 * @description 读取并加载类文件获得byte数组返回
    	 * @return byte[] 数组
    	 * @throws Exception 读取失败
    	 */
    	private byte[] loadBinaryClassFile() throws Exception {
    		String classFilePath = "D:/Person.class";// 设置当前class文件的路径
    		File classFile = new File(classFilePath);
    		System.out.println("文件的长度:" + classFile.length());
    		if (!classFile.exists()) {// 判断文件是否
    			throw new FileNotFoundException("文件不存在。。。。。。。。。。");
    		}
    		InputStream fis = null;
    		ByteArrayOutputStream bos = null;// 内存流
    		byte[] bytes = new byte[(int) classFile.length()]; // 设置缓冲区
    
    		byte[] readBytes = null;
    		try {
    			bos = new ByteArrayOutputStream();
    			// 开始实例化流,并加载流
    			fis = new FileInputStream(classFile);// 这里必须为文件的实际路劲
    			while (( fis.read(bytes)) != -1) {
    				bos.write(bytes);
    			}
    			readBytes = bos.toByteArray();
    			System.out.println("byte数组的长度:" + readBytes.length);
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			if (fis != null) {
    				fis.close();
    			}
    			if (bos != null) {
    				bos.close();
    			}
    		}
    		return readBytes;
    	}
    }
    
    

    当前的Person中的内容为

    package com.hy.classloader.demo;
    public class Person{
    	public Person(){
    		System.out.println("Person 类被创建实例");
       }
    }
    

    测试自定义类加载器

    将创建好的Person.java复制到D盘,使用cmd来编译Person.java并获得Person.class文件

    编写测试类

    package com.hy.classloader.demo;
    
    public class MyClassLoaderTest {
    	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
    		MyClassLoader myClassLoader = new MyClassLoader();
    		try {
    			Class<?> loadClass = myClassLoader.transClassFile("com.hy.classloader.demo.Person");
    			Object newInstance = loadClass.newInstance();//实例化当前Class文件并创建当前文件的实例
    			System.out.println(newInstance);
    		} catch (Exception e) {
    			System.out.println("使用自定义类加载器加载类失败。。。。");
    			e.printStackTrace();
    		}
    	}
    }
    

    结果:Person 类被创建实例

    总结:
    1.使用自定义的类加载器的时候需要继承ClassLoader来实现class的加载
    2.在加载的过程中须要使用 ByteArrayOutputStream 内存流
    3.解析的时候需要使用父类来解析获得二进制信息以此得到Class的信息(必须调用 super.defineClass(fileName, classBytes, 0, classBytes.length);)
    4.使用自定义的类加载器可以没有限制的在其他的地方加载类

    展开全文
  • ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLoader的具体作用就是将class文件加载...

    转自:http://blog.csdn.net/briblue/article/details/54973413

    ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了。但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。想想也是的,一次性加载那么多jar包那么多class,那内存不崩溃。本文的目的也是学习ClassLoader这种加载机制。

    备注:本文篇幅比较长,但内容简单,大家不要恐慌,安静地耐心翻阅就是

    Class文件的认识

    我们都知道在Java中程序是运行在虚拟机中,我们平常用文本编辑器或者是IDE编写的程序都是.java格式的文件,这是最基础的源码,但这类文件是不能直接运行的。如我们编写一个简单的程序HelloWorld.java

    public class HelloWorld{
    
        public static void main(String[] args){
            System.out.println("Hello world!");
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如图:
    这里写图片描述
    然后,我们需要在命令行中进行java文件的编译

    javac HelloWorld.java
    • 1

    这里写图片描述
    可以看到目录下生成了.class文件

    我们再从命令行中执行命令:

    java HelloWorld
    • 1

    这里写图片描述

    上面是基本代码示例,是所有入门JAVA语言时都学过的东西,这里重新拿出来是想让大家将焦点回到class文件上,class文件是字节码格式文件,java虚拟机并不能直接识别我们平常编写的.java源文件,所以需要javac这个命令转换成.class文件。另外,如果用C或者PYTHON编写的程序正确转换成.class文件后,java虚拟机也是可以识别运行的。更多信息大家可以参考这篇

    了解了.class文件后,我们再来思考下,我们平常在Eclipse中编写的java程序是如何运行的,也就是我们自己编写的各种类是如何被加载到jvm(java虚拟机)中去的。

    你还记得java环境变量吗?

    初学java的时候,最害怕的就是下载JDK后要配置环境变量了,关键是当时不理解,所以战战兢兢地照着书籍上或者是网络上的介绍进行操作。然后下次再弄的时候,又忘记了而且是必忘。当时,心里的想法很气愤的,想着是–这东西一点也不人性化,为什么非要自己配置环境变量呢?太不照顾菜鸟和新手了,很多菜鸟就是因为卡在环境变量的配置上,遭受了太多的挫败感。

    因为我是在Windows下编程的,所以只讲Window平台上的环境变量,主要有3个:JAVA_HOMEPATHCLASSPATH

    JAVA_HOME

    指的是你JDK安装的位置,一般默认安装在C盘,如

    C:\Program Files\Java\jdk1.8.0_91
    • 1

    PATH

    将程序路径包含在PATH当中后,在命令行窗口就可以直接键入它的名字了,而不再需要键入它的全路径,比如上面代码中我用的到javacjava两个命令。
    一般的

    PATH=%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;%PATH%;
    • 1

    也就是在原来的PATH路径上添加JDK目录下的bin目录和jre目录的bin.

    CLASSPATH

    CLASSPATH=.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
    • 1

    一看就是指向jar包路径。
    需要注意的是前面的.;.代表当前目录。

    环境变量的设置与查看

    设置可以右击我的电脑,然后点击属性,再点击高级,然后点击环境变量,具体不明白的自行查阅文档。

    查看的话可以打开命令行窗口

    
    echo %JAVA_HOME%
    
    echo %PATH%
    
    echo %CLASSPATH%
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    好了,扯远了,知道了环境变量,特别是CLASSPATH时,我们进入今天的主题Classloader.

    JAVA类加载流程

    Java语言系统自带有三个类加载器:
    - Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录。
    - Extention ClassLoader 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
    - Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。

    我们上面简单介绍了3个ClassLoader。说明了它们加载的路径。并且还提到了-Xbootclasspath-D java.ext.dirs这两个虚拟机参数选项。

    加载顺序?

    我们看到了系统的3个类加载器,但我们可能不知道具体哪个先行呢?
    我可以先告诉你答案
    1. Bootstrap CLassloder
    2. Extention ClassLoader
    3. AppClassLoader

    为了更好的理解,我们可以查看源码。
    sun.misc.Launcher,它是一个java虚拟机的入口应用。

    public class Launcher {
        private static Launcher launcher = new Launcher();
        private static String bootClassPath =
            System.getProperty("sun.boot.class.path");
    
        public static Launcher getLauncher() {
            return launcher;
        }
    
        private ClassLoader loader;
    
        public Launcher() {
            // Create the extension class loader
            ClassLoader extcl;
            try {
                extcl = ExtClassLoader.getExtClassLoader();
            } catch (IOException e) {
                throw new InternalError(
                    "Could not create extension class loader", e);
            }
    
            // Now create the class loader to use to launch the application
            try {
                loader = AppClassLoader.getAppClassLoader(extcl);
            } catch (IOException e) {
                throw new InternalError(
                    "Could not create application class loader", e);
            }
    
            //设置AppClassLoader为线程上下文类加载器,这个文章后面部分讲解
            Thread.currentThread().setContextClassLoader(loader);
        }
    
        /*
         * Returns the class loader used to launch the main application.
         */
        public ClassLoader getClassLoader() {
            return loader;
        }
        /*
         * The class loader used for loading installed extensions.
         */
        static class ExtClassLoader extends URLClassLoader {}
    
    /**
         * The class loader used for loading from java.class.path.
         * runs in a restricted security context.
         */
        static class AppClassLoader extends URLClassLoader {}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    源码有精简,我们可以得到相关的信息。
    1. Launcher初始化了ExtClassLoader和AppClassLoader。
    2. Launcher中并没有看见BootstrapClassLoader,但通过System.getProperty("sun.boot.class.path")得到了字符串bootClassPath,这个应该就是BootstrapClassLoader加载的jar包路径。

    我们可以先代码测试一下sun.boot.class.path是什么内容。

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

    得到的结果是:

    C:\Program Files\Java\jre1.8.0_91\lib\resources.jar;
    C:\Program Files\Java\jre1.8.0_91\lib\rt.jar;
    C:\Program Files\Java\jre1.8.0_91\lib\sunrsasign.jar;
    C:\Program Files\Java\jre1.8.0_91\lib\jsse.jar;
    C:\Program Files\Java\jre1.8.0_91\lib\jce.jar;
    C:\Program Files\Java\jre1.8.0_91\lib\charsets.jar;
    C:\Program Files\Java\jre1.8.0_91\lib\jfr.jar;
    C:\Program Files\Java\jre1.8.0_91\classes
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看到,这些全是JRE目录下的jar包或者是class文件。

    ExtClassLoader源码

    如果你有足够的好奇心,你应该会对它的源码感兴趣

    /*
         * The class loader used for loading installed extensions.
         */
        static class ExtClassLoader extends URLClassLoader {
    
            static {
                ClassLoader.registerAsParallelCapable();
            }
    
            /**
             * create an ExtClassLoader. The ExtClassLoader is created
             * within a context that limits which files it can read
             */
            public static ExtClassLoader getExtClassLoader() throws IOException
            {
                final File[] dirs = getExtDirs();
    
                try {
                    // Prior implementations of this doPrivileged() block supplied
                    // aa synthesized ACC via a call to the private method
                    // ExtClassLoader.getContext().
    
                    return AccessController.doPrivileged(
                        new PrivilegedExceptionAction<ExtClassLoader>() {
                            public ExtClassLoader run() throws IOException {
                                int len = dirs.length;
                                for (int i = 0; i < len; i++) {
                                    MetaIndex.registerDirectory(dirs[i]);
                                }
                                return new ExtClassLoader(dirs);
                            }
                        });
                } catch (java.security.PrivilegedActionException e) {
                    throw (IOException) e.getException();
                }
            }
    
            private static File[] getExtDirs() {
                String s = System.getProperty("java.ext.dirs");
                File[] dirs;
                if (s != null) {
                    StringTokenizer st =
                        new StringTokenizer(s, File.pathSeparator);
                    int count = st.countTokens();
                    dirs = new File[count];
                    for (int i = 0; i < count; i++) {
                        dirs[i] = new File(st.nextToken());
                    }
                } else {
                    dirs = new File[0];
                }
                return dirs;
            }
    
    ......
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    我们先前的内容有说过,可以指定-D java.ext.dirs参数来添加和改变ExtClassLoader的加载路径。这里我们通过可以编写测试代码。

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

    结果如下:

    C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext
    • 1

    AppClassLoader源码

    /**
         * The class loader used for loading from java.class.path.
         * runs in a restricted security context.
         */
        static class AppClassLoader extends URLClassLoader {
    
    
            public static ClassLoader getAppClassLoader(final ClassLoader extcl)
                throws IOException
            {
                final String s = System.getProperty("java.class.path");
                final File[] path = (s == null) ? new File[0] : getClassPath(s);
    
    
                return AccessController.doPrivileged(
                    new PrivilegedAction<AppClassLoader>() {
                        public AppClassLoader run() {
                        URL[] urls =
                            (s == null) ? new URL[0] : pathToURLs(path);
                        return new AppClassLoader(urls, extcl);
                    }
                });
            }
    
            ......
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    可以看到AppClassLoader加载的就是java.class.path下的路径。我们同样打印它的值。

    System.out.println(System.getProperty("java.class.path"));
    • 1

    结果:

    D:\workspace\ClassLoaderDemo\bin
    • 1

    这个路径其实就是当前java工程目录bin,里面存放的是编译生成的class文件。

    好了,自此我们已经知道了BootstrapClassLoader、ExtClassLoader、AppClassLoader实际是查阅相应的环境属性sun.boot.class.pathjava.ext.dirsjava.class.path来加载资源文件的。

    接下来我们探讨它们的加载顺序,我们先用Eclipse建立一个java工程。
    这里写图片描述
    然后创建一个Test.java文件。

    public class Test{}
    • 1

    然后,编写一个ClassLoaderTest.java文件。

    
    public class ClassLoaderTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            ClassLoader cl = Test.class.getClassLoader();
    
            System.out.println("ClassLoader is:"+cl.toString());
    
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们获取到了Test.class文件的类加载器,然后打印出来。结果是:

    ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93  
    • 1

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

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

    public class ClassLoaderTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            ClassLoader cl = Test.class.getClassLoader();
    
            System.out.println("ClassLoader is:"+cl.toString());
    
            cl = int.class.getClassLoader();
    
            System.out.println("ClassLoader is:"+cl.toString());
    
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    运行一下,却报错了

    ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
    Exception in thread "main" java.lang.NullPointerException
        at ClassLoaderTest.main(ClassLoaderTest.java:15)
    • 1
    • 2
    • 3

    提示的是空指针,意思是int.class这类基础类没有类加载器加载?

    当然不是!
    int.class是由Bootstrap ClassLoader加载的。要想弄明白这些,我们首先得知道一个前提。

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

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

    ClassLoader cl = Test.class.getClassLoader();
    
    System.out.println("ClassLoader is:"+cl.toString());
    System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
    • 1
    • 2
    • 3
    • 4

    运行结果如下:

    ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
    ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
    • 1
    • 2

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

    System.out.println("ClassLoader is:"+cl.toString());
    System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
    System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString());
    • 1
    • 2
    • 3

    运行如果:

    ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
    Exception in thread "main" ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
    java.lang.NullPointerException
        at ClassLoaderTest.main(ClassLoaderTest.java:13)
    • 1
    • 2
    • 3
    • 4

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

    父加载器不是父类

    我们先前已经粘贴了ExtClassLoader和AppClassLoader的代码。

    static class ExtClassLoader extends URLClassLoader {}
    static class AppClassLoader extends URLClassLoader {}
    • 1
    • 2

    可以看见ExtClassLoader和AppClassLoader同样继承自URLClassLoader,但上面一小节代码中,为什么调用AppClassLoader的getParent()代码会得到ExtClassLoader的实例呢?先从URLClassLoader说起,这个类又是什么?
    先上一张类的继承关系图
    这里写图片描述

    URLClassLoader的源码中并没有找到getParent()方法。这个方法在ClassLoader.java中。

    public abstract class ClassLoader {
    
    // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;
    // The class loader for the system
        // @GuardedBy("ClassLoader.class")
    private static ClassLoader scl;
    
    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        ...
    }
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    public final ClassLoader getParent() {
        if (parent == null)
            return null;
        return parent;
    }
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        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;
                //通过Launcher获取ClassLoader
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;
        }
    }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    我们可以看到getParent()实际上返回的就是一个ClassLoader对象parent,parent的赋值是在ClassLoader对象的构造方法中,它有两个情况:
    1. 由外部类创建ClassLoader时直接指定一个ClassLoader为parent。
    2. 由getSystemClassLoader()方法生成,也就是在sun.misc.Laucher通过getClassLoader()获取,也就是AppClassLoader。直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。

    我们主要研究的是ExtClassLoader与AppClassLoader的parent的来源,正好它们与Launcher类有关,我们上面已经粘贴过Launcher的部分代码。

    public class Launcher {
        private static URLStreamHandlerFactory factory = new Factory();
        private static Launcher launcher = new Launcher();
        private static String bootClassPath =
            System.getProperty("sun.boot.class.path");
    
        public static Launcher getLauncher() {
            return launcher;
        }
    
        private ClassLoader loader;
    
        public Launcher() {
            // Create the extension class loader
            ClassLoader extcl;
            try {
                extcl = ExtClassLoader.getExtClassLoader();
            } catch (IOException e) {
                throw new InternalError(
                    "Could not create extension class loader", e);
            }
    
            // Now create the class loader to use to launch the application
            try {
            //将ExtClassLoader对象实例传递进去
                loader = AppClassLoader.getAppClassLoader(extcl);
            } catch (IOException e) {
                throw new InternalError(
                    "Could not create application class loader", e);
            }
    
    public ClassLoader getClassLoader() {
            return loader;
        }
    static class ExtClassLoader extends URLClassLoader {
    
            /**
             * create an ExtClassLoader. The ExtClassLoader is created
             * within a context that limits which files it can read
             */
            public static ExtClassLoader getExtClassLoader() throws IOException
            {
                final File[] dirs = getExtDirs();
    
                try {
                    // Prior implementations of this doPrivileged() block supplied
                    // aa synthesized ACC via a call to the private method
                    // ExtClassLoader.getContext().
    
                    return AccessController.doPrivileged(
                        new PrivilegedExceptionAction<ExtClassLoader>() {
                            public ExtClassLoader run() throws IOException {
                                //ExtClassLoader在这里创建
                                return new ExtClassLoader(dirs);
                            }
                        });
                } catch (java.security.PrivilegedActionException e) {
                    throw (IOException) e.getException();
                }
            }
    
    
            /*
             * Creates a new ExtClassLoader for the specified directories.
             */
            public ExtClassLoader(File[] dirs) throws IOException {
                super(getExtURLs(dirs), null, factory);
    
            }
            }
     }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    我们需要注意的是

    ClassLoader extcl;
    
    extcl = ExtClassLoader.getExtClassLoader();
    
    loader = AppClassLoader.getAppClassLoader(extcl);
    • 1
    • 2
    • 3
    • 4
    • 5

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

    ExtClassLoader并没有直接找到对parent的赋值。它调用了它的父类也就是URLClassLoder的构造方法并传递了3个参数。

    public ExtClassLoader(File[] dirs) throws IOException {
                super(getExtURLs(dirs), null, factory);   
    }
    • 1
    • 2
    • 3

    对应的代码

    public  URLClassLoader(URL[] urls, ClassLoader parent,
                              URLStreamHandlerFactory factory) {
         super(parent);
    }
    • 1
    • 2
    • 3
    • 4

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

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

    不过,细心的同学发现,还是有疑问的我们只看到ExtClassLoader和AppClassLoader的创建,那么BootstrapClassLoader呢?

    还有,ExtClassLoader的父加载器为null,但是Bootstrap CLassLoader却可以当成它的父加载器这又是为何呢?

    我们继续往下进行。

    Bootstrap ClassLoader是由C++编写的。

    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的父加载器。Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释之前通过ExtClassLoader的getParent方法获取为Null的现象。具体是什么原因,很快就知道答案了。

    双亲委托

    双亲委托。
    我们终于来到了这一步了。
    一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托。
    整个流程可以如下图所示:
    这里写图片描述
    这张图是用时序图画出来的,不过画出来的结果我却自己都觉得不理想。

    大家可以看到2根箭头,蓝色的代表类加载器向上委托的方向,如果当前的类加载器没有查询到这个class对象已经加载就请求父加载器(不一定是父类)进行操作,然后以此类推。直到Bootstrap ClassLoader。如果Bootstrap ClassLoader也没有加载过此class实例,那么它就会从它指定的路径中去查找,如果查找成功则返回,如果没有查找成功则交给子类加载器,也就是ExtClassLoader,这样类似操作直到终点,也就是我上图中的红色箭头示例。
    用序列描述一下:
    1. 一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
    2. 递归,重复第1部的操作。
    3. 如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出面,它首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class下面的路径。找到就返回,没有找到,让子加载器自己去找。
    4. Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在java.ext.dirs路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找。
    5. ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path路径下查找。找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。

    上面的序列,详细说明了双亲委托的加载流程。我们可以发现委托是从下向上,然后具体查找过程却是自上至下。

    我说过上面用时序图画的让自己不满意,现在用框图,最原始的方法再画一次。
    这里写图片描述

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

    重要方法

    loadClass()

    JDK文档中是这样写的,通过指定的全限定类名加载class,它通过同名的loadClass(String,boolean)方法。

    protected Class<?> loadClass(String name,
                                 boolean resolve)
                          throws ClassNotFoundException
    • 1
    • 2
    • 3

    上面是方法原型,一般实现这个方法的步骤是
    1. 执行findLoadedClass(String)去检测这个class是不是已经加载过了。
    2. 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
    3. 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。

    如果class在上面的步骤中找到了,参数resolve又是true的话,那么loadClass()又会调用resolveClass(Class)这个方法来生成最终的Class对象。 我们可以从源代码看出这个步骤。

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 首先,检测是否已经加载
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
                            //父加载器不为空则调用父加载器的loadClass
                            c = parent.loadClass(name, false);
                        } else {
                            //父加载器为空则调用Bootstrap Classloader
                            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();
                        //父加载器没有找到,则调用findclass
                        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()
                    resolveClass(c);
                }
                return c;
            }
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    代码解释了双亲委托。

    另外,要注意的是如果要编写一个classLoader的子类,也就是自定义一个classloader,建议覆盖findClass()方法,而不要直接改写loadClass()方法。
    另外

    if (parent != null) {
        //父加载器不为空则调用父加载器的loadClass
        c = parent.loadClass(name, false);
    } else {
        //父加载器为空则调用Bootstrap Classloader
        c = findBootstrapClassOrNull(name);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    前面说过ExtClassLoader的parent为null,所以它向上委托时,系统会为它指定Bootstrap ClassLoader。

    自定义ClassLoader

    不知道大家有没有发现,不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。如果在某种情况下,我们需要动态加载一些东西呢?比如从D盘某个文件夹加载一个class文件,或者从网络上下载class主内容然后再进行加载,这样可以吗?

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

    自定义步骤

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

    defineClass()

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

    注意点:

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

    上面说的是,如果自定义一个ClassLoader,默认的parent父加载器是AppClassLoader,因为这样就能够保证它能访问系统内置加载器加载成功的class文件。

    自定义ClassLoader示例之DiskClassLoader。

    假设我们需要一个自定义的classloader,默认加载路径为D:\lib下的jar包和资源。

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

    Test.java

    package com.frank.test;
    
    public class Test {
    
        public void say(){
            System.out.println("Say Hello");
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    DiskClassLoader

    我们编写DiskClassLoader的代码。

    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    
    
    public class DiskClassLoader extends ClassLoader {
    
        private String mLibPath;
    
        public DiskClassLoader(String path) {
            // TODO Auto-generated constructor stub
            mLibPath = path;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // TODO Auto-generated method stub
    
            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) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            return super.findClass(name);
        }
    
        //获取要加载 的class文件名
        private String getFileName(String name) {
            // TODO Auto-generated method stub
            int index = name.lastIndexOf('.');
            if(index == -1){ 
                return name+".class";
            }else{
                return name.substring(index)+".class";
            }
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    我们在findClass()方法中定义了查找class的方法,然后数据通过defineClass()生成了Class对象。

    测试

    现在我们要编写测试代码。我们知道如果调用一个Test对象的say方法,它会输出”Say Hello”这条字符串。但现在是我们把Test.class放置在应用工程所有的目录之外,我们需要加载它,然后执行它的方法。具体效果如何呢?我们编写的DiskClassLoader能不能顺利完成任务呢?我们拭目以待。

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class ClassLoaderTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            //创建自定义classloader对象。
            DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
            try {
                //加载class文件
                Class c = diskLoader.loadClass("com.frank.test.Test");
    
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        Method method = c.getDeclaredMethod("say",null);
                        //通过反射调用Test类的say方法
                        method.invoke(obj, null);
                    } catch (InstantiationException | IllegalAccessException 
                            | NoSuchMethodException
                            | SecurityException | 
                            IllegalArgumentException | 
                            InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    我们点击运行按钮,结果显示。

    这里写图片描述

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

    回首

    讲了这么大的篇幅,自定义ClassLoader才姗姗来迟。 很多同学可能觉得前面有些啰嗦,但我按照自己的思路,我觉得还是有必要的。因为我是围绕一个关键字进行讲解的。

    关键字是什么?

    关键字 路径

    • 从开篇的环境变量
    • 到3个主要的JDK自带的类加载器
    • 到自定义的ClassLoader

    它们的关联部分就是路径,也就是要加载的class或者是资源的路径。
    BootStrap ClassLoader、ExtClassLoader、AppClassLoader都是加载指定路径下的jar包。如果我们要突破这种限制,实现自己某些特殊的需求,我们就得自定义ClassLoader,自已指定加载的路径,可以是磁盘、内存、网络或者其它。

    所以,你说路径能不能成为它们的关键字?

    当然上面的只是我个人的看法,可能不正确,但现阶段,这样有利于自己的学习理解。

    自定义ClassLoader还能做什么?

    突破了JDK系统内置加载路径的限制之后,我们就可以编写自定义ClassLoader,然后剩下的就叫给开发者你自己了。你可以按照自己的意愿进行业务的定制,将ClassLoader玩出花样来。

    玩出花之Class解密类加载器

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

    下面,我们编写代码。

    1.定义加密解密协议

    加密和解密的协议有很多种,具体怎么定看业务需要。在这里,为了便于演示,我简单地将加密解密定义为异或运算。当一个文件进行异或运算后,产生了加密文件,再进行一次异或后,就进行了解密。

    2.编写加密工具类

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    
    public class FileUtils {
    
        public static void test(String path){
            File file = new File(path);
            try {
                FileInputStream fis = new FileInputStream(file);
                FileOutputStream fos = new FileOutputStream(path+"en");
                int b = 0;
                int b1 = 0;
                try {
                    while((b = fis.read()) != -1){
                        //每一个byte异或一个数字2
                        fos.write(b ^ 2);
                    }
                    fos.close();
                    fis.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    我们再写测试代码

    FileUtils.test("D:\\lib\\Test.class");
    • 1

    这里写图片描述
    然后可以看见路径D:\\lib\\Test.class下Test.class生成了Test.classen文件。

    编写自定义classloader,DeClassLoader

    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    
    
    public class DeClassLoader extends ClassLoader {
    
        private String mLibPath;
    
        public DeClassLoader(String path) {
            // TODO Auto-generated constructor stub
            mLibPath = path;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // TODO Auto-generated method stub
    
            String fileName = getFileName(name);
    
            File file = new File(mLibPath,fileName);
    
            try {
                FileInputStream is = new FileInputStream(file);
    
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                int len = 0;
                byte b = 0;
                try {
                    while ((len = is.read()) != -1) {
                        //将数据异或一个数字2进行解密
                        b = (byte) (len ^ 2);
                        bos.write(b);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
                byte[] data = bos.toByteArray();
                is.close();
                bos.close();
    
                return defineClass(name,data,0,data.length);
    
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            return super.findClass(name);
        }
    
        //获取要加载 的class文件名
        private String getFileName(String name) {
            // TODO Auto-generated method stub
            int index = name.lastIndexOf('.');
            if(index == -1){ 
                return name+".classen";
            }else{
                return name.substring(index+1)+".classen";
            }
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    测试

    我们可以在ClassLoaderTest.java中的main方法中如下编码:

    DeClassLoader diskLoader = new DeClassLoader("D:\\lib");
            try {
                //加载class文件
                Class c = diskLoader.loadClass("com.frank.test.Test");
    
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        Method method = c.getDeclaredMethod("say",null);
                        //通过反射调用Test类的say方法
                        method.invoke(obj, null);
                    } catch (InstantiationException | IllegalAccessException 
                            | NoSuchMethodException
                            | SecurityException | 
                            IllegalArgumentException | 
                            InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    查看运行结果是:

    这里写图片描述

    可以看到了,同样成功了。现在,我们有两个自定义的ClassLoader:DiskClassLoader和DeClassLoader,我们可以尝试一下,看看DiskClassLoader能不能加载Test.classen文件也就是Test.class加密后的文件。

    我们首先移除D:\\lib\\Test.class文件,只剩下一下Test.classen文件,然后进行代码的测试。

    DeClassLoader diskLoader1 = new DeClassLoader("D:\\lib");
            try {
                //加载class文件
                Class c = diskLoader1.loadClass("com.frank.test.Test");
    
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        Method method = c.getDeclaredMethod("say",null);
                        //通过反射调用Test类的say方法
                        method.invoke(obj, null);
                    } catch (InstantiationException | IllegalAccessException 
                            | NoSuchMethodException
                            | SecurityException | 
                            IllegalArgumentException | 
                            InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
            try {
                //加载class文件
                Class c = diskLoader.loadClass("com.frank.test.Test");
    
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        Method method = c.getDeclaredMethod("say",null);
                        //通过反射调用Test类的say方法
                        method.invoke(obj, null);
                    } catch (InstantiationException | IllegalAccessException 
                            | NoSuchMethodException
                            | SecurityException | 
                            IllegalArgumentException | 
                            InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    运行结果:
    这里写图片描述

    我们可以看到。DeClassLoader运行正常,而DiskClassLoader却找不到Test.class的类,并且它也无法加载Test.classen文件。

    Context ClassLoader 线程上下文类加载器

    前面讲到过Bootstrap ClassLoader、ExtClassLoader、AppClassLoader,现在又出来这么一个类加载器,这是为什么?

    前面三个之所以放在前面讲,是因为它们是真实存在的类,而且遵从”双亲委托“的机制。而ContextClassLoader其实只是一个概念。

    查看Thread.java源码可以发现

    public class Thread implements Runnable {
    
    /* The context ClassLoader for this thread */
       private ClassLoader contextClassLoader;
    
       public void setContextClassLoader(ClassLoader cl) {
           SecurityManager sm = System.getSecurityManager();
           if (sm != null) {
               sm.checkPermission(new RuntimePermission("setContextClassLoader"));
           }
           contextClassLoader = cl;
       }
    
       public ClassLoader getContextClassLoader() {
           if (contextClassLoader == null)
               return null;
           SecurityManager sm = System.getSecurityManager();
           if (sm != null) {
               ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                                      Reflection.getCallerClass());
           }
           return contextClassLoader;
       }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    contextClassLoader只是一个成员变量,通过setContextClassLoader()方法设置,通过getContextClassLoader()设置。

    每个Thread都有一个相关联的ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader除非子线程特别设置。

    我们同样可以编写代码来加深理解。
    现在有2个SpeakTest.class文件,一个源码是

    package com.frank.test;
    
    public class SpeakTest implements ISpeak {
    
        @Override
        public void speak() {
            // TODO Auto-generated method stub
            System.out.println("Test");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    它生成的SpeakTest.class文件放置在D:\\lib\\test目录下。
    另外ISpeak.java代码

     package com.frank.test;
    
    public interface ISpeak {
        public void speak();
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后,我们在这里还实现了一个SpeakTest.java

    package com.frank.test;
    
    public class SpeakTest implements ISpeak {
    
        @Override
        public void speak() {
            // TODO Auto-generated method stub
            System.out.println("I\' frank");
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    它生成的SpeakTest.class文件放置在D:\\lib目录下。

    然后我们还要编写另外一个ClassLoader,DiskClassLoader1.java这个ClassLoader的代码和DiskClassLoader.java代码一致,我们要在DiskClassLoader1中加载位置于D:\\lib\\test中的SpeakTest.class文件。

    测试代码:

    DiskClassLoader1 diskLoader1 = new DiskClassLoader1("D:\\lib\\test");
    Class cls1 = null;
    try {
    //加载class文件
     cls1 = diskLoader1.loadClass("com.frank.test.SpeakTest");
    System.out.println(cls1.getClassLoader().toString());
    if(cls1 != null){
        try {
            Object obj = cls1.newInstance();
            //SpeakTest1 speak = (SpeakTest1) obj;
            //speak.speak();
            Method method = cls1.getDeclaredMethod("speak",null);
            //通过反射调用Test类的speak方法
            method.invoke(obj, null);
        } catch (InstantiationException | IllegalAccessException 
                | NoSuchMethodException
                | SecurityException | 
                IllegalArgumentException | 
                InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    } catch (ClassNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    
    DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
    System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());
    new Thread(new Runnable() {
    
        @Override
        public void run() {
            System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());
    
            // TODO Auto-generated method stub
            try {
                //加载class文件
            //  Thread.currentThread().setContextClassLoader(diskLoader);
                //Class c = diskLoader.loadClass("com.frank.test.SpeakTest");
                ClassLoader cl = Thread.currentThread().getContextClassLoader();
                Class c = cl.loadClass("com.frank.test.SpeakTest");
                // Class c = Class.forName("com.frank.test.SpeakTest");
                System.out.println(c.getClassLoader().toString());
                if(c != null){
                    try {
                        Object obj = c.newInstance();
                        //SpeakTest1 speak = (SpeakTest1) obj;
                        //speak.speak();
                        Method method = c.getDeclaredMethod("speak",null);
                        //通过反射调用Test类的say方法
                        method.invoke(obj, null);
                    } catch (InstantiationException | IllegalAccessException 
                            | NoSuchMethodException
                            | SecurityException | 
                            IllegalArgumentException | 
                            InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }).start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    结果如下:
    这里写图片描述

    我们可以得到如下的信息:
    1. DiskClassLoader1加载成功了SpeakTest.class文件并执行成功。
    2. 子线程的ContextClassLoader是AppClassLoader。
    3. AppClassLoader加载不了父线程当中已经加载的SpeakTest.class内容。

    我们修改一下代码,在子线程开头处加上这么一句内容。

    Thread.currentThread().setContextClassLoader(diskLoader1);
    • 1

    结果如下:
    这里写图片描述

    可以看到子线程的ContextClassLoader变成了DiskClassLoader。

    继续改动代码:

    Thread.currentThread().setContextClassLoader(diskLoader);
    
    • 1
    • 2

    结果:
    这里写图片描述

    可以看到DiskClassLoader1和DiskClassLoader分别加载了自己路径下的SpeakTest.class文件,并且它们的类名是一样的com.frank.test.SpeakTest,但是执行结果不一样,因为它们的实际内容不一样。

    Context ClassLoader的运用时机

    其实这个我也不是很清楚,我的主业是Android,研究ClassLoader也是为了更好的研究Android。网上的答案说是适应那些Web服务框架软件如Tomcat等。主要为了加载不同的APP,因为加载器不一样,同一份class文件加载后生成的类是不相等的。如果有同学想多了解更多的细节,请自行查阅相关资料。

    总结

    1. ClassLoader用来加载class文件的。
    2. 系统内置的ClassLoader通过双亲委托来加载指定路径下的class和资源。
    3. 可以自定义ClassLoader一般覆盖findClass()方法。
    4. ContextClassLoader与线程相关,可以获取和设置,可以绕过双亲委托的机制。

    下一步

    1. 你可以研究ClassLoader在Web容器内的应用了,如Tomcat。
    2. 可以尝试以这个为基础,继续学习Android中的ClassLoader机制。

    引用

    以下为参考网站链接。
    1. grepcode ClassLoader源码
    2. http://blog.csdn.net/xyang81/article/details/7292380
    3. http://blog.csdn.net/irelandken/article/details/7048817
    4. https://docs.oracle.com/javase/7/docs/api/java/net/URLClassLoader.html

     

      相关文章推荐

    展开全文
  • java中Classloader是什么意思?

    千次阅读 2018-09-13 22:33:56
    java中Classloader是一种程序,可以在我们想要访问某个类的时候,把相应的字节码载入入内存。我们可以通过继承ClassLoader类并覆盖loadClass(String name)方法来自己编写classloader....
  • java 中ClassLoader 的加载顺序

    千次阅读 2015-08-21 16:50:42
    原文引自:... 当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:  bootstrap classloader  |  extension classloader  
  • java面试题28 牛客 下面有关java classloader说法错误的是? A Java默认提供的三个ClassLoader是BootStrap ClassLoader,Extension ClassLoader,App ClassLoader B ClassLoader使用的是双亲委托模型来搜索类的 ...
  • 深入分析Java ClassLoader原理

    万次阅读 多人点赞 2012-02-25 21:39:24
    一、什么是ClassLoader?  大家都知道,当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的...
  • java classloader

    2019-03-01 17:00:01
    NULL 博文链接:https://sbiigu.iteye.com/blog/260456
  • 破解java加密的ClassLoader.java,在classloader植入破解代码
  • ClassLoader类getSystemResources()方法 (ClassLoader Class getSystemResources() method) getSystemResources() method is available in java.lang package. getSystemResources()方法在java.lang包中可用。 ...
  • java中classloader原理

    2013-05-20 17:27:04
    Java内部实现了三种类型的ClassLoader:Bootstrap ClassLoader,Extension ClassLoader, System ClassLoader。这三个ClassLoader通过parent形成一条单向链,其中Bootstrap处于链尾,而System ClassLoader处于链头...
  • ClassLoader主要对类的请求提供服务,当JVM...ClassLoader负责载入系统的所有资源(Class,文件,图片,来自网络的字节流等),通过ClassLoader从而将资源载入JVM 。每个class都有一个引用,指向自己的ClassLoader
  • 主要介绍了Java classloader和namespace详细介绍的相关资料,需要的朋友可以参考下
  • Android解析ClassLoader(一)Java中ClassLoader

    万次阅读 热门讨论 2017-09-25 19:42:50
    热修复和插件化是目前比较热门的技术,要想更好的掌握它们需要了解ClassLoader,因此也就有了本系列的产生,这一篇我们先来学习Java中ClassLoader
  • 主要介绍了Java中Classloader的运行机制,包括从JVM方面讲解类加载器的委托机制等,需要的朋友可以参考下
  • 主要讲解了关于Java虚拟机的classloader部分~
  • 众所周知, Java 或者其他运行在 JVM(java 虚拟机)上面的程序都需要最终便以为字节码,然后被 JVM加载运行,那么这个加载到虚拟机的过程就是 classloader 类加载器所干的事情.直白一点,就是 通过一个类的全限定类名称...
  • 最近做项目的时候遇到这个问题,就是在服务器不重启的情况下实现版本替换,即项目一部分的类需要实时替换,甚至,对利用动态编译去生成很多临时类; 那就需要解决两个问题: 1.如何实现类的动态替换 2.如何实现类...
  • Java中ClassLoader和SPI机制

    千次阅读 2019-03-27 20:10:31
    深入探讨 Java 类加载器 成富是著名的Java专家,在IBM技术网站发表很多Java好文,也有著作。...类java.lang.Thread的方法getContextClassLoader()和setContextClassLoader(ClassLoader cl)用...
  • 继承ClassLoader并且重写findClass方法就可以自定义一个类加载器,具体什么是类加载器以及类加载器的加载过程与顺序下次再说,下面给出一个小demo首先定义一个类,比如MyTest,并且将其编译成class文件,然后放到一...
  • 1. 应用场景有时候我们需要在一个 Project 运行多个不同版本的 jar 包,以应对不同集群的版本或其它的问题。如果这个时候选择在同一个项目实现这样的功能,那么通常只能选择更...2. 实现在 Java ,所有的类默认通
  • 把EffectiveJava.class放入本地目录E:\\classloader_test\\other\\ 测试时LoadClassFileClassLoader与Animal、Dog、EffectiveJava不在同一个工程里面。开始测试: ``` //加载Animal、Dog LoadClassFileClassLoader ...
  •  Java默认是有三个ClassLoader,按层次关系从上到下依次是:  Bootstrap ClassLoader  Ext ClassLoader  System ClassLoader  Bootstrap ClassLoader是顶层的ClassLoader,它比较特殊,是用C++编写集成在...
  • 主要给大家介绍了关于Java运行时环境之ClassLoader类加载机制的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • ClassLoader

    2018-11-12 16:25:45
    Java 虚拟机中ClassLoader 相关简介 双亲委托机制 Android 中ClassLoader 简介
  • 内容简介: ClassLoader体系结构 类装载器在JVM并不是唯一的,JVM自带了三个装载器,用户也可以根据自己的需求自定义新的装载器,这些装载器的体系结构可以看作是树状结构,如图1所示:

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 184,916
精华内容 73,966
关键字:

java中classloader

java 订阅