打破双亲委派

在开始阅读之前请先思考以下两个问题,并希望您能再接下来的文章中找到答案

1. 如果我自己实现了一个新的java.lang.String类,并通过UrlClassLoader加载使用该类,能否覆盖JDK中的 java.lang.String ?

2. 如果问题1的回答是不能,那用什么方式能做到覆盖JDK中的java.lang.String么?

一、双亲委派

熟悉java类加载机制的一定都知道双亲委派,双亲委派模式的工作原理的是;如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式

采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。至于双亲委派的实现原理网上有很多文章可以参考,不是本文的重点,因此不再赘述。

 

二、破坏双亲委派

 

1.父类加载器需委托子类加载器加载class文件

受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MYSQL CONNECTOR,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能加载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要破坏了双亲委派, 启动类加载器来委托子类加载器来加载Driver实现。这就是著名的SPI(SERVICE PROVIDER INTERFACE)机制,其基本概念和运用可参考以JDBC为例谈双亲委派模型的破坏

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。其基本特点就是,父类提供接口,子类负责实现,父类接口通过配置文件中的实现类的全限定名确定具体的实现,并通过线程上下文加载器接在实现类,最终执行子类的方法实现。概括其逻辑为:加载类=》委托父类加载器加载=》父类加载器通过配置文件找到实现类并获取线程上下文加载器=》加载实现类=》返回实现类实例List

2.实现插件热插拔

DataX是一款热门的数据同步框架,可以将不同数据源的同步抽象为从源头数据源读取数据的Reader插件,以及向目标端写入数据的Writer插件,理论上DataX框架可以支持任意数据源类型的数据同步工作。为了支持对插件化的热插拔,DataX继承UrlClassLoader实现了类加载器JarLoader, 并实现了ClassLoaderSwapper进行类加载器的切换。

同样是破坏双亲委培机制,与SPI机制不同的是,它不通过双亲委派委托父类加载器加载,而是直接通过UrlClassLoader (JarLoader只是将路径下的所有jar加入classpath)去加载指定的插件类。概括其逻辑为: 加载类=》通过配置文件获取插件类名和路径=》实例化该插件UrlClassLoader=>将线程上下文加载器切换为UrlClassLoader并保存原来的线程上下文加载器=》加载插件实现类=》完成基于实现类的操作=》恢复原来的线程上下文加载器。下面是DATAX加载插件实现的部分源码

 

private Reader.Job initJobReader(JobPluginCollector jobPluginCollector) {
ClassLoaderSwapper classLoaderSwapper = ClassLoaderSwapper
.newCurrentThreadClassLoaderSwapper();

classLoaderSwapper.setCurrentThreadClassLoader(LoadUtil.getJarLoader(
PluginType.READER, this.readerPluginName));

Reader.Job jobReader = (Reader.Job) LoadUtil.loadJobPlugin(
PluginType.READER, this.readerPluginName);

classLoaderSwapper.restoreCurrentThreadClassLoader();
return jobReader;
}

 


/*
* 为避免jar冲突,比如hbase可能有多个版本的读写依赖jar包,JobContainer和TaskGroupContainer
* 就需要脱离当前classLoader去加载这些jar包,执行完成后,又退回到原来classLoader上继续执行接下来的代码
*/
public final class ClassLoaderSwapper {
private ClassLoader storeClassLoader = null;

private ClassLoaderSwapper() {
}

public static ClassLoaderSwapper newCurrentThreadClassLoaderSwapper() {
return new ClassLoaderSwapper();
}

/**
* 保存当前classLoader,并将当前线程的classLoader设置为所给classLoader
*/
public ClassLoader setCurrentThreadClassLoader(ClassLoader classLoader) {
this.storeClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
return this.storeClassLoader;
}

/**
* 将当前线程的类加载器设置为保存的类加载
*/
public ClassLoader restoreCurrentThreadClassLoader() {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
Thread.currentThread().setContextClassLoader(this.storeClassLoader);
return classLoader;
}
}

 


 

打破双亲委派

上一篇:Tomcat环境搭载及入门


下一篇:服务器配置nginx.conf文件