Spring boot的ClasssLoader加载和卸载jar/class
一.前言
在开发过程中,有时候需要动态加载各种jar/class到程序中,然而又只使用一次,或者加载的jar/class中有改动需要持续升级。程序一直在运行,每次加载进去的jar/class如果过多,对系统会造成很大的压力,而且已经加载进去的jar/class不能覆盖,从而造成得不到想要的结果。此时就需要考虑对加载进去的jar/class进行卸载。
二.思路
我们知道,当一个java类的完整的生命周期会经历 加载、连接、初始化、使用、和卸载 五个阶段,当该类的class对象不再被引用之后,该类的生命周期也就结束了,那么该类会被GC回收,从而达到卸载的功能。
三.案例编写
编写一个简单的自定义classLoad,继承 URLClassLoader 。
此处特别注意 MyClassLoad 构造函数中调用父级构造的第二个参数
-
package com.vae.classLoader.loader;
-
-
import com.vae.classLoader.Application;
-
-
import java.io.File;
-
import java.net.MalformedURLException;
-
import java.net.URL;
-
import java.net.URLClassLoader;
-
-
/**
-
* 自定义的ClassLader
-
*/
-
public class MyClassLoad extends URLClassLoader {
-
-
public MyClassLoad(String pathName) {
-
/**
-
* 第一个参数为jar/class地址
-
* 第二个参数是父级classLoader,此处是spring boot 的classLoader,意味着继承了spring boot
-
* 的classLoader,在MyClassLoad中可使用到spring boot的jvm
-
*/
-
super(getMyURLs(pathName), Application.class.getClassLoader());
-
}
-
-
private static URL[] getMyURLs(String pathName) {
-
URL url = null;
-
try {
-
url = new File(pathName).toURI().toURL();
-
} catch (MalformedURLException e) {
-
e.printStackTrace();
-
}
-
return new URL[]{url};
-
}
-
-
-
protected Class<?> findClass(String name) throws ClassNotFoundException {
-
return super.findClass(name);
-
}
-
-
-
public Class<?> loadClass(String name) throws ClassNotFoundException {
-
return super.loadClass(name, false);
-
}
-
-
-
public URL[] getURLs() {
-
return super.getURLs();
-
}
-
-
-
public void addURL(URL url) {
-
super.addURL(url);
-
}
-
}
四、测试
新建一个简单的project工程,只编写一个main方法,代码结构如下图,打成jar包后放在 F:/v/test.jar,修改打印部分数字为2再打一个jar包放在 F:/v/v/test.jar(注意目录层次)
编写测试方法
-
package com.vae.classLoader.loader;
-
-
import com.vae.classLoader.Application;
-
-
import java.lang.reflect.Method;
-
import java.net.URL;
-
import java.net.URLClassLoader;
-
-
public class ClassUnLoadTest {
-
-
/**
-
* 测试class的加载与卸载情况。
-
* @param args
-
* @throws Exception
-
*/
-
public static void main(String[] args) throws Exception {
-
-
printSupJvm();
-
-
MyClassLoad loader1 = new MyClassLoad("f:/v/test.jar");
-
Class<?> clazz1 = loader1.loadClass("com.vae.Test");
-
Object a1 = clazz1.newInstance();
-
Method method = clazz1.getMethod("main", String[].class);
-
method.invoke(null, (Object) new String[]{"2019"});
-
for (URL url : loader1.getURLs()){
-
System.out.println(url.toString());
-
}
-
// 清除相关引用,等待gc回收
-
a1 = null;
-
clazz1 = null;
-
loader1 = null;
-
-
printSupJvm();
-
-
MyClassLoad loader2 = new MyClassLoad("f:/v/v/test.jar");
-
Class<?> clazz2 = loader2.loadClass("com.vae.Test");
-
Object a2 = clazz2.newInstance();
-
Method method2 = clazz2.getMethod("main", String[].class);
-
method2.invoke(null, (Object) new String[]{"2019"});
-
for (URL url : loader2.getURLs()){
-
System.out.println(url.toString());
-
}
-
a2 = null;
-
clazz2 = null;
-
loader2 = null;
-
-
printSupJvm();
-
}
-
-
/**
-
* 打印输出父级SpringBoot中的className
-
*/
-
private static void printSupJvm(){
-
URLClassLoader load = (URLClassLoader)Application.class.getClassLoader();
-
System.out.println(load.getClass().getName() + "===================================");
-
for (URL url : load.getURLs()){
-
System.out.println(url.toString());
-
}
-
System.out.println("================================================================");
-
}
-
}
运行输出打印结果如下
打印输出结构:
"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
-
"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
-
sun.misc.Launcher$AppClassLoader===================================
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/deploy.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/access-bridge-64.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/cldrdata.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/dnsns.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jaccess.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jfxrt.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/localedata.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/nashorn.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunec.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunjce_provider.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunmscapi.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunpkcs11.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/zipfs.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/javaws.jar
-
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
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfxswt.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/management-agent.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/plugin.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar
-
file:/D:/workRepository/example/case-classLoader/target/classes/
-
file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-starter/2.0.1.RELEASE/spring-boot-starter-2.0.1.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/boot/spring-boot/2.0.1.RELEASE/spring-boot-2.0.1.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-context/5.0.5.RELEASE/spring-context-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-aop/5.0.5.RELEASE/spring-aop-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-beans/5.0.5.RELEASE/spring-beans-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-expression/5.0.5.RELEASE/spring-expression-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-autoconfigure/2.0.1.RELEASE/spring-boot-autoconfigure-2.0.1.RELEASE.jar
-
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
-
file:/D:/maven/repository/idea/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar
-
file:/D:/maven/repository/idea/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar
-
file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-to-slf4j/2.10.0/log4j-to-slf4j-2.10.0.jar
-
file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-api/2.10.0/log4j-api-2.10.0.jar
-
file:/D:/maven/repository/idea/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar
-
file:/D:/maven/repository/idea/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-core/5.0.5.RELEASE/spring-core-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-jcl/5.0.5.RELEASE/spring-jcl-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/yaml/snakeyaml/1.19/snakeyaml-1.19.jar
-
file:/D:/maven/repository/idea/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar
-
file:/D:/maven/repository/idea/org/apache/tomcat/embed/tomcat-embed-core/8.5.29/tomcat-embed-core-8.5.29.jar
-
file:/D:/maven/repository/idea/org/apache/tomcat/tomcat-annotations-api/8.5.29/tomcat-annotations-api-8.5.29.jar
-
file:/D:/maven/repository/idea/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar
-
file:/D:/maven/repository/idea/log4j/log4j/1.2.17/log4j-1.2.17.jar
-
file:/D:/Program%20Files/JetBrains/IntelliJ%20IDEA%20Community%20Edition%202018.3.6/lib/idea_rt.jar
-
================================================================
-
Hello World!__ 1 __
-
file:/f:/v/test.jar
-
sun.misc.Launcher$AppClassLoader===================================
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/deploy.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/access-bridge-64.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/cldrdata.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/dnsns.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jaccess.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jfxrt.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/localedata.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/nashorn.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunec.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunjce_provider.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunmscapi.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunpkcs11.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/zipfs.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/javaws.jar
-
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
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfxswt.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/management-agent.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/plugin.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar
-
file:/D:/workRepository/example/case-classLoader/target/classes/
-
file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-starter/2.0.1.RELEASE/spring-boot-starter-2.0.1.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/boot/spring-boot/2.0.1.RELEASE/spring-boot-2.0.1.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-context/5.0.5.RELEASE/spring-context-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-aop/5.0.5.RELEASE/spring-aop-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-beans/5.0.5.RELEASE/spring-beans-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-expression/5.0.5.RELEASE/spring-expression-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-autoconfigure/2.0.1.RELEASE/spring-boot-autoconfigure-2.0.1.RELEASE.jar
-
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
-
file:/D:/maven/repository/idea/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar
-
file:/D:/maven/repository/idea/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar
-
file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-to-slf4j/2.10.0/log4j-to-slf4j-2.10.0.jar
-
file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-api/2.10.0/log4j-api-2.10.0.jar
-
file:/D:/maven/repository/idea/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar
-
file:/D:/maven/repository/idea/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-core/5.0.5.RELEASE/spring-core-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-jcl/5.0.5.RELEASE/spring-jcl-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/yaml/snakeyaml/1.19/snakeyaml-1.19.jar
-
file:/D:/maven/repository/idea/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar
-
file:/D:/maven/repository/idea/org/apache/tomcat/embed/tomcat-embed-core/8.5.29/tomcat-embed-core-8.5.29.jar
-
file:/D:/maven/repository/idea/org/apache/tomcat/tomcat-annotations-api/8.5.29/tomcat-annotations-api-8.5.29.jar
-
file:/D:/maven/repository/idea/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar
-
file:/D:/maven/repository/idea/log4j/log4j/1.2.17/log4j-1.2.17.jar
-
file:/D:/Program%20Files/JetBrains/IntelliJ%20IDEA%20Community%20Edition%202018.3.6/lib/idea_rt.jar
-
================================================================
-
Hello World!__ 2 __
-
file:/f:/v/v/test.jar
-
sun.misc.Launcher$AppClassLoader===================================
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/deploy.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/access-bridge-64.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/cldrdata.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/dnsns.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jaccess.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/jfxrt.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/localedata.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/nashorn.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunec.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunjce_provider.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunmscapi.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/sunpkcs11.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/ext/zipfs.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/javaws.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jce.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfxswt.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/management-agent.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/plugin.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar
-
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar
-
file:/D:/workRepository/example/case-classLoader/target/classes/
-
file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-starter/2.0.1.RELEASE/spring-boot-starter-2.0.1.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/boot/spring-boot/2.0.1.RELEASE/spring-boot-2.0.1.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-context/5.0.5.RELEASE/spring-context-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-aop/5.0.5.RELEASE/spring-aop-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-beans/5.0.5.RELEASE/spring-beans-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-expression/5.0.5.RELEASE/spring-expression-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/boot/spring-boot-autoconfigure/2.0.1.RELEASE/spring-boot-autoconfigure-2.0.1.RELEASE.jar
-
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
-
file:/D:/maven/repository/idea/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar
-
file:/D:/maven/repository/idea/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar
-
file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-to-slf4j/2.10.0/log4j-to-slf4j-2.10.0.jar
-
file:/D:/maven/repository/idea/org/apache/logging/log4j/log4j-api/2.10.0/log4j-api-2.10.0.jar
-
file:/D:/maven/repository/idea/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar
-
file:/D:/maven/repository/idea/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-core/5.0.5.RELEASE/spring-core-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/springframework/spring-jcl/5.0.5.RELEASE/spring-jcl-5.0.5.RELEASE.jar
-
file:/D:/maven/repository/idea/org/yaml/snakeyaml/1.19/snakeyaml-1.19.jar
-
file:/D:/maven/repository/idea/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar
-
file:/D:/maven/repository/idea/org/apache/tomcat/embed/tomcat-embed-core/8.5.29/tomcat-embed-core-8.5.29.jar
-
file:/D:/maven/repository/idea/org/apache/tomcat/tomcat-annotations-api/8.5.29/tomcat-annotations-api-8.5.29.jar
-
file:/D:/maven/repository/idea/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar
-
file:/D:/maven/repository/idea/log4j/log4j/1.2.17/log4j-1.2.17.jar
-
file:/D:/Program%20Files/JetBrains/IntelliJ%20IDEA%20Community%20Edition%202018.3.6/lib/idea_rt.jar
-
================================================================
-
-
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)
一、背景
动态插件化编程是一件很酷的事情,能实现业务功能的 「解耦」 便于维护,另外也可以提升 「可扩展性」 随时可以在不停服务器的情况下扩展功能,也具有非常好的 「开放性」 除了自己的研发人员可以开发功能之外,也能接纳第三方开发商按照规范开发的插件。
常见的动态插件的实现方式有 SPI
、OSGI
等方案,由于脱离了 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