本地tomcat調用遠程接口報錯:java.lang.reflect.InvocationTargetException

 

  今天碰到一個奇怪的問題,本地Eclipse起了一個tomcat經過http去調一個外部接口,結果居然報了一個反射的異常,先看下完整日誌:java

四月 12, 2018 4:00:14 下午 org.apache.catalina.startup.Catalina start
信息: Server startup in 31334 ms
[2018-04-12 16:01:00] DEBUG EnvironmentInterceptor:33 - EnvironmentInterceptor.preHandle requesturl:http://127.0.0.1:8080/ms-search-war/ms.search.searchService/getSearchRank
[2018-04-12 16:01:00] DEBUG EnvironmentInterceptor:34 - RequestHeads,User-Agent=Apache-HttpClient/4.1.1 (java 1.5),X-Identity-ID=15077870000,X-Auth-Token=123,X-Login-Type=2
[2018-04-12 16:01:00] DEBUG MsCommonAspect:128 - Enter into UrlAdviser.setUrlParam request param = {"paramMap": [{"key": "pluginCode","value": "search_rank"},{"key": "title","value": ""},{"key": "rankType","value": "5"},{"key": "rankDateType","value": "2"},{"key": "pageNo","value": "1"},{"key": "pageSize","value": "10"},{"key": "isMarginTop","value": ""},{"key": "isMarginBottom","value": ""},{"key": "isPaddingTop","value": ""},{"key": "isShowLine","value": ""},{"key": "isAjax","value": ""},{"key": "nodeId","value": ""},{"key": "book_id_list","value": ""},{"key": "listen_book_id_list","value": ""},{"key": "magzine_id_list","value": ""},{"key": "kw","value": "+++++"},{"key": "itemType","value": "0"},{"key": "source","value": "1"}]},identityId = 15077870000
[2018-04-12 16:01:00] INFO  PerformanceMonitorComponent:66 - [15ms] - interface:[ IGaiaClient ],method:[ get ]USER_SESSION_KEY:15077870000:cm

[2018-04-12 16:01:00] DEBUG MsCommonAspect:169 - Exit UrlAdviser.setUrlParam success , nln= null ,cm = M801005I ,identityId = 15077870000 
[2018-04-12 16:01:00] DEBUG GetSearchRankMethodImpl:83 - Enter GetSearchRankMethodImpl.getSearchRank ,identityId:15077870000 ComponentRequest :{"paramMap": [{"key": "pluginCode","value": "search_rank"},{"key": "title","value": ""},{"key": "rankType","value": "5"},{"key": "rankDateType","value": "2"},{"key": "pageNo","value": "1"},{"key": "pageSize","value": "10"},{"key": "isMarginTop","value": ""},{"key": "isMarginBottom","value": ""},{"key": "isPaddingTop","value": ""},{"key": "isShowLine","value": ""},{"key": "isAjax","value": ""},{"key": "nodeId","value": ""},{"key": "book_id_list","value": ""},{"key": "listen_book_id_list","value": ""},{"key": "magzine_id_list","value": ""},{"key": "kw","value": "+++++"},{"key": "itemType","value": "0"},{"key": "source","value": "1"}]}
[2018-04-12 16:01:01] DEBUG CampaignEngine:218 - enter CampaignEngine.getAppMiguSearch request:{"bookType":"0","categoryType":"0","chargeMode":"0","ct":"4","isContinue":"0","isNeedCorrect":"0","pageIndex":1,"pageNum":1,"phoneNumber":"15077870000","queryWord":"+++++","searchType":"0","sorter":"0","wordCount":"0"}
[2018-04-12 16:01:08] DEBUG ResourceLeakDetectorFactory:76 - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@62c3bb38
[2018-04-12 16:01:26] ERROR ServiceMethodImpl:101 - invoke failed, request_id = 601424C46E1591BD5BC9D9590C8EB793, sc={ rpc: { cname: ms.search.searchService:1.0.0.dev, method: getSearchRank, operation: ms.search.searchService:1.0.0.dev/getSearchRank}, implVersion: 0, complete: false, canceled: false}, ex=java.lang.reflect.InvocationTargetException
[2018-04-12 16:01:34] DEBUG MsCommonAspect:84 - Enter setActionCdrParamAspect,identityId:15077870000
[2018-04-12 16:01:34] DEBUG MsCommonAspect:108 - Exit setActionCdrParamAspect,identityId:15077870000,ActionCdrParams:
[2018-04-12 16:01:34] INFO  MethodInvoker:151 - call: ms.search.searchService/getSearchRank, request_id=601424C46E1591BD5BC9D9590C8EB793, expected= 0, version=0, code=1, rt=33763132
[2018-04-12 16:01:34] DEBUG EnvironmentInterceptor:55 - EnvironmentInterceptor completed, requesturl= http://127.0.0.1:8080/ms-search-war/ms.search.searchService/getSearchRank and server delayTime = 34008
[2018-04-12 16:03:01] DEBUG AbstractPool:114 - server removed, node=ZJHZ-CMREAD-TEST213:8080, server={ node: ZJHZ-CMREAD-TEST213:8080, hostname: ZJHZ-CMREAD-TEST213, port: 8080, status: 8, weight: 8, capacity: 128, breaker: { state :CLOSED, working: 0, delay: 15000000000, failureThreshold: [80/100, 0.8], successThreshold: [12/16, 0.75]}, version: 2447894418 }
[2018-04-12 16:03:45] DEBUG AbstractPool:107 - server updated, node=ZJHZ-CMREAD-TEST213:8080, server={ node: ZJHZ-CMREAD-TEST213:8080, hostname: ZJHZ-CMREAD-TEST213, port: 8080, status: 1, weight: 8, capacity: 128, breaker: { state :CLOSED, working: 0, delay: 15000000000, failureThreshold: [80/100, 0.8], successThreshold: [12/16, 0.75]}, version: 2447934730 }

  從日誌裏其實看不出啥來,經過eclipse調試才發現,進入這一行代碼後發生了詭異的事情:node

String response = HttpTools.getDataFromFirstSerach(uri.toString(), jsonData);

  詭異的事情就是直接跳入異常了react

    /**
     * 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裏面是能夠找到HttpTools類和getDataFromFirstSerach方法的。想了一下,應該是編譯出問題了,class文件裏沒有對應的代碼。直接到eclipse部署的本地tomcat路徑下找class:打開Servers窗口 -> 找到Server Locations,看Server path和Deploy path,這裏就是本地eclipse部署路徑:web

  進入本地部署路徑:spring

  進入wtpwebapps -> 進入war包 -> 找到引用的jar -> 打開jar包,根據包路徑找類,果真沒有HttpTools這個類。估計是以前引用的jar包編譯出了問題,沒編譯完。apache

  解決辦法:從新編譯問題jar包,刷新引用jar包的工程,最後clean一下tomcat,再次進入到tomcat本地部署路徑E:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps,發現HttpTools類出現了,從新啓動tomcat,問題依然存在。這就奇怪了,從新調試,發現另外一個問題:json

java.lang.NoSuchMethodError: io.netty.handler.ssl.SslContextBuilder.protocols([Ljava/lang/String;)Lio/netty/handler/ssl/SslContextBuilder;

  有點摸不着頭腦,代碼調試時流程沒變,直接跳入異常,但異常顯示的是上面的問題,找不到SslContextBuilder.protocols這個方法。tomcat

  問題定位:仔細看了下HttpTools這個類:session

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.AsyncHttpClientConfig;
import org.asynchttpclient.BoundRequestBuilder;
import org.asynchttpclient.DefaultAsyncHttpClient;
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
import org.asynchttpclient.Response;
import org.springframework.http.HttpStatus;/**
 * 基礎的http方法 此類包含了經過HTTP協議的請求響應等方法。 此類屬於公共方法,負責提供接口給業務調用。 目前請求支持4種請求方式: 1 xml請求報文經過POST方法請求URL,返回響應報文 2
 * xml請求報文經過PUT方法請求URL,返回響應報文 3 無參數的請求經過GET方法請求URL,返回響應報文 4 無參數的請求經過DELETE方法請求URL,返回操做結果 使用方法: 先獲得HttpUtil的實例(
 * 使用鏈接池方式),經過HttpUtil.getInstance().getHttpClient()返回HttpClient對象 而後調用指定的方法便可
 * 
 */
public class HttpTools
{/**
     * HttpUtil類構造函數
     */
    public HttpTools()
    {
        
    }
    
    /**
     * 單例模式返回惟一的HttpUtil的實例 在建立HttpUtil實例的時候建立HttpClient對象,而且設置HttpClient超時的屬性。
     * 建立HttpClient實例,默認是SimpleHttpConnectionManager建立的,不支持多線程。 使用多線程技術就是說,client能夠在多個線程中被用來執行多個方法。
     * 每次調用HttpClient.executeMethod() 方法,都會去連接管理器申請一個鏈接實例, 申請成功這個連接實例被簽出(checkout),隨之在連接使用完後必須歸還管理器。 管理器支持兩個設置:
     * maxConnectionsPerHost 每一個主機的最大並行連接數,默認爲2 maxTotalConnections 客戶端總並行連接最大數,默認爲20
     * 
     * @return HttpUtil
     */
    public static HttpTools getInstance()
    {
        return instance;
    }
    
    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;
    }
     public static String getDataFromFirstSerach(String uri,String  jsonParam)
    {
        long startTime = System.currentTimeMillis();
        String responseContent = null;
        CloseableHttpResponse response = null;
        
        // 設置超時
        RequestConfig requestConfig = RequestConfig.custom()
            .setSocketTimeout(PropertiesConfig.getInt("timeOut", 15000))
            .setConnectTimeout(PropertiesConfig.getInt("timeOut", 15000))
            .setConnectionRequestTimeout(PropertiesConfig.getInt("timeOut", 15000))
            .build();
        
        // 調用一級搜索接口
        CloseableHttpClient httpClient = HttpClients.createDefault();
        try
        {
            
            StringEntity entity = new StringEntity(jsonParam, "utf-8");// 解決中文亂碼問題
            entity.setContentEncoding("UTF-8");
            entity.setContentType("application/json");
            String timeTapms = new Date().getTime() + "";
            String mgauth = PropertiesConfig.getProperty(APPID) + PropertiesConfig.getProperty(APPKEY) + timeTapms;
            HttpPost httpPost = new HttpPost(uri);
            httpPost.setConfig(requestConfig);
            httpPost.setEntity(entity);
            httpPost.addHeader(ParamConstants.MGAUTH, CipherOperateUtil.encryptByMD5(mgauth));
            httpPost.addHeader(ParamConstants.REQTIME, timeTapms);
            response = httpClient.execute(httpPost);
            
            // 請求響應成功則獲取響應
            if (null != response && response.getStatusLine().getStatusCode() == 200)
            {
                HttpEntity httpentity = response.getEntity();
                responseContent = EntityUtils.toString(httpentity, "UTF-8");
            }
        }
        catch (Exception e)
        {
            logger.error(" httpTools.getDataFromFirstSerach call classifySearch error message:{},request:{} ",e.getMessage(),jsonParam);
        }
        finally
        {
            try
            {
                // 關閉鏈接,釋放資源
                if (response != null)
                {
                    response.close();
                }
                if (httpClient != null)
                {
                    httpClient.close();
                }
            }
            catch (IOException e)
            {
                logger.error(" httpTools.getDataFromFirstSerach call classifySearch error message:{},request:{} ",e.getMessage(),jsonParam);
            }
        }
        long endTime = System.currentTimeMillis();

        if (logger.isDebugEnabled())
        {
            logger.debug("=======================call getDataFromFirstSerach interface = {},spend time = {} ms",
                uri,
                endTime - startTime);
        }

        return responseContent;
    }
    
}

  從上面看出,類一開始就經過靜態方法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)
    at cn.migu.newportal.cache.util.HttpTools.getAsyncHttpClient(HttpTools.java:109)
    at cn.migu.newportal.cache.util.HttpTools.<clinit>(HttpTools.java:95)

  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);
        }

        ChannelPool channelPool = config.getChannelPool();
        if (channelPool == null) {
            if (config.isKeepAlive()) {
                channelPool = new DefaultChannelPool(config, nettyTimer);
            } else {
                channelPool = NoopChannelPool.INSTANCE;
            }
        }
        this.channelPool = channelPool;

        
        tooManyConnections = unknownStackTrace(new TooManyConnectionsException(config.getMaxConnections()), ChannelManager.class, "acquireChannelLock");
        tooManyConnectionsPerHost = unknownStackTrace(new TooManyConnectionsPerHostException(config.getMaxConnectionsPerHost()), ChannelManager.class, "acquireChannelLock");
        maxTotalConnectionsEnabled = config.getMaxConnections() > 0;
        maxConnectionsPerHostEnabled = config.getMaxConnectionsPerHost() > 0;

        if (maxTotalConnectionsEnabled || maxConnectionsPerHostEnabled) {
            openChannels = new DefaultChannelGroup("asyncHttpClient", GlobalEventExecutor.INSTANCE) {
                @Override
                public boolean remove(Object o) {
                    boolean removed = super.remove(o);
                    if (removed) {
                        if (maxTotalConnectionsEnabled)
                            freeChannels.release();
                        if (maxConnectionsPerHostEnabled) {
                            Object partitionKey = channelId2PartitionKey.remove(Channel.class.cast(o));
                            if (partitionKey != null) {
                                Semaphore hostFreeChannels = freeChannelsPerHost.get(partitionKey);
                                if (hostFreeChannels != null)
                                    hostFreeChannels.release();
                            }
                        }
                    }
                    return removed;
                }
            };
            freeChannels = new Semaphore(config.getMaxConnections());
        } else {
            openChannels = new DefaultChannelGroup("asyncHttpClient", GlobalEventExecutor.INSTANCE);
            freeChannels = null;
        }

        handshakeTimeout = config.getHandshakeTimeout();

        // check if external EventLoopGroup is defined
        ThreadFactory threadFactory = config.getThreadFactory() != null ? config.getThreadFactory() : new DefaultThreadFactory(config.getThreadPoolName());
        allowReleaseEventLoopGroup = config.getEventLoopGroup() == null;
        ChannelFactory<? extends Channel> channelFactory;
        if (allowReleaseEventLoopGroup) {
            if (config.isUseNativeTransport()) {
                eventLoopGroup = newEpollEventLoopGroup(config.getIoThreadsCount(), threadFactory);
                channelFactory = getEpollSocketChannelFactory();

            } else {
                eventLoopGroup = new NioEventLoopGroup(config.getIoThreadsCount(), threadFactory);
                channelFactory = NioSocketChannelFactory.INSTANCE;
            }

        } else {
            eventLoopGroup = config.getEventLoopGroup();
            if (eventLoopGroup instanceof OioEventLoopGroup)
                throw new IllegalArgumentException("Oio is not supported");

            if (eventLoopGroup instanceof NioEventLoopGroup) {
                channelFactory = NioSocketChannelFactory.INSTANCE;
            } else {
                channelFactory = getEpollSocketChannelFactory();
            }
        }

        httpBootstrap = newBootstrap(channelFactory, eventLoopGroup, config);
        wsBootstrap = newBootstrap(channelFactory, eventLoopGroup, config);

        // for reactive streams
        httpBootstrap.option(ChannelOption.AUTO_READ, false);
    }

  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();
  }
  
  public SSLEngine newSslEngine(AsyncHttpClientConfig config, String peerHost, int peerPort)
  {
    SSLEngine sslEngine = this.sslContext.newEngine(ByteBufAllocator.DEFAULT, peerHost, peerPort);
    configureSslEngine(sslEngine, config);
    return sslEngine;
  }
  
  public void init(AsyncHttpClientConfig config)
    throws SSLException
  {
    this.sslContext = buildSslContext(config);
  }
  
  protected SslContextBuilder configureSslContextBuilder(SslContextBuilder builder)
  {
    return builder;
  }
}

  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方法:

 * Copyright (c) 2015 AsyncHttpClient Project. All rights reserved.
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();
    }

    @Override
    public SSLEngine newSslEngine(AsyncHttpClientConfig config, String peerHost, int peerPort) {
        // FIXME should be using ctx allocator
        SSLEngine sslEngine = sslContext.newEngine(ByteBufAllocator.DEFAULT, peerHost, peerPort);
        configureSslEngine(sslEngine, config);
        return sslEngine;
    }

    @Override
    public void init(AsyncHttpClientConfig config) throws SSLException {
        sslContext = buildSslContext(config);
    }

    /**
     * The last step of configuring the SslContextBuilder used to create an SslContext when no context is provided in
     * the {@link AsyncHttpClientConfig}. This defaults to no-op and is intended to be overridden as needed.
     *
     * @param builder builder with normal configuration applied
     * @return builder to be used to build context (can be the same object as the input)
     */
    protected SslContextBuilder configureSslContextBuilder(SslContextBuilder builder) {
        // default to no op
        return builder;
    }

}

  重啓後測試經過,問題解決。

相關文章
相關標籤/搜索