javaSpringBoot程序员

SpringBoot之class is not visible

2017-12-28  本文已影响102人  阿里加多

一、前言

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

二、问题产生

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是通过二方库方式引入。

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

image.png

三、问题分析

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

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

而interfaceClass则是使用

try {
                   interfaceClass = Class.forName(intf.getName(), false, loader);
               } catch (ClassNotFoundException e) {
             }

创建的。

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

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

   System.out.println("TestProxy classloader:" + MassTopicQueryProxy.class.getClassLoader());
   System.out.println("TestService classloader:" + MassTopicQueryService.class.getClassLoader());
   System.out.println("RemoteConsumerProxy classloader:" + ACCSHSFConsumerProxy.class.getClassLoader());

运行后输出为:

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
               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,所以运行正常。

四、如何解决

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。欢迎大家批评指正。

六、 参考

上一篇下一篇

猜你喜欢

热点阅读