自定义classload与热加载

自定义classload与热加载

场景

  Classload是java语言较为底层的技术,对于自定义框架,中间件开发等都有广泛的应用场景,可以利用它设置出较为灵活的应用。像duckula就是使用它完成插件机制,为扩展duckula提供了可能。还有插件平台,也是使用它来实现ES业务插件的热加载等功能。

双亲委派模型

  类加载这个概念应该算是Java语言的一种创新,目的是为了将类的加载过程与虚拟机解耦,达到”通过类的全限定名来获取描述此类的二进制字节流“的目的。实现这个功能的代码模块就是类加载器。类加载器的基本模型就是大名鼎鼎的双亲委派模型(Parents Delegation Model)。,在需要加载一个类的时候,我们首先判断该类是否已被加载,如果没有就判断是否已被父加载器加载,如果还没有再调用自己的findClass方法尝试加载。基本的模型就是这样:

工具类简化classload

自定义classload

1
2
3
4
5
    <dependency>
<groupId>net.wicp.tams</groupId>
<artifactId>common-apiext</artifactId>
<version>最后版本</version>
</dependency>

示例代码在git库: https://github.com/rjzjh/demo-tams

ext下面有两个目录,demo-tams-apiext-client和demo-tams-apiext-client-new用于模拟热加载。它的代码就是demo-tams-apiext-client模块。

看看如何使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private String pluginDir = PathType.getPath("clp:/../../ext");	

private void doClassLoaderPlugin() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ClassLoaderPlugin classLoaderPlugin1 = new ClassLoaderPlugin(pluginDir, null, 2);
Class<?> findClass = classLoaderPlugin1.loadClass("net.wicp.tams.common.apiext.test.services.SayHello");
Object invokeMothed = ReflectAssist.invokeMothed(findClass.newInstance(), "retHello");
System.out.println("doClassLoaderPlugin=" + invokeMothed);
classLoaderPlugin1.close();
try {
Class<?> findClass2 = classLoaderPlugin1.loadClass("net.wicp.tams.common.apiext.test.services.SayHello");
Object invokeMothed2 = ReflectAssist.invokeMothed(findClass2.newInstance(), "retHello");
System.out.println("doClassLoaderPlugin2=" + invokeMothed2);
} catch (Exception e) {
e.printStackTrace();
}
ClassLoaderPlugin classLoaderPlugin3 = new ClassLoaderPlugin(pluginDir, null, 2);
Class<?> findClass3 = classLoaderPlugin3.loadClass("net.wicp.tams.common.apiext.test.services.SayHello");
Object invokeMothed3 = ReflectAssist.invokeMothed(findClass3.newInstance(), "retHello");
System.out.println("doClassLoaderPlugin3=" + invokeMothed3);
}

输出日志:

1
2
3
doClassLoaderPlugin=rjzjh
java.lang.NullPointerException
doClassLoaderPlugin3=rjzjh

关键代码:

1
ClassLoaderPlugin classLoaderPlugin3 = new ClassLoaderPlugin(pluginDir, null, 2);

pluginDir:自定义classload在哪个目录下去加载类

null: 父classload,null表示当前线程的classload

2: 加载的类在该文件夹的第几层。jar包在目录ext\demo-tams-apiext-client\下,所以是第2层。lib包中的类是通过demo-tams-apiext-client.jar包中的classpath进行加载的。

插件机制

我们实现一个带插件功能一般来说就是主程序里使用接口,主体功能都通过接口完成。而实现类则需要不同插件件来开发,由于某些原因,主程序要保证不能挂,且会做HA机制,那么如何对插件进行热替换也就是插件机制需要考虑的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void doPluginHot() throws InstantiationException, IllegalAccessException {
Plugin plugin = new Plugin(PathType.getPath("clp:/../../ext/demo-tams-apiext-client"),
"net.wicp.tams.common.apiext.test.services.ISayHello", ClassloadMain.class.getClassLoader(), null,
Arrays.asList("net.wicp.tams.common.apiext.test.services.ISayHello"), 1);
Class<?> findClass = plugin.loadSingle();
Object invokeMothed = ReflectAssist.invokeMothed(findClass.newInstance(), "retHello");
plugin.close();
System.out.println("doPlugin=" + invokeMothed);

Plugin pluginnew = new Plugin(PathType.getPath("clp:/../../ext/demo-tams-apiext-client-new"),
"net.wicp.tams.common.apiext.test.services.ISayHello", ClassloadMain.class.getClassLoader(), null,
Arrays.asList("net.wicp.tams.common.apiext.test.services.ISayHello"), 1);
Class<?> findClassnew = pluginnew.loadSingle();
Object invokeMothednew = ReflectAssist.invokeMothed(findClassnew.newInstance(), "retHello");
pluginnew.close();
System.out.println("doPluginnew=" + invokeMothednew);
}

上面的代码演示了如何把  demo-tams-apiext-client插件替换为demo-tams-apiext-client-new插件。模拟了插件的热加载。

输出:

1
2
doPlugin=rjzjh
doPluginnew=rjzjh+new

坚持原创技术分享,您的支持是我前进的动力!