ClassLoader类加载器

一.ClassLoader类加载器
  在java语言中,提供有一个系统的环境变量叫做classpath,这个环境属性的作用主要是在JVM进程启动的时候进行类加载路径的定义.在JVM里面可以根据类加载器而后可以根据指定路径中类的加载,也就是说找到了类的加载器就意味着找到了类的来源.

二.系统类的加载器
  如果说现在要想获得类的加载器,那么一定要通过ClassLoader来获取.而要想获取ClassLoader类的对象,则必须利用Class类实现(反射的起源,根源)
--Class类中的方法: public ClassLoader getClassLoader();
--范例:观察类加载器: 
--JVM类加载器在运行的时候,需要将类加载到内存,而内存之中的对象通过Class类可以知道ClassLoader.
  当换取了ClassLoader之后,还可以继续获取其父类的ClassLoader类对象public final ClassLoader getParent()

 1 class Message{
 2 
 3 }
 4 public class GetClassLoaderDemo {
 5     public static void main(String[] args) {
 6         ClassLoader loader = Message.class.getClassLoader();        //获取当前类的类加载器
 7         ClassLoader parent = loader.getParent();    //获取父类加载器
 8         System.out.println(loader);
 9         System.out.println(parent);
10     }
11 }

--运行结果

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586

Process finished with exit code 0开始

--查看获取祖父类加载器

 1 class Message{
 2 
 3 }
 4 public class GetClassLoaderDemo {
 5     public static void main(String[] args) {
 6         ClassLoader loader = Message.class.getClassLoader();        //获取当前类的类加载器
 7         ClassLoader parent = loader.getParent();    //获取父类加载器
 8         System.out.println(loader);
 9         System.out.println(parent);
10         System.out.println(parent.getParent());     //获取祖父类加载器
11     }
12 }

--运行结果

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
null

Process finished with exit code 0

--ClassLoader类别
  AppClassLoader: 应用程序的加载器
  ExtClassLoader(JKD1.9之后为PlatformClassLoader) :扩展类加载器,JDK1.9之后为平台类加载器.从JDK1.8之后的版本(JDK1.9,JDK1.10)提供有一个"PlatformClassLoader"类加载器,而在JDK1.8及以前的版本里面提供的加载器为"ExtClassLoader",因为在JDK的安装目录里面提供有一个ext目录,开发者可以将*.jar文件拷贝到此目录里面,这样就可以直接执行了,但是这样的处理并不安全.最初的时候也是不提倡使用的.所以在JDK9开始就将这样的操作彻底废除了,同时为了与系统类加载器和应用类加载器之间保持设计的平衡,提供有平台类加载器.
  BootstrapClassLoader: 根平台加载器,系统类加载器.
--当你获得了类加载器之后就可以利用类加载器来实现类的反射加载处理.

三.自定义类加载器
  清楚了类加载器的功能之后,就可以根据自身的需要来完成自定义的类加载器,但是千万记住一点,自定义的类加载器其加载的顺序是在所有系统类加载器的最后.系统提供的类加载器都有一个特点,系统中的类加载器都是根据CLASSPATH路径进行类加载的,而如果有了自定义的类加载器,就可以由开发者任意指派类的加载位置(磁盘,网络),例如在网络服务器上存在一个*.class的类文件,实现了自定义的类加载器就可以实现对该类文件的加载.但是需要注意的是加载的所有数据对类加载器而言只有一种数据类型,那就是字节.
--实现自定义类加载器的步骤
1. 编写一个程序类,并将这个类保存在磁盘上.

 1 package 反射.ClassLoader类加载器.util;
 2 
 3 /**
 4  * @author : S K Y
 5  * @version :0.0.1
 6  */
 7 public class Message {
 8     public void send(){
 9         System.out.println("加载Message类......");
10     }
11 }

2.将此类直接拷贝到D盘上进行编译处理,并且不打包 D:java_testMessage.class,此时并没有进行打包处理,,所以这个类无法通过classpath正常加载.
3.自定义一个类加载器,并且继承自ClassLoader类.在ClassLoader类中为用户提供有一个字节转化为类结构的方法:  protected final 类<?> defineClass(Stringname,byte[] b,int off,int len)throws ClassFormatError.

 1 package 反射.ClassLoader类加载器.util;
 2 
 3 import java.io.*;
 4 import java.lang.reflect.Method;
 5 
 6 /**
 7  * @author : S K Y
 8  * @version :0.0.1
 9  */
10 public class MyClassLoader extends ClassLoader {
11     private static final String MESSAGE_CLASS_PATH = "D:" + File.separator + "java_test" + File.separator + "Message.class";
12 
13     /**
14      * 进行制定类的加载
15      *
16      * @param className 类的完整名称: XX包.XX类
17      * @return 返回一个指定类的Class对象
18      * @throws Exception 如果类文件不存在,则无法加载
19      */
20     public Class<?> loadData(String className) throws Exception {
21         byte[] data = this.loadClassData();        //读取二进制数据文件
22         if (data != null) {       //读取到了
23             return super.defineClass(className, data, 0, data.length);
24         }
25         return null;
26     }
27 
28     private byte[] loadClassData() throws Exception {     //通过文件进行类的加载
29         InputStream inputStream = null;
30         ByteArrayOutputStream outputStream = null;
31         byte[] result = null;
32         try {
33             inputStream = new FileInputStream(new File(MESSAGE_CLASS_PATH));
34             outputStream = new ByteArrayOutputStream();
35             byte data[] = new byte[1024];       //进行读取
36             int len;
37             while ((len = inputStream.read(data)) != -1){       //将数据信息读取到内存中
38                 outputStream.write(data,0,len);
39             }
40 
41             result = outputStream.toByteArray();      //将所有读取到的字节数据取出
42             System.out.println("result: " + result.length);
43         } catch (Exception e) {
44             e.printStackTrace();
45         } finally {
46             if (inputStream != null) {
47                 inputStream.close();
48             }
49             if (outputStream != null) {
50                 outputStream.close();
51             }
52         }
53         return result;
54     }
55 }
56 
57 class TestClassLoader {
58     public static void main(String[] args) throws Exception {
59         MyClassLoader loader = new MyClassLoader();     //自定义类加载器
60         Class<?> aClass = loader.loadData("反射.ClassLoader类加载器.util.Message");//进行类的机载
61         System.out.println(aClass);
62         Object o = aClass.newInstance();
63         Method send = aClass.getDeclaredMethod("send");
64         send.invoke(o);
65     }
66 }

--运行结果:

result: 441
class 反射.ClassLoader类加载器.util.Message
加载Message类......

Process finished with exit code 0

--如果在以后结合到网络程序,就可以通过一个远程的服务器来确定类的功能.在正常的开发流程之中,在客户端的JVM上可以存在可以自己独立运行的程序,但是程序中的某几个文件可能需要随时更新,当程序启动的时候,可以通过服务器端来加载这些类的class文件,这样就是显示实时的远程更新处理.
--观察当前Message类的加载情况:

 1 class TestClassLoader {
 2     public static void main(String[] args) throws Exception {
 3         MyClassLoader loader = new MyClassLoader();     //自定义类加载器
 4         Class<?> aClass = loader.loadData("反射.ClassLoader类加载器.util.Message");//进行类的机载
 5         System.out.println(aClass.getClassLoader());
 6         System.out.println(aClass.getClassLoader().getParent());
 7         System.out.println(aClass.getClassLoader().getParent().getParent());
 8         System.out.println(aClass.getClassLoader().getParent().getParent().getParent());
 9     }
10 }

--运行结果

反射.ClassLoader类加载器.util.MyClassLoader@4554617c
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
null

Process finished with exit code 0

--可以发现自定义的类加载器一定是在最后执行的,但是这样操作是存在安全隐患问题的.例如现在定义了一个类,且类的名称为java.lang.String,并且利用了自定义的类加载器进行类的加载,则此时这个类不会被加载.java中正对类加载器提供有双亲加载机制.如果要加载的程序类是由系统提供的类,则会有系统的类加载器进行加载.如果说现在开发者定义的类与系统的类的名称相同,那么绝对不会加载(为了保证系统的安全性).

原文地址:https://www.cnblogs.com/skykuqi/p/11440849.html