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

 1 package com.tds.test.classloader;
 2
 3 import java.net.JarURLConnection;
 4 import java.net.URL;
 5 import java.net.URLClassLoader;
 6 import java.net.URLConnection;
 7 import java.util.ArrayList;
 8 import java.util.List;
 9
10
11 /**
12  * 插件类加载器,在插件目录中搜索jar包,并为发现的资源(jar)构造一个类加载器,将对应的jar添加到classpath中
13  * @author strawxdl
14  */
15 public class PluginClassLoader extends URLClassLoader {
16
17     private List<JarURLConnection> cachedJarFiles = new ArrayList<JarURLConnection>();
18     public PluginClassLoader() {
19         super(new URL[] {}, findParentClassLoader());
20     }
21
22     /**
23      * 将指定的文件url添加到类加载器的classpath中去,并缓存jar connection,方便以后卸载jar
24      * @param 一个可想类加载器的classpath中添加的文件url
25      */
26     public void addURLFile(URL file) {
27         try {
28             // 打开并缓存文件url连接
29
30             URLConnection uc = file.openConnection();
31             if (uc instanceof JarURLConnection) {
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 void unloadJarFiles() {
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 static ClassLoader 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         return parent;
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 package com.tds.test.classloader;
 2
 3 import java.net.MalformedURLException;
 4 import java.net.URL;
 5 import java.util.HashMap;
 6 import java.util.Map;
 7
 8 public class PluginManager {
 9     static{
10         System.out.println(PluginManager.class.getName());
11     }
12     private Map<String ,PluginClassLoader> pluginMap = new HashMap<String,PluginClassLoader>();
13     private static String packagename = "com.tds.test.classloader.Plugin1";
14     public PluginManager(){
15
16     }
17
18     public void doSome(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 void addLoader(String pluginName,PluginClassLoader loader){
29         this.pluginMap.put(pluginName, loader);
30     }
31     private PluginClassLoader getLoader(String pluginName){
32         return this.pluginMap.get(pluginName);
33     }
34     public void loadPlugin(String pluginName){
35         this.pluginMap.remove(pluginName);
36         PluginClassLoader loader = new PluginClassLoader();
37         String pluginurl = "jar:file:/D:/testclassloader/"+pluginName+".jar!/";
38         URL url = null;
39         try {
40             url = new URL(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 void unloadPlugin(String pluginName){
50         this.pluginMap.get(pluginName).unloadJarFiles();
51         this.pluginMap.remove(pluginName);
52     }
53 }

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

1 package com.tds.test.classloader;
2
3 public interface Plugin {
4
5     public void doSome();
6 }

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

 1 package com.tds.test.classloader;
 2
 3 public class Plugin1 implements Plugin{
10     public void doSome(){
11         System.out.println("Plugin1 doSome ... 我不可以?");
12     }
13 }

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

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

package com.tds.test.classloader;public class Plugin1 implements Plugin{public void doSome(){System.out.println("Plugin1 doSome ... 我可以?");}
}

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

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

 1 package com.tds.test.classloader;
 2
 3 import java.io.BufferedReader;
 4 import java.io.InputStreamReader;
 5
 6 public class TestMain {
 7
 8     public static void main(String[] args) throws Exception {
 9
10
11         PluginManager manager = new PluginManager();;
12
13         BufferedReader br = new BufferedReader(new InputStreamReader(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  success
do plugin
com.tds.test.classloader.Plugin1
Plugin1 doSome ... 我可以?
unload plugin
Unloading plugin JAR file D:\testclassloader\plugin.jar
load plugin
load plugin  success
do plugin
com.tds.test.classloader.Plugin1
Plugin1 doSome ... 我不可以?

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

  1. java 动态卸载jar包_jar包 热加载/卸载 的初步实现

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

  2. Java-查看JVM从哪个JAR包中加载指定类

    背景 方式一 方式二 背景 有的时候,我们经常会碰到java.lang.NoSuchMethodError的错误信息. 究其根源,是由于JVM的 全盘负责委托机制导致的. 关于 全盘负责委托机制 ,请 ...

  3. springboot运行jar包时候加载指定目录的其他jar支持包

    最近发生一个小故障,调试好的项目,发布成jar包后无法找到oracle的驱动,研究了一下解决了.记录一下. 写了一个run.sh脚本 #!/bin/bash cd ~ cd app nohup jav ...

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

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

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

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

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

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

  7. springboot-devtools idea或eclipse 热加载

    大家好,我是烤鸭: 今天分享一下springboot项目的热加载. 第二种方式在eclipse和idea中都可以.虽然会有一些小坑. 方式有两种: 1.   springloaded(无效) < ...

  8. java编写hot_类的热加载(Hot Deployment)的简单例子

    应用服务器一般都支持热部署(Hot Deployment),更新代码时把新编译的确类 替换旧的就行,后面的程序就执行新类中的代码.这也是由各种应用服务器的独 有的类加载器层次实现的.那如何在我们的程序 ...

  9. java热加载_java热加载

    应用服务器一般都支持热部署(Hot Deployment),更新代码时把新编译的确类 替换旧的就行,后面的程序就执行新类中的代码.这也是由各种应用服务器的独 有的类加载器层次实现的.那如何在我们的程序 ...

最新文章

  1. Docker的使用(四:Docker Registry本地私有仓库搭建知识点总结)
  2. SAP MM MB21创建预留单据报错- Error during conversion to alternative units of measure -
  3. C++中cin输入类型不匹配解决方法
  4. Linux 编程--三种常用的定时器
  5. MySQL(三)数据库的六种约束、表的关系、三大范式
  6. Win7系统不能录音怎么办
  7. 更改我的网页默认的暴风影音播放器
  8. 天龙八部荣耀版体验服服务器未响应,天龙八部荣耀版体验服
  9. STM32八路ADC采用DMA方式
  10. 常见web中间件及其漏洞概述
  11. 【Gitlab】 remote: Ask a project Owner or Maintainer to create a default branch:
  12. Linux运维常见面试题汇总
  13. win10忘记redis密码
  14. 使用 FileReader进行文件读取
  15. css浮动清除以及BFC
  16. ios浏览器打开app页面提示“浏览器打不开该网页,因为网址无效”
  17. IAR中eww、ewp、ewd···等各文件的含义和用途
  18. 程序员们,千万不要接私活!
  19. Java商城面试题(三)
  20. Outlook Express错误代码表

热门文章

  1. Android开发入门教程--Android应用程序结构分析
  2. 六:Cocos2d-x的CCLayer
  3. MySQL使用Amoeba作为Proxy时的注意事项
  4. Windows下rsync软件配置和使用【数据同步】
  5. 一级建造师考试通过了 !
  6. hdu 3046(最小割最大流)
  7. 1951: [Sdoi2010]古代猪文
  8. hdu-2199 Can you solve this equation?(二分+精度)
  9. NYOJ 660 逃离地球
  10. NYOJ 643 发短信 暴力求解