Spring boot的ClasssLoader加载和卸载jar/class

一.前言

在开发过程中,有时候需要动态加载各种jar/class到程序中,然而又只使用一次,或者加载的jar/class中有改动需要持续升级。程序一直在运行,每次加载进去的jar/class如果过多,对系统会造成很大的压力,而且已经加载进去的jar/class不能覆盖,从而造成得不到想要的结果。此时就需要考虑对加载进去的jar/class进行卸载。

 

二.思路

我们知道,当一个java类的完整的生命周期会经历 加载、连接、初始化、使用、和卸 五个阶段,当该类的class对象不再被引用之后,该类的生命周期也就结束了,那么该类会被GC回收,从而达到卸载的功能。

       

三.案例编写

编写一个简单的自定义classLoad,继承 URLClassLoader 。

此处特别注意 MyClassLoad 构造函数中调用父级构造的第二个参数

 
  1. package com.vae.classLoader.loader;
  2.  
  3. import com.vae.classLoader.Application;
  4.  
  5. import java.io.File;
  6. import java.net.MalformedURLException;
  7. import java.net.URL;
  8. import java.net.URLClassLoader;
  9.  
  10. /**
  11. * 自定义的ClassLader
  12. */
  13. public class MyClassLoad extends URLClassLoader {
  14.  
  15. public MyClassLoad(String pathName) {
  16. /**
  17. * 第一个参数为jar/class地址
  18. * 第二个参数是父级classLoader,此处是spring boot 的classLoader,意味着继承了spring boot
  19. * 的classLoader,在MyClassLoad中可使用到spring boot的jvm
  20. */
  21. super(getMyURLs(pathName), Application.class.getClassLoader());
  22. }
  23.  
  24. private static URL[] getMyURLs(String pathName) {
  25. URL url = null;
  26. try {
  27. url = new File(pathName).toURI().toURL();
  28. } catch (MalformedURLException e) {
  29. e.printStackTrace();
  30. }
  31. return new URL[]{url};
  32. }
  33.  
  34. @Override
  35. protected Class<?> findClass(String name) throws ClassNotFoundException {
  36. return super.findClass(name);
  37. }
  38.  
  39. @Override
  40. public Class<?> loadClass(String name) throws ClassNotFoundException {
  41. return super.loadClass(name, false);
  42. }
  43.  
  44. @Override
  45. public URL[] getURLs() {
  46. return super.getURLs();
  47. }
  48.  
  49. @Override
  50. public void addURL(URL url) {
  51. super.addURL(url);
  52. }
  53. }
 

四、测试

新建一个简单的project工程,只编写一个main方法,代码结构如下图,打成jar包后放在 F:/v/test.jar,修改打印部分数字为2再打一个jar包放在 F:/v/v/test.jar(注意目录层次)

 

 编写测试方法

 
  1. package com.vae.classLoader.loader;
  2.  
  3. import com.vae.classLoader.Application;
  4.  
  5. import java.lang.reflect.Method;
  6. import java.net.URL;
  7. import java.net.URLClassLoader;
  8.  
  9. public class ClassUnLoadTest {
  10.  
  11. /**
  12. * 测试class的加载与卸载情况。
  13. * @param args
  14. * @throws Exception
  15. */
  16. public static void main(String[] args) throws Exception {
  17.  
  18. printSupJvm();
  19.  
  20. MyClassLoad loader1 = new MyClassLoad("f:/v/test.jar");
  21. Class<?> clazz1 = loader1.loadClass("com.vae.Test");
  22. Object a1 = clazz1.newInstance();
  23. Method method = clazz1.getMethod("main", String[].class);
  24. method.invoke(null, (Object) new String[]{"2019"});
  25. for (URL url : loader1.getURLs()){
  26. System.out.println(url.toString());
  27. }
  28. // 清除相关引用,等待gc回收
  29. a1 = null;
  30. clazz1 = null;
  31. loader1 = null;
  32.  
  33. printSupJvm();
  34.  
  35. MyClassLoad loader2 = new MyClassLoad("f:/v/v/test.jar");
  36. Class<?> clazz2 = loader2.loadClass("com.vae.Test");
  37. Object a2 = clazz2.newInstance();
  38. Method method2 = clazz2.getMethod("main", String[].class);
  39. method2.invoke(null, (Object) new String[]{"2019"});
  40. for (URL url : loader2.getURLs()){
  41. System.out.println(url.toString());
  42. }
  43. a2 = null;
  44. clazz2 = null;
  45. loader2 = null;
  46.  
  47. printSupJvm();
  48. }
  49.  
  50. /**
  51. * 打印输出父级SpringBoot中的className
  52. */
  53. private static void printSupJvm(){
  54. URLClassLoader load = (URLClassLoader)Application.class.getClassLoader();
  55. System.out.println(load.getClass().getName() + "===================================");
  56. for (URL url : load.getURLs()){
  57. System.out.println(url.toString());
  58. }
  59. System.out.println("================================================================");
  60. }
  61. }
 

 运行输出打印结果如下

打印输出结构:

"C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" ...

sun.misc.Launcher$AppClassLoader===================================

 Application的classLoad中所有的jar/class列表......
================================================================
Hello World!__ 1 __
file:/f:/v/test.jar
sun.misc.Launcher$AppClassLoader===================================
 Application的classLoad中所有的jar/class列表......
================================================================
Hello World!__ 2 __
file:/f:/v/v/test.jar
sun.misc.Launcher$AppClassLoader===================================
 Application的classLoad中所有的jar/class列表......
================================================================

Process finished with exit code 0

 
  1. "C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2018.3.6\lib\idea_rt.jar=52595:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2018.3.6\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;D:\workRepository\example\case-classLoader\target\classes;D:\maven\repository\idea\org\springframework\boot\spring-boot-starter\2.0.1.RELEASE\spring-boot-starter-2.0.1.RELEASE.jar;D:\maven\repository\idea\org\springframework\boot\spring-boot\2.0.1.RELEASE\spring-boot-2.0.1.RELEASE.jar;D:\maven\repository\idea\org\springframework\spring-context\5.0.5.RELEASE\spring-context-5.0.5.RELEASE.jar;D:\maven\repository\idea\org\springframework\spring-aop\5.0.5.RELEASE\spring-aop-5.0.5.RELEASE.jar;D:\maven\repository\idea\org\springframework\spring-beans\5.0.5.RELEASE\spring-beans-5.0.5.RELEASE.jar;D:\maven\repository\idea\org\springframework\spring-expression\5.0.5.RELEASE\spring-expression-5.0.5.RELEASE.jar;D:\maven\repository\idea\org\springframework\boot\spring-boot-autoconfigure\2.0.1.RELEASE\spring-boot-autoconfigure-2.0.1.RELEASE.jar;D:\maven\repository\idea\org\springframework\boot\spring-boot-starter-logging\2.0.1.RELEASE\spring-boot-starter-logging-2.0.1.RELEASE.jar;D:\maven\repository\idea\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\maven\repository\idea\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\maven\repository\idea\org\apache\logging\log4j\log4j-to-slf4j\2.10.0\log4j-to-slf4j-2.10.0.jar;D:\maven\repository\idea\org\apache\logging\log4j\log4j-api\2.10.0\log4j-api-2.10.0.jar;D:\maven\repository\idea\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;D:\maven\repository\idea\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\maven\repository\idea\org\springframework\spring-core\5.0.5.RELEASE\spring-core-5.0.5.RELEASE.jar;D:\maven\repository\idea\org\springframework\spring-jcl\5.0.5.RELEASE\spring-jcl-5.0.5.RELEASE.jar;D:\maven\repository\idea\org\yaml\snakeyaml\1.19\snakeyaml-1.19.jar;D:\maven\repository\idea\org\apache\commons\commons-lang3\3.5\commons-lang3-3.5.jar;D:\maven\repository\idea\org\apache\tomcat\embed\tomcat-embed-core\8.5.29\tomcat-embed-core-8.5.29.jar;D:\maven\repository\idea\org\apache\tomcat\tomcat-annotations-api\8.5.29\tomcat-annotations-api-8.5.29.jar;D:\maven\repository\idea\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;D:\maven\repository\idea\log4j\log4j\1.2.17\log4j-1.2.17.jar" com.vae.classLoader.loader.ClassUnLoadTest
  2. sun.misc.Launcher$AppClassLoader===================================
  3. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar
  4. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/deploy.jar
  5. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/access-bridge-64.jar
  6. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/cldrdata.jar
  7. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/dnsns.jar
  8. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jaccess.jar
  9. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jfxrt.jar
  10. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/localedata.jar
  11. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/nashorn.jar
  12. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunec.jar
  13. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunjce_provider.jar
  14. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunmscapi.jar
  15. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunpkcs11.jar
  16. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/zipfs.jar
  17. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/javaws.jar
  18. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jce.jar
  19. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfr.jar
  20. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfxswt.jar
  21. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar
  22. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/management-agent.jar
  23. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/plugin.jar
  24. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar
  25. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar
  26. file:/D:/workRepository/example/case-classLoader/target/classes/
  27. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-starter/2.0.1.RELEASE/spring-boot-starter-2.0.1.RELEASE.jar
  28. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot/2.0.1.RELEASE/spring-boot-2.0.1.RELEASE.jar
  29. file:/D:/maven/repository/idea/org/springframework/spring-context/5.0.5.RELEASE/spring-context-5.0.5.RELEASE.jar
  30. file:/D:/maven/repository/idea/org/springframework/spring-aop/5.0.5.RELEASE/spring-aop-5.0.5.RELEASE.jar
  31. file:/D:/maven/repository/idea/org/springframework/spring-beans/5.0.5.RELEASE/spring-beans-5.0.5.RELEASE.jar
  32. file:/D:/maven/repository/idea/org/springframework/spring-expression/5.0.5.RELEASE/spring-expression-5.0.5.RELEASE.jar
  33. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-autoconfigure/2.0.1.RELEASE/spring-boot-autoconfigure-2.0.1.RELEASE.jar
  34. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-starter-logging/2.0.1.RELEASE/spring-boot-starter-logging-2.0.1.RELEASE.jar
  35. file:/D:/maven/repository/idea/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar
  36. file:/D:/maven/repository/idea/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar
  37. file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-to-slf4j/2.10.0/log4j-to-slf4j-2.10.0.jar
  38. file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-api/2.10.0/log4j-api-2.10.0.jar
  39. file:/D:/maven/repository/idea/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar
  40. file:/D:/maven/repository/idea/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar
  41. file:/D:/maven/repository/idea/org/springframework/spring-core/5.0.5.RELEASE/spring-core-5.0.5.RELEASE.jar
  42. file:/D:/maven/repository/idea/org/springframework/spring-jcl/5.0.5.RELEASE/spring-jcl-5.0.5.RELEASE.jar
  43. file:/D:/maven/repository/idea/org/yaml/snakeyaml/1.19/snakeyaml-1.19.jar
  44. file:/D:/maven/repository/idea/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar
  45. file:/D:/maven/repository/idea/org/apache/tomcat/embed/tomcat-embed-core/8.5.29/tomcat-embed-core-8.5.29.jar
  46. file:/D:/maven/repository/idea/org/apache/tomcat/tomcat-annotations-api/8.5.29/tomcat-annotations-api-8.5.29.jar
  47. file:/D:/maven/repository/idea/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar
  48. file:/D:/maven/repository/idea/log4j/log4j/1.2.17/log4j-1.2.17.jar
  49. file:/D:/Program%20Files/JetBrains/IntelliJ%20IDEA%20Community%20Edition%202018.3.6/lib/idea_rt.jar
  50. ================================================================
  51. Hello World!__ 1 __
  52. file:/f:/v/test.jar
  53. sun.misc.Launcher$AppClassLoader===================================
  54. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar
  55. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/deploy.jar
  56. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/access-bridge-64.jar
  57. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/cldrdata.jar
  58. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/dnsns.jar
  59. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jaccess.jar
  60. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jfxrt.jar
  61. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/localedata.jar
  62. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/nashorn.jar
  63. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunec.jar
  64. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunjce_provider.jar
  65. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunmscapi.jar
  66. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunpkcs11.jar
  67. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/zipfs.jar
  68. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/javaws.jar
  69. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jce.jar
  70. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfr.jar
  71. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfxswt.jar
  72. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar
  73. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/management-agent.jar
  74. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/plugin.jar
  75. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar
  76. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar
  77. file:/D:/workRepository/example/case-classLoader/target/classes/
  78. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-starter/2.0.1.RELEASE/spring-boot-starter-2.0.1.RELEASE.jar
  79. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot/2.0.1.RELEASE/spring-boot-2.0.1.RELEASE.jar
  80. file:/D:/maven/repository/idea/org/springframework/spring-context/5.0.5.RELEASE/spring-context-5.0.5.RELEASE.jar
  81. file:/D:/maven/repository/idea/org/springframework/spring-aop/5.0.5.RELEASE/spring-aop-5.0.5.RELEASE.jar
  82. file:/D:/maven/repository/idea/org/springframework/spring-beans/5.0.5.RELEASE/spring-beans-5.0.5.RELEASE.jar
  83. file:/D:/maven/repository/idea/org/springframework/spring-expression/5.0.5.RELEASE/spring-expression-5.0.5.RELEASE.jar
  84. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-autoconfigure/2.0.1.RELEASE/spring-boot-autoconfigure-2.0.1.RELEASE.jar
  85. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-starter-logging/2.0.1.RELEASE/spring-boot-starter-logging-2.0.1.RELEASE.jar
  86. file:/D:/maven/repository/idea/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar
  87. file:/D:/maven/repository/idea/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar
  88. file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-to-slf4j/2.10.0/log4j-to-slf4j-2.10.0.jar
  89. file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-api/2.10.0/log4j-api-2.10.0.jar
  90. file:/D:/maven/repository/idea/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar
  91. file:/D:/maven/repository/idea/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar
  92. file:/D:/maven/repository/idea/org/springframework/spring-core/5.0.5.RELEASE/spring-core-5.0.5.RELEASE.jar
  93. file:/D:/maven/repository/idea/org/springframework/spring-jcl/5.0.5.RELEASE/spring-jcl-5.0.5.RELEASE.jar
  94. file:/D:/maven/repository/idea/org/yaml/snakeyaml/1.19/snakeyaml-1.19.jar
  95. file:/D:/maven/repository/idea/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar
  96. file:/D:/maven/repository/idea/org/apache/tomcat/embed/tomcat-embed-core/8.5.29/tomcat-embed-core-8.5.29.jar
  97. file:/D:/maven/repository/idea/org/apache/tomcat/tomcat-annotations-api/8.5.29/tomcat-annotations-api-8.5.29.jar
  98. file:/D:/maven/repository/idea/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar
  99. file:/D:/maven/repository/idea/log4j/log4j/1.2.17/log4j-1.2.17.jar
  100. file:/D:/Program%20Files/JetBrains/IntelliJ%20IDEA%20Community%20Edition%202018.3.6/lib/idea_rt.jar
  101. ================================================================
  102. Hello World!__ 2 __
  103. file:/f:/v/v/test.jar
  104. sun.misc.Launcher$AppClassLoader===================================
  105. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar
  106. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/deploy.jar
  107. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/access-bridge-64.jar
  108. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/cldrdata.jar
  109. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/dnsns.jar
  110. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jaccess.jar
  111. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jfxrt.jar
  112. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/localedata.jar
  113. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/nashorn.jar
  114. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunec.jar
  115. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunjce_provider.jar
  116. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunmscapi.jar
  117. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunpkcs11.jar
  118. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/zipfs.jar
  119. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/javaws.jar
  120. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfr.jar
  1. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfxswt.jar
  2. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar
  3. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/management-agent.jar
  4. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/plugin.jar
  5. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar
  6. file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar
  7. file:/D:/workRepository/example/case-classLoader/target/classes/
  8. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-starter/2.0.1.RELEASE/spring-boot-starter-2.0.1.RELEASE.jar
  9. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot/2.0.1.RELEASE/spring-boot-2.0.1.RELEASE.jar
  10. file:/D:/maven/repository/idea/org/springframework/spring-context/5.0.5.RELEASE/spring-context-5.0.5.RELEASE.jar
  11. file:/D:/maven/repository/idea/org/springframework/spring-aop/5.0.5.RELEASE/spring-aop-5.0.5.RELEASE.jar
  12. file:/D:/maven/repository/idea/org/springframework/spring-beans/5.0.5.RELEASE/spring-beans-5.0.5.RELEASE.jar
  13. file:/D:/maven/repository/idea/org/springframework/spring-expression/5.0.5.RELEASE/spring-expression-5.0.5.RELEASE.jar
  14. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-autoconfigure/2.0.1.RELEASE/spring-boot-autoconfigure-2.0.1.RELEASE.jar
  15. file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-starter-logging/2.0.1.RELEASE/spring-boot-starter-logging-2.0.1.RELEASE.jar
  16. file:/D:/maven/repository/idea/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar
  17. file:/D:/maven/repository/idea/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar
  18. file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-to-slf4j/2.10.0/log4j-to-slf4j-2.10.0.jar
  19. file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-api/2.10.0/log4j-api-2.10.0.jar
  20. file:/D:/maven/repository/idea/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar
  21. file:/D:/maven/repository/idea/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar
  22. file:/D:/maven/repository/idea/org/springframework/spring-core/5.0.5.RELEASE/spring-core-5.0.5.RELEASE.jar
  23. file:/D:/maven/repository/idea/org/springframework/spring-jcl/5.0.5.RELEASE/spring-jcl-5.0.5.RELEASE.jar
  24. file:/D:/maven/repository/idea/org/yaml/snakeyaml/1.19/snakeyaml-1.19.jar
  25. file:/D:/maven/repository/idea/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar
  26. file:/D:/maven/repository/idea/org/apache/tomcat/embed/tomcat-embed-core/8.5.29/tomcat-embed-core-8.5.29.jar
  27. file:/D:/maven/repository/idea/org/apache/tomcat/tomcat-annotations-api/8.5.29/tomcat-annotations-api-8.5.29.jar
  28. file:/D:/maven/repository/idea/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar
  29. file:/D:/maven/repository/idea/log4j/log4j/1.2.17/log4j-1.2.17.jar
  30. file:/D:/Program%20Files/JetBrains/IntelliJ%20IDEA%20Community%20Edition%202018.3.6/lib/idea_rt.jar
  31. ================================================================
  32.  
  33. Process finished with exit code 0
 

由上述可以看出,每次加载进去的jar,与父级SpringBoot的Application的classLoad中的jar是隔离的,每次new出来的MyClassLaoder都是一个新的classLoad,把每次new出来的自定义的classLoad置为null,等待GC回收,实现卸载jar/class功能。

Spring Boot 如何热加载jar实现动态插件? (qq.com)

一、背景

动态插件化编程是一件很酷的事情,能实现业务功能的 「解耦」 便于维护,另外也可以提升 「可扩展性」 随时可以在不停服务器的情况下扩展功能,也具有非常好的 「开放性」 除了自己的研发人员可以开发功能之外,也能接纳第三方开发商按照规范开发的插件。

常见的动态插件的实现方式有 SPIOSGI 等方案,由于脱离了 Spring IOC 的管理在插件中无法注入主程序的 Bean 对象,例如主程序中已经集成了 Redis 但是在插件中无法使用。

本文主要介绍在 Spring Boot 工程中热加载 jar 包并注册成为 Bean 对象的一种实现思路,在动态扩展功能的同时支持在插件中注入主程序的 Bean 实现功能更强大的插件。

 

二、热加载 jar 包

通过指定的链接或者路径动态加载 jar 包,可以使用 URLClassLoader 的 addURL 方法来实现,样例代码如下:

「ClassLoaderUtil 类」

public class ClassLoaderUtil {
    public static ClassLoader getClassLoader(String url) {
        try {
            Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }
            URLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader());
            method.invoke(classLoader, new URL(url));
            return classLoader;
        } catch (Exception e) {
            log.error("getClassLoader-error", e);
            return null;
        }
    }
}

其中在创建 URLClassLoader 时,指定当前系统的 ClassLoader 为父类加载器  ClassLoader.getSystemClassLoader() 这步比较关键,用于打通主程序与插件之间的 ClassLoader ,解决把插件注册进 IOC 时的各种 ClassNotFoundException 问题。

 

三、动态注册 Bean

将插件 jar 中加载的实现类注册到 Spring 的 IOC 中,同时也会将 IOC 中已有的 Bean 注入进插件中;分别在程序启动时和运行时两种场景下的实现方式。

3.1. 启动时注册

使用 ImportBeanDefinitionRegistrar  实现在 Spring Boot 启动时动态注册插件的 Bean,样例代码如下:「PluginImportBeanDefinitionRegistrar 类」

public class PluginImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    private final String targetUrl = "file:/D:/SpringBootPluginTest/plugins/plugin-impl-0.0.1-SNAPSHOT.jar";
    private final String pluginClass = "com.plugin.impl.PluginImpl";

    @SneakyThrows
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl);
        Class<?> clazz = classLoader.loadClass(pluginClass);
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        BeanDefinition beanDefinition = builder.getBeanDefinition();
        registry.registerBeanDefinition(clazz.getName(), beanDefinition);
    }
}

 

3.2. 运行时注册

程序运行时动态注册插件的 Bean 通过使用 ApplicationContext 对象来实现,样例代码如下:

@GetMapping("/reload")
public Object reload() throws ClassNotFoundException {
  ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl);
  Class<?> clazz = classLoader.loadClass(pluginClass);
  springUtil.registerBean(clazz.getName(), clazz);
  PluginInterface plugin = (PluginInterface)springUtil.getBean(clazz.getName());
  return plugin.sayHello("test reload");
}

「SpringUtil 类」

@Component
public class SpringUtil implements ApplicationContextAware {
    private DefaultListableBeanFactory defaultListableBeanFactory;
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
    }

    public void registerBean(String beanName, Class<?> clazz) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());
    }

    public Object getBean(String name) {
        return applicationContext.getBean(name);
    }
}

 

四、总结

本文介绍的插件化实现思路通过 「共用 ClassLoader」 和 「动态注册 Bean」 的方式,打通了插件与主程序之间的类加载器和 Spring 容器,使得可以非常方便的实现插件与插件之间和插件与主程序之间的 「类交互」,例如在插件中注入主程序的 Redis、DataSource、调用远程 Dubbo 接口等等。

但是由于没有对插件之间的 ClassLoader 进行 「隔离」 也可能会存在如类冲突、版本冲突等问题;并且由于 ClassLoader 中的 Class 对象无法销毁,所以除非修改类名或者类路径,不然插件中已加载到 ClassLoader 的类是没办法动态修改的。

所以本方案比较适合插件数据量不会太多、具有较好的开发规范、插件经过测试后才能上线或发布的场景。

 

五、完整 demo

https://github.com/zlt2000/springs-boot-plugin-test

Possible classloader leak through incomplete clearing of annotation caches · Issue #31170 · spring-projects/spring-framework (github.com)

 

posted @ 2024-06-26 18:35  CharyGao  阅读(47)  评论(0编辑  收藏  举报