前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot之class is not visible from class loader

SpringBoot之class is not visible from class loader

作者头像
加多
发布2018-09-06 15:12:03
2.2K0
发布2018-09-06 15:12:03
举报
文章被收录于专栏:Java编程技术Java编程技术

一、前言

最近在搭建SpringBoot的新应用,遇到个有意思的问题,如题就是在加载某一个类时候抛出了class is not visible from class loader, 下面就带大家看看是如何产生的。

二、问题产生

  • 首先有如下bean的定义:
代码语言:javascript
复制
public class TestProxy implements TestService {

   private TestService testService;


   public void init() throws Exception {
       RemoteConsumerProxy<TestService> proxy =
               RemoteConsumerProxy()
                       .setInterfaceClass(TestService.class)
                       .build();
       testService = proxy.getService();
   }
   。。。
}

如上代码代理类TestProxy继承了TestService类,并且在init方法里面消费了接口TestService的提供的远程服务。RemoteConsumerProxy类做了两件事,首先是生成接口TestService的远程服务bean这里假设为beanRemote,然后对beanRemote进行JDK代理生成代理类beanRemoteProxy,代理的作用是执行具体远程服务方法前进行统一限流处理或者指定调用的ip等。并且RemoteConsumerProxy是通过二方库方式引入。

  • 然后引入了SpringBoot的开发工具模块spring-boot-devtools。
  • 满足上面两个条件后注入TestProxy到IOC容器,运行Spring-boot工程的main函数(注意打成jar,然后运行jar则不会有这个问题),就会抛出: TestService is not visible from class loader

从调用堆栈看是java.lang.reflect.Proxy的apply方法抛出的异常。

image.png

三、问题分析

既然是Proxy的apply方法抛出了异常,那么就看什么情况下会抛出异常,从Proxy的代码看是 interfaceClass != intf时候抛出异常。

这里intf是通过 RemoteConsumerProxy<TestService> proxy = RemoteConsumerProxy() .setInterfaceClass(TestService.class)传递的,

而interfaceClass则是使用

代码语言:javascript
复制
try {
                   interfaceClass = Class.forName(intf.getName(), false, loader);
               } catch (ClassNotFoundException e) {
             }

创建的。

到这里对类加载器比较熟悉的童鞋应该会有所思了,同一个类两次加载后的Class对象不一样,那只有一种情况,那就是使用了两个类加载器加载了同一个类。

为了证明这个,可以在init方法里面添加如下代码:

代码语言:javascript
复制
   System.out.println("TestProxy classloader:" + MassTopicQueryProxy.class.getClassLoader());
   System.out.println("TestService classloader:" + MassTopicQueryService.class.getClassLoader());
   System.out.println("RemoteConsumerProxy classloader:" + ACCSHSFConsumerProxy.class.getClassLoader());

运行后输出为:

代码语言:javascript
复制
TestProxy classloader:org.springframework.boot.devtools.restart.classloader.RestartClassLoader@63e66532
TestService classloader:org.springframework.boot.devtools.restart.classloader.RestartClassLoader@63e66532
RemoteConsumerProxy classloader:sun.misc.Launcher$AppClassLoader@4554617c
  • 从结果可知TestProxy和TestService是使用RestartClassLoader类加载器加载的,所以调用代码 RemoteConsumerProxy.setInterfaceClass(TestService)传递的时候传递的是RestartClassLoader加载的Class对象。
  • 从结果可知RemoteConsumerProxy.setInterfaceClass是AppClassLoader加载的,所以ACCSHSFConsumerProxy内部执行代码
代码语言:javascript
复制
               try {
                   interfaceClass = Class.forName(intf.getName(), false, loader);
               } catch (ClassNotFoundException e) {
               }

时候也是使用AppClassLoader加载的,也就是这里的TestService是AppClassLoader加载的,所以同一个接口由两个类加载器加载,所以两个Class对象不相等。

另外通过debug可以发现RestartClassLoader的父加载器就是AppClassLoader。

那么RestartClassLoader又是什么那?从何而来? 经查阅资料的20.2 Automatic Restart章节可知,SpringBoot使用spring-boot-devtools模块实现当classpath下的文件被修改后自动重启的功能。这是通过使用两个类加载器来实现的,一些不需要的改变的类比如三方jar是使用base类加载器加载的(这里值AppClassloader),开发中一些需要修改的类则使用restart classloader进行重新加载。

总结:在IDE里面main函数方式运行时候由于会编译类,classpath下的内容会发生变化,所以会触发restart,从而导致抛出异常。而首先通过mvn clean package 打包,然后在java -jar jar方式由于jar内部不会变了所以不会触发restart,所以运行正常。

四、如何解决

  • 方案一,排查掉spring-boot-devtools模块模块的maven引入可以解决,这时候所有类都是使用APPClassloader加载。
  • 方案二,可以引入spring-boot-devtools模块,但是禁用禁用reStart功能
代码语言:javascript
复制
public static void main( String[] args )
   {
       System.setProperty("spring.devtools.restart.enabled", "false");

       SpringApplication.run(Application.class, args);
   }

五、总结

虽然是同一个类,但是使用不同的类加载器加载后得到的Class对象是不一样的,区分一个Class对象是否相等要看包名+类名,也要看是否是同一个类加载器。另外SpringBoot的spring-boot-devtools模块的restart功能在IDE里面运行main函数时候应该有bug。欢迎大家批评指正。

六、 参考

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.12.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、问题产生
  • 三、问题分析
  • 四、如何解决
  • 五、总结
  • 六、 参考
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档