本地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 = new ChannelManager(config, nettyTimer);
requestSender = new NettyRequestSender(config, channelManager, nettyTimer, new AsyncHttpClientState(closed));
channelManager.configureBootstraps(requestSender);
}
ChannelManager:
public ChannelManager(final AsyncHttpClientConfig config, Timer nettyTimer) {
this.config = config;
this.sslEngineFactory = config.getSslEngineFactory() != null ? config.getSslEngineFactory() : new DefaultSslEngineFactory();
try {
this.sslEngineFactory.init(config);
} 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(); }
...
}
重启后测试通过,问题解决。