springboot项目通过jar包启动和开发工具启动所使用的类加载器不同

项目场景:

项目做成了插件化的模式,主项目提供定义接口,插件完成具体实现,在主项目启动时对插件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.再次打包调用,由于使用类加载器相同,插件可以被顺利使用

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值