自定义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
|