本地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 这个方法。

  问题定位:仔细看了下调用类,发现有这么一个方法:

    private static AsyncHttpClient asynHttpClient = getAsyncHttpClient();
    
    /**
     * 获取请求类的客户端
     * 
     * @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 方法加断点,否则就直接进入异常了。异常跟踪直接看日志:

ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. Set system property 'log4j2.debug' to show Log4j2 internal initialization logging.
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();
}

  
  ...
}

  重启后测试通过,问题解决。