本地tomcat调用远程接口报错:java.lang.reflect.InvocationTargetException
今天碰到一个奇怪的问题,本地 Eclipse 起了一个 tomcat 通过 http 去调一个外部接口,结果竟然报了一个反射的异常——java.lang.reflect.InvocationTargetException,从日志里看不出啥来,通过 eclipse 调试发生了诡异的事情——直接跳入异常了
* Constructs a InvocationTargetException with a target exception. * * @param target the target exception */ public InvocationTargetException(Throwable target) {super((Throwable)null); // Disallow initCause this.target = target; }
从异常看,是程序找不到调用代码了,但是 eclipse 里面是可以找到调用类和方法的。想了一下,应该是编译出问题了,或许 class 文件里没有应的代码。去 eclipse 部署的本地 tomcat 路径下找 class:打开 Servers 窗口 -> 找到 Server Locations,看 Server path 和 Deploy path,进入本地部署路径:
进入 wtpwebapps -> 进入 war 包 -> 找到引用的 jar -> 打开 jar 包,根据包路径找类,果然没有调用类。估计是之前引用的 jar 包编译出了问题。
解决办法:重新编译问题 jar 包,刷新引用 jar 包的工程,最后 clean 一下 tomcat,再次进入到 tomcat 本地部署路径 E:\workspace.metadata.pluginsorg.eclipse.wst.server.core mp0wtpwebapps,发现调用类出现了,重新启动 tomcat,问题依然存在。这就奇怪了,重新调试,发现另一个问题:
java.lang.NoSuchMethodError: io.netty.handler.ssl.SslContextBuilder.protocols([Ljava/lang/String;)Lio/netty/handler/ssl/SslContextBuilder;
有点摸不着头脑,代码调试时流程没变,直接跳入异常,但异常显示的是上面的问题,找不到 SslContextBuilder.protocols 这个方法。
问题定位:仔细看了下调用类,发现有这么一个方法:
/** * 获取请求类的客户端 * * @return */ public static AsyncHttpClient getAsyncHttpClient() {AsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().setFollowRedirect(false) .setConnectTimeout(PropertiesConfig.getInt("asynHttp.connectTimeout", 500)) .setRequestTimeout(PropertiesConfig.getInt("asynHttp.requestTimeout", 10000)) .setReadTimeout(PropertiesConfig.getInt("asynHttp.readTimeout", 10000)) .build(); AsyncHttpClient client = new DefaultAsyncHttpClient(config); return client; }
从上面看出,类一开始就通过静态方法 getAsyncHttpClient 得到异步请求的客户端对象 asynHttpClient,所以应该在 getAsyncHttpClient 方法加断点,否则就直接进入异常了。异常跟踪直接看日志:
Exception in thread "main" java.lang.NoSuchMethodError: io.netty.handler.ssl.SslContextBuilder.protocols([Ljava/lang/String;)Lio/netty/handler/ssl/SslContextBuilder;
at org.asynchttpclient.netty.ssl.DefaultSslEngineFactory.buildSslContext(DefaultSslEngineFactory.java:45)
at org.asynchttpclient.netty.ssl.DefaultSslEngineFactory.init(DefaultSslEngineFactory.java:69)
at org.asynchttpclient.netty.channel.ChannelManager.<init>(ChannelManager.java:110)
at org.asynchttpclient.DefaultAsyncHttpClient.<init>(DefaultAsyncHttpClient.java:85)
一路跟过去:
DefaultAsyncHttpClient:
* Create a new HTTP Asynchronous Client using the specified * {@link DefaultAsyncHttpClientConfig} configuration. This configuration * will be passed to the default {@link AsyncHttpClient} that will be * selected based on the classpath configuration. * * @param config a {@link DefaultAsyncHttpClientConfig} */ public DefaultAsyncHttpClient(AsyncHttpClientConfig config) {this.config = config; allowStopNettyTimer = config.getNettyTimer() == null; nettyTimer = allowStopNettyTimer ? newNettyTimer() : config.getNettyTimer(); channelManager = <span style="background-color: rgba(255, 255, 0, 1)">new ChannelManager(config, nettyTimer);</span> requestSender = new NettyRequestSender(config, channelManager, nettyTimer, new AsyncHttpClientState(closed)); channelManager.configureBootstraps(requestSender); }</pre>
ChannelManager:
public ChannelManager(final AsyncHttpClientConfig config, Timer nettyTimer) {this.config = config; this.sslEngineFactory = config.getSslEngineFactory() != null ? config.getSslEngineFactory() : new DefaultSslEngineFactory(); try { <span style="background-color: rgba(255, 255, 0, 1)">this.sslEngineFactory.init(config);</span> } catch (SSLException e) { throw new RuntimeException("Could not initialize sslEngineFactory", e); }
...
DefaultSslEngineFactory:
package org.asynchttpclient.netty.ssl;import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import java.util.Arrays;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import org.asynchttpclient.AsyncHttpClientConfig;
import org.asynchttpclient.util.MiscUtils;public class DefaultSslEngineFactory
extends SslEngineFactoryBase
{
private volatile SslContext sslContext;private SslContext buildSslContext(AsyncHttpClientConfig config)
throws SSLException
{
if (config.getSslContext() != null) {
return config.getSslContext();
}
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().sslProvider(config.isUseOpenSsl() ? SslProvider.OPENSSL : SslProvider.JDK).sessionCacheSize(config.getSslSessionCacheSize()).sessionTimeout(config.getSslSessionTimeout());
if (MiscUtils.isNonEmpty(config.getEnabledProtocols())) {
sslContextBuilder.protocols(config.getEnabledProtocols());
}
if (MiscUtils.isNonEmpty(config.getEnabledCipherSuites())) {
sslContextBuilder.ciphers(Arrays.asList(config.getEnabledCipherSuites()));
}
if (config.isUseInsecureTrustManager()) {
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
}
return configureSslContextBuilder(sslContextBuilder).build();
}
...
终于找到正在——DefaultSslEngineFactory 调用 SslContextBuilder.protocols 方法时找不到了,进入 SslContextBuilder 类看了一眼,确实没有 protocols 方法。去看了下 pomx.ml 文件,引入的 async-http-client.jar 是2.1.0-alpha26版本,匹配的 netty-all.jar 却是4.1.8.Final版本的,而这个版本的 SslContextBuilder 并不存在 protocols 方法。
问题解决:使用2.1.0-alpha6版本 async-http-client 的 jar 包,这个包里 DefaultSslEngineFactory 没有调用 SslContextBuilder.protocols 方法:
package org.asynchttpclient.netty.ssl;import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;import org.asynchttpclient.AsyncHttpClientConfig;
public class DefaultSslEngineFactory extends SslEngineFactoryBase {
private volatile SslContext sslContext; private SslContext buildSslContext(AsyncHttpClientConfig config) throws SSLException { if (config.getSslContext() != null) return config.getSslContext(); SslContextBuilder sslContextBuilder = SslContextBuilder.forClient()// .sslProvider(config.isUseOpenSsl() ? SslProvider.OPENSSL : SslProvider.JDK)// .sessionCacheSize(config.getSslSessionCacheSize())// .sessionTimeout(config.getSslSessionTimeout()); if (config.isUseInsecureTrustManager()) { sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); } return configureSslContextBuilder(sslContextBuilder).build(); }
...
}
重启后测试通过,问题解决。