项目场景:
项目做成了插件化的模式,主项目提供定义接口,插件完成具体实现,在主项目启动时对插件jar包进行冷加载。
代码展示
1.在主项目中定义一个接口
/**
* 这里是主项目
*/
public interface TestInterface {
void testClassLoad();
}
2. 在插件里实现定义的接口
/**
* 这里是插件
*/
public class TestClassLoadImpl implements TestInterface {
public void testClassLoad() {
System.out.println("plugin run success");
}
}
3.在主项目的Application里冷加载插件
/**
* 这里是主项目
*/
@SpringBootApplication
public class YukinoApplication {
public static void main(String[] args) {
//载入插件
File jarDirectory = new File("./testClassLoad");
if(jarDirectory.isDirectory()) {
File[] files = jarDirectory.listFiles();
for (File file : files) {
try {
//详细代码看第4步
ClassLoadUtils.addUrl(file);
}catch (Exception e){
e.printStackTrace();
}
}
}
ConfigurableApplicationContext run = SpringApplication.run(YukinoApplication.class,args);
}
}
4.插件载入工具的详细代码
/**
* 这里是主项目
*/
public static void addUrl(File jarPath) throws Exception {
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
System.out.println("classLoad:[" + classLoader + "]");
// 反射获取类加载器中的addURL方法,并将需要加载类的jar路径
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
if (!method.isAccessible()) {
method.setAccessible(true);
}
URL url = jarPath.toURI().toURL();
// 把当前jar的路径加入到类加载器需要扫描的路径
method.invoke(classLoader, url);
}
5.写一个接口调用插件
/**
* 这里是主项目
*/
@GetMapping("/testClassLoad")
public String testClassLoad() throws Exception{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
System.out.println("classLoad:[" + classLoader + "]");
TestInterface instance = (TestInterface) Class.forName("com.test.TestClassLoadImpl").newInstance();
instance.testClassLoad();
return "success";
}
问题描述
1.通过idea启动主项目,并调用接口,插件成功运作
2.将主项目打成jar包启动,并调用接口,插件报ClassNotFoundException
原因分析:
1.由于springboot和插件加载分别打印了所使用的类加载器,让我们看一下具体的情况
2.用idea启动,所使用类加载器详情如下
3.使用jar包启动,所使用类加载器详情如下
4.可以看出由于使用jar包启动时,springboot项目所使用的类加载器发生了变化,与冷加载插件所使用的类加载器不同,因此导致ClassNotFoundException异常。
解决方案:
1.更改加载插件所使用的类加载器
将
ClassLoader.getSystemClassLoader()
替换为
Thread.currentThread().getContextClassLoader()
/**
* 这里是主项目
*/
public static void addUrl(File jarPath) throws Exception {
//打印类加载器
//旧加载
URLClassLoader classLoaderOld = (URLClassLoader) ClassLoader.getSystemClassLoader();
//新加载
URLClassLoader classLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
System.out.println("plugin classLoad:[" + classLoader + "]");
// 反射获取类加载器中的addURL方法,并将需要加载类的jar路径
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
if (!method.isAccessible()) {
method.setAccessible(true);
}
URL url = jarPath.toURI().toURL();
// 把当前jar的路径加入到类加载器需要扫描的路径
method.invoke(classLoader, url);
}
2.再次打包调用,由于使用类加载器相同,插件可以被顺利使用