这两天做的项目中按照客户要求需要将插件模式应用到本项目中,以达到客户可以自己动态增加相关功能的目的,然后我们就根据需求制定出接口,再由客户自己实现接口,通过项目提供的相应界面将实现的jar包上传,由服务器应用对jar包进行热加载/卸载,jar包的热加载用java原生的一些api即可实现,但问题是,使用原生的api的话,是无法实现卸载jar包的功能的,除非重启应用,但又因为插件的基本特征就是热加载,热卸载,热启动等等热的问题(总之就是热,呵呵),这样的话,重启应用就显得有那么点不专业了。所以还是实现一个热加载/卸载的功能好点,一开始不知道怎么下手,后来在研究openfire时发现其插件jar包可以热加载/卸载,于是乎研究其插件加载方式。经过一天的代码调试,发现openfire是通过继承URLClassLoader实现了一个自己的PluginClassLoader(想看源码的自己去openfire官网下吧,这里就不提供了),于是我对PluginClassLoader进行了一下改造,去掉了一些没用的方法与代码,留下关键部分(这些部分仍然是openfire原生的东西).改造以后的PluginCLassLoader代码如下:

1 packagecom.tds.test.classloader;2

3 importjava.net.JarURLConnection;4 importjava.net.URL;5 importjava.net.URLClassLoader;6 importjava.net.URLConnection;7 importjava.util.ArrayList;8 importjava.util.List;9

10

11 /**

12 * 插件类加载器,在插件目录中搜索jar包,并为发现的资源(jar)构造一个类加载器,将对应的jar添加到classpath中13 *@authorstrawxdl14 */

15 public class PluginClassLoader extendsURLClassLoader {16

17 private List cachedJarFiles = new ArrayList();18 publicPluginClassLoader() {19 super(newURL[] {}, findParentClassLoader());20 }21

22 /**

23 * 将指定的文件url添加到类加载器的classpath中去,并缓存jar connection,方便以后卸载jar24 *@param一个可想类加载器的classpath中添加的文件url25 */

26 public voidaddURLFile(URL file) {27 try{28 //打开并缓存文件url连接

29

30 URLConnection uc =file.openConnection();31 if (uc instanceofJarURLConnection) {32 uc.setUseCaches(true);33 ((JarURLConnection) uc).getManifest();34 cachedJarFiles.add((JarURLConnection)uc);35 }36 } catch(Exception e) {37 System.err.println("Failed to cache plugin JAR file: " +file.toExternalForm());38 }39 addURL(file);40 }41

42 /**

43 * 卸载jar包44 */

45 public voidunloadJarFiles() {46 for(JarURLConnection url : cachedJarFiles) {47 try{48 System.err.println("Unloading plugin JAR file " +url.getJarFile().getName());49 url.getJarFile().close();50 url=null;51 } catch(Exception e) {52 System.err.println("Failed to unload JAR file\n"+e);53 }54 }55 }56

57 /**

58 * 定位基于当前上下文的父类加载器59 *@return返回可用的父类加载器.60 */

61 private staticClassLoader findParentClassLoader() {62 ClassLoader parent = PluginManager.class.getClassLoader();63 if (parent == null) {64 parent = PluginClassLoader.class.getClassLoader();65 }66 if (parent == null) {67 parent =ClassLoader.getSystemClassLoader();68 }69 returnparent;70 }71 }

然后通过PluginManager.java对每个插件jar包的PluginClassLoader进行管理,PluginManager.java实现了jar包的加载(loadPlugin方法)与卸载(unloadPlugin方法),这里为了测试假设每一个插件jar包中实现了插件接口的类的package名均为com.tds.test.classloader.Plugin1,其中Plugin1即为实现了Plugin接口的类名(这两个类稍后提供源码),这里将其写死在PluginManager中,在实际项目中当然每个插件的实现的package都会是不一样的现在不深究这个问题。下边上PluginManager.java代码:

1 packagecom.tds.test.classloader;2

3 importjava.net.MalformedURLException;4 importjava.net.URL;5 importjava.util.HashMap;6 importjava.util.Map;7

8 public classPluginManager {9 static{10 System.out.println(PluginManager.class.getName());11 }12 private Map pluginMap = new HashMap();13 private static String packagename = "com.tds.test.classloader.Plugin1";14 publicPluginManager(){15

16 }17

18 public voiddoSome(String pluginName){19

20 try{21 Class> forName = Class.forName(packagename, true, getLoader(pluginName));//this.pluginMap.get(pluginName).loadClass(packagename);

22 Plugin ins =(Plugin)forName.newInstance();23 ins.doSome();24 }catch(Exception e){25 e.printStackTrace();26 }27 }28 private voidaddLoader(String pluginName,PluginClassLoader loader){29 this.pluginMap.put(pluginName, loader);30 }31 privatePluginClassLoader getLoader(String pluginName){32 return this.pluginMap.get(pluginName);33 }34 public voidloadPlugin(String pluginName){35 this.pluginMap.remove(pluginName);36 PluginClassLoader loader = newPluginClassLoader();37 String pluginurl = "jar:file:/D:/testclassloader/"+pluginName+".jar!/";38 URL url = null;39 try{40 url = newURL(pluginurl);41 } catch(MalformedURLException e) {42 //TODO Auto-generated catch block

43 e.printStackTrace();44 }45 loader.addURLFile(url);46 addLoader(pluginName, loader);47 System.out.println("load " + pluginName + " success");48 }49 public voidunloadPlugin(String pluginName){50 this.pluginMap.get(pluginName).unloadJarFiles();51 this.pluginMap.remove(pluginName);52 }53 }

下边是接口类Plugin.java代码:

1 packagecom.tds.test.classloader;2

3 public interfacePlugin {4

5 public voiddoSome();6 }

下边是接口Plugin.java实现类:Plugin1.java

1 packagecom.tds.test.classloader;2

3 public class Plugin1 implementsPlugin{

10 public voiddoSome(){11 System.out.println("Plugin1 doSome ... 我不可以?");12 }13 }

将上述Plugin1.java单独导出为jar包,命名为plugin1.jar

修改Plugin1.java代码,如下所示:

packagecom.tds.test.classloader;public class Plugin1 implementsPlugin{public voiddoSome(){

System.out.println("Plugin1 doSome ... 我可以?");

}

}

将修改以后的Plugin1.java单独导出为Plugin2.jar

这时Plugin1.jar和Plugin2.jar对Plugin接口的实现都是不同的了,测试主类如下:

1 packagecom.tds.test.classloader;2

3 importjava.io.BufferedReader;4 importjava.io.InputStreamReader;5

6 public classTestMain {7

8 public static void main(String[] args) throwsException {9

10

11 PluginManager manager = newPluginManager();;12

13 BufferedReader br = new BufferedReader(newInputStreamReader(System.in));14 String cmd =br.readLine();15

16 while(!cmd.equals("bye")){17 if(cmd.startsWith("do")){18 String pluginName = cmd.split(" ")[1];19 manager.doSome(pluginName);20 }21 if(cmd.startsWith("load")){22 String pluginName = cmd.split(" ")[1];23 manager.loadPlugin(pluginName);24 }25 if(cmd.startsWith("unload")){26 String pluginName = cmd.split(" ")[1];27 manager.unloadPlugin(pluginName);28 }29 cmd =br.readLine();30 }31 }32 }

测试方法为:在TestMain.java中run as JavaApplication,然后plugin1.jar放到D:/testclassloader/目录下并改名为plugin.jar,最后在控制台输入load plugin即可将plugin.jar使用自定义的PluginClassLoader进行装载,这时再使用do plugin命令即可调用该插件的实现,现在卸载plugin.jar,输入unload plugin命令即可卸载掉plugin.jar,接下来关键时刻到了删除D:/testclassloader/目录的plugin.jar,将plugin2.jar放入该目录并更名为plugin.jar,重复之前的jar包加载命令并运行之,你会发现两次运行的结果不一样了下边是我运行的示例:

com.tds.test.classloader.PluginManager

load plugin

load plugin successdoplugin

com.tds.test.classloader.Plugin1

Plugin1 doSome ... 我可以?unload plugin

Unloading plugin JAR file D:\testclassloader\plugin.jar

load plugin

load plugin successdoplugin

com.tds.test.classloader.Plugin1

Plugin1 doSome ... 我不可以?

可能有的人看了以后不明白我做了这么多是为什么,这些人你们可以试一下用java原生的classloader进行jar包的加载,看看有没有办法卸载掉已经加载的jar包,最明显的一个现象就是你如果不卸载掉该jar包的话,我上边要求的删除plugin.jar就无法删除了,除非你把应用停掉,才可以删除该jar包,我现在所做的这一切也都只是为了不需要你重启或者停止服务器应用就可以直接更换相应的jar包,这个是插件模式必须的要求了。

最后关于PluginClassLoader类中addURLFile和unloadJarFiles两个方法,我有点疑惑,这个类加载器也只是在addURLFile的时候将对应jar文件进行了URLConnection uc = file.openConnection();然后缓存了这个连接,在卸载jar时关闭了这个连接,这两处的打开和关闭操作为什么会对jvm对相应jar包的加载产生影响?希望有明白的人看到本文后指点一二,不甚感激!!

PS:本人不善言辞写的不好观者多多包涵!哈哈...

java 动态卸载jar包_jar包 热加载/卸载 的初步实现相关推荐

  1. jar包 热加载/卸载 的初步实现

    这两天做的项目中按照客户要求需要将插件模式应用到本项目中,以达到客户可以自己动态增加相关功能的目的,然后我们就根据需求制定出接口,再由客户自己实现接口,通过项目提供的相应界面将实现的jar包上传,由服 ...

  2. 京东 java 研发岗二面:Tomcat 是如何做到热加载和热部署的?

    前言 热部署就是在服务器运行时重新部署项目,热加载即在在运行时重新加载 class,从而升级应用. 通常情况下在开发环境中我们使用的是热加载,因为热加载的实现的方式在 Web 容器中启动一个后台线程, ...

  3. 热加载和热部署,没听过?看看 Tomcat 是怎么实现的

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者 | 不学无数的程序员 来源 | https://urlify ...

  4. 死磕Tomcat系列(6)——Tomcat如何做到热加载和热部署的

    死磕Tomcat系列(6)--Tomcat如何做到热加载和热部署的 热部署就是在服务器运行时重新部署项目,热加载即在在运行时重新加载class,从而升级应用. 通常情况下在开发环境中我们使用的是热加载 ...

  5. URLClassLoader热加载、卸载,自定义热加载数据池 jar包(附:Springboot + Hutool-db 使用实例)

    参考文章: 利用classloader实现jar包的加载和卸载,实现类的热替换 动态添加classpath 关于ClassLoader.getSystemClassLoader()和Thread.cu ...

  6. java spring包_java 自定义加载器,加载spring包,动态加载实现,jar包隔离,tomcat加载webapp方式...

    java 自定义加载器,加载spring包,动态加载实现,jar包隔离,tomcat加载webapp方式 发布时间:2018-08-20 12:02, 浏览次数:774 , 标签: java spri ...

  7. 京东一面:Spring Boot 如何热加载jar实现动态插件?

    一.背景 动态插件化编程是一件很酷的事情,能实现业务功能的 解耦 便于维护,另外也可以提升 可扩展性 随时可以在不停服务器的情况下扩展功能,也具有非常好的 开放性 除了自己的研发人员可以开发功能之外, ...

  8. Java 错误:找不到或无法加载主类(源文件中含有包名 package)

    1. 问题定位 编译(javac)和执行(java)java 程序时,出现这种类型的错误:找不到或无法加载主类: 首先排除是否是环境变量配置不当造成的问题,只要保证,命令行界面能够识别 javac/j ...

  9. 厉害了,自己手写一个Java热加载!

    热加载:在不停止程序运行的情况下,对类(对象)的动态替换. Java ClassLoader 简述 Java中的类从被加载到内存中到卸载出内存为止,一共经历了七个阶段:加载.验证.准备.解析.初始化. ...

最新文章

  1. PHP与SQL注入***(实战篇五)
  2. Java三行代码搞定MD5加密
  3. 005. how is RFC to backend determined - maintenance view IWFNDV_MGDEAM
  4. 由LintCode问题子集出发,浅析ArrayList的拷贝问题
  5. 笑说设计模式-小白逃课被点名
  6. Tecplot云图锯齿状边界解决办法
  7. 人工智能是未来发展趋势吗 用Python入门怎么样 赶紧看看
  8. JAVA线程1 - 基本概念
  9. Solr中的数据导入
  10. 如何才能在jsp文件中使用el表达式
  11. 如何为自定义的控件在工具箱中自定义个性化的图标
  12. 自定义ArcView-构造拓展性高的view
  13. steam网络相关问题-社区错误代码118/无法自动登陆/短期内来自您网络的失败登录过多/无法连接至steam网络(2021/2/18更新)
  14. 用supermemo背单词达到5000词条
  15. 老外用VB6写的Windows驱动备份软件
  16. C++ 调用Asprise OCR识别图片中的文字
  17. 版本号规范,镜像版本SNAPSHOT,LATEST 和 RELEASE 版本
  18. 基于CNN的垃圾分类模型
  19. 记录一次h5上传身份证照片、上传人面像
  20. 计算机毕业设计谢辞怎么写,关于毕业论文的谢辞范文(通用12篇)

热门文章

  1. unity 小任务一之点击方块变色
  2. 北大青鸟BCSP8.0-s2项目答辩-Smiling Shop项目
  3. Python新手入门-操作篇
  4. 法线贴图Normalmap小结
  5. HUAWEI 机试题:最长连续子序
  6. nginx目录路径重定向
  7. python函数指针
  8. React攻略秘籍二:React中使用css的五种方法(包括less与styled-compenents)
  9. [转]读《程序员应该知道的97件事》
  10. 12. 查询每一门课的间接先修课(即先修课的先修课)