问题表现
先看问题的表现(线上错误日志):
2018-09-25 18:48:35,791 ERROR [pool-9-thread-136] (AccountGlobalService.java:139) - verify from asia1 api failed java.lang.ClassCastException
2018-09-25 18:48:35,791 ERROR [pool-9-thread-170] (AccountGlobalService.java:139) - verify from us api failed java.lang.ClassCastException
当看到这个错误日志是表示一脸懵逼,是什么导致类型转化失败?
问题定位
该问题是在测试加解密登陆时候碰到的,故可以先大致判断登陆模块导致的错误,然后把堆栈信息打印出来。堆栈信息如下:
有空补齐
通过定位可以知道toolkit jar中的RestClient出现了问题。该类可实现使用接口(接口中定义相对路径)可以实现远程调用(吐槽一下,该类以前还存在内存泄漏问题)。
RestClient出问题的代码
ResteasyWebTarget target;
String uri = null;
if (0 == url.getPort()) {
uri = url.getProtocol() + "://" + url.getHost() + "/" + getContextPath(url);
} else {
uri = url.getProtocol() + "://" + url.getHost() + ":" + url.getPort() + "/" + getContextPath(url);
}
target = client.target(uri);
而报错的代码如下图所示:
那UriBuilder转化ResteasyUriBuilder? 查看UriBuilders的子类如下:
那运行的类就有可能是其他子类,比如JerseyUriBuilder 或者 UriBuilderImpl。
现在只能看UriBuilder类:
我们使用的方法是fromUri,而该方法使用动态代理加载一个类
public static RuntimeDelegate getInstance() {
RuntimeDelegate result = cachedDelegate;
if (result == null) {
Object var1 = RD_LOCK;
synchronized(RD_LOCK) {
result = cachedDelegate;
if (result == null) {
cachedDelegate = result = findDelegate();
}
}
}
return result;
}
private static RuntimeDelegate findDelegate() {
try {
// 这个东西是啥
Object delegate = FactoryFinder.find("javax.ws.rs.ext.RuntimeDelegate", "org.glassfish.jersey.internal.RuntimeDelegateImpl");
if (!(delegate instanceof RuntimeDelegate)) {
Class pClass = RuntimeDelegate.class;
String classnameAsResource = pClass.getName().replace('.', '/') + ".class";
ClassLoader loader = pClass.getClassLoader();
if (loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
URL targetTypeURL = loader.getResource(classnameAsResource);
throw new LinkageError("ClassCastException: attempting to cast" + delegate.getClass().getClassLoader().getResource(classnameAsResource) + " to " + targetTypeURL);
} else {
return (RuntimeDelegate)delegate;
}
} catch (Exception var5) {
throw new RuntimeException(var5);
}
}
该类是存放在这里
为啥放在META-INF/service呢?经过谷歌一番后,才知道这个是SPI加载方式。这是SPI一篇博客
那预计是查找错了一个文件,导致加载了其他jar的实例,最终导致类型转化失败。
解决方法:
1.去除不使用的jar
2.修改RestClient代码,如下:
ResteasyWebTarget target;
UriBuilder uriBuilder = new ResteasyUriBuilder();
String uri = null;
if (0 == url.getPort()) {
uri = url.getProtocol() + "://" + url.getHost() + "/" + getContextPath(url);
} else {
uri = url.getProtocol() + "://" + url.getHost() + ":" + url.getPort() + "/" + getContextPath(url);
}
uriBuilder.uri(uri);
target = client.target(uriBuilder);