7. String className = "org.classloader.simple.NetClassLoaderSimple";
8. NetworkClassLoader ncl1 = new NetworkClassLoader(rootUrl);
9. NetworkClassLoader ncl2 = new NetworkClassLoader(rootUrl);
10. Class clazz1 = ncl1.loadClass(className);
11. Class clazz2 = ncl2.loadClass(className);
12. Object obj1 = clazz1.newInstance();
13. Object obj2 = clazz2.newInstance();
14. clazz1.getMethod("setNetClassLoaderSimple", Object.class).invoke(obj1, obj2);
15. } catch (Exception e) {
16. e.printStackTrace();
17. }
18. }
19.
20. }
首先获得网络上一个class文件的二进制名称,然后通过自定义的类加载器NetworkClassLoader创建两个实例,并根据网络地址分别加载这份class,并得到这两个ClassLoader实例加载后生成的Class实例clazz1和clazz2,最后将这两个Class实例分别生成具体的实例对象obj1和obj2,再通过反射调用clazz1中的setNetClassLoaderSimple方法。
3)、查看测试结果
3-5:定义自己的classloader
既然JVM已经提供了默认的类加载器,为什么还要定义自已的类加载器呢?
因为Java中提供的默认ClassLoader,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时,比如:我要加载网络上的一个class文件,通过动态加载到内存之后,要调用这个类中的方法实现我的业务逻辑。在这样的情况下,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的ClassLoader。
定义自已的类加载器分为两步:
1、继承java.lang.ClassLoader
2、重写父类的findClass方法
读者可能在这里有疑问,父类有那么多方法,为什么偏偏只重写findClass方法?
因为JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可。如没有特殊的要求,一般不建议重写loadClass搜索类的算法。下图是API中ClassLoader的loadClass方法:


在该代码中必须要说明的一点是, 该自定义类加载器的并没有指定父加载器。
JVM规范中规定在不指定父类加载器的情况下, 默认采用系统类加载器作为其父加载器, 所以在使用该自定义类加载器时, 需要加载的类不能在类路径中, 否则的话依据类加载器的代理/委托原则, 待加载类会由系统类加载器加载,
这样自定义类加载器想要实现的, 诸如类的热替换, 多版本共存, 将变的不可实现。 如果我们一定想要把自定义加载器需要加载的类放在类路径中, 应该怎么办呢, 答案是把自定义类加载器的父加载器设置为null。
JVM规范中规定如果用户自定义的类加载器将父类加载器强制设置为null,那么会自动将启动类加载器设置为当前用户自定义类加载器的父类加载器。
需要注意的是自定义类加载器不同的父加载器决定了加载类的不同的可见性。
下面的代码示例是一个把自定义类加载器的父加载器设置为null时, 如何处理加载类的不同可见性。

3-6:类的加载方式
除了上面提到的通过自定义类加载器加载类, 我们通常会使用下面的两种方式来加载类
3-6-1: 隐式加载
A a = new A() ;
如果程序运行到这段代码时还没有A类,那么JVM会请求装载当前类的类装器来装载类。
3-6-2: 显示加载
| 01 |
//效果相同, 执行类的初始化 |
|||||||||||
| 02 |
Class.forName("test.A"); |
|||||||||||
| 03 |
Class.forName("test.A", true, this.getClass().getClassLoader()) |
|||||||||||
| 04 |
//效果相同, 不执行类的初始化 |
|||||||||||
| 05 |
getClass().getClassLoader().loadClass("test.A"); |
|||||||||||
| 06 |
Class.forName("test.A", false, this.getClass().getClassLoader()); |
|||||||||||
| 07 |
//效果相同, 不执行类的初始化 |
|||||||||||
| 08 |
ClassLoader.getSystemClassLoader().loadClass("test.A"); |
|||||||||||
| 09 |
Class.forName("test.A", false, Classloader.getSystemClassLoader()); |
|||||||||||
| 10 |
//效果相同, 不执行类的初始化 |
|||||||||||
| 11 |
Thread.currentThread().getContextClassLoader().loadClass("test.A") |
|||||||||||
| 12 |
Class.forName("test.A", false, Thread.currentThread().getContextClassLoader()); |
|||||||||||
获得Class 类的实体方式:
1.Class.forName(包名+类名) ; 2.具体类.Class ;3.具体对象.getClass() ;
4.通过ClassLoader类加载器中loadClass(包名+类名) ;
通过线程来获得该线程的上下文加载器
Thread.currentThread().getContextClassLoader();
3-7:上下文类加载器( ContextClassLoader)
类java.lang.Thread中的方法 getContextClassLoader()和setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。
Java 应用运行的初始线程的上下文类加载器是系统类加载器。
在线程中运行的代码可以通过此类加载器来加载类和资源。
正常的双亲委派模型中,下层的类加载器可以使用上层父加载器加载的对象,但是上层父类的加载器不可以使用子类加载的对象。
而有些时候程序的确需要上层调用下层,这时候就需要线程上下文加载器来处理。
| 1 |
Thread.currentThread().getContextClassLoader() |
前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的全部问题。
Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接