成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

為什么要理解類加載?遇到這種問題就知道書到用時方恨少了

數據庫 MySQL
究竟是哪一行呢?本地可以調試的話很簡單,Debug跟蹤一下,但是預發布環境不能Debug呀!當然其實有些公司網是通的,可以做遠程Debug,更多的是一個規范的問題。

1、問題背景

我們項目中引入了sharding-jdbc,本機運行、開發環境運行、測試環境運行都沒有問題,結果到了預發布環境發生了一個異常:

Cannot support database type 'MySQL' at org.apache.shardingsphere.sql.parser.core.parser.SQLParserFactory.newInstance(SQLParserFactory.java:55) 
at org.apache.shardingsphere.sql.parser.core.parser.SQLParserExecutor.towPhaseParse(SQLParserExecutor.java:55) 
at org.apache.shardingsphere.sql.parser.core.parser.SQLParserExecutor.execute(SQLParserExecutor.java:47) 
at org.apache.shardingsphere.sql.parser.SQLParserEngine.parse0(SQLParserEngine.java:79) 
at org.apache.shardingsphere.sql.parser.SQLParserEngine.parse(SQLParserEngine.java:61) 
at org.apache.shardingsphere.underlying.route.DataNodeRouter.createRouteContext(DataNodeRouter.java:97) 
at org.apache.shardingsphere.underlying.route.DataNodeRouter.executeRoute(DataNodeRouter.java:89) 
at org.apache.shardingsphere.underlying.route.DataNodeRouter.route(DataNodeRouter.java:76) 
at org.apache.shardingsphere.underlying.pluggble.prepare.PreparedQueryPrepareEngine.route(PreparedQueryPrepareEngine.java:54)

而我們除了本機環境各人使用上有些差異外,開發環境運行、測試環境運行和預發布環境上只有MySQL服務端版本是不同的,雖然是報錯上看和MySQL服務端并沒有直接關系,但我們還是在開發環境還原了預發布環境的MySQL服務端版本,還原之后開發環境并沒有復現問題。

這就非常詭異了。也給我們解決帶來了一定的技術挑戰:不能通過本地調試或者加JVM參數來做進一步驗證。

以下就是我們的排查過程。

2、源碼分析

既然有明確的報錯日志,首先要進行代碼分析:

SQLParserFactory.newInstance(SQLParserFactory.java:55)

跟進這一行報錯的源碼:

public static SQLParser newInstance(final String databaseTypeName, final String sql) {
      for (SQLParserConfiguration each : NewInstanceServiceLoader.newServiceInstances(SQLParserConfiguration.class)) {
          if (each.getDatabaseTypeName().equals(databaseTypeName)) {
              return createSQLParser(sql, each);
          }
      }
      throw new UnsupportedOperationException(String.format("Cannot support database type '%s'", databaseTypeName));
  }

第7行拋出了日志中的異常。這說明問題就發生在2、3、4這三行中的一行。

究竟是哪一行呢?本地可以調試的話很簡單,Debug跟蹤一下,但是預發布環境不能Debug呀!當然其實有些公司網是通的,可以做遠程Debug,更多的是一個規范的問題。

在不能Debug的前提下,我把這三行代碼拷貝出來,分步打日志,再放到預發布環境運行:

try{
    log.warn("ShardingDebug test=1==============================begin");
    for (SQLParserConfiguration each : NewInstanceServiceLoader.newServiceInstances(SQLParserConfiguration.class)) {
        log.warn("ShardingDebug test=2==============================each:{}", each);
        if (each.getDatabaseTypeName().equals("MySQL")) {
            log.warn("ShardingDebug test=3==============================equals:{}", each);
            CodePointCharStream codePointCharStream = CharStreams.fromString("select version()");
            log.warn("ShardingDebug test=4==============================codePointCharStream:{}", codePointCharStream);
            // 這次存在
            Lexer lexer = null;
            try {
                log.warn("ShardingDebug test=5.0==============================MySQLLexer:{}", each.getLexerClass().getName());
                log.warn("ShardingDebug test=5.1==============================MySQLLexer:{}", each.getLexerClass().getConstructor(CharStream.class).getName());
                SQLLexer sqlLexer = each.getLexerClass().getConstructor(CharStream.class).newInstance(codePointCharStream);
                log.warn("ShardingDebug test=5.2==============================sqlLexer:{}, isInstance:{}", sqlLexer, sqlLexer instanceof Lexer);
                lexer = (Lexer) each.getLexerClass().getConstructor(CharStream.class).newInstance(codePointCharStream);
                log.warn("ShardingDebug test=5==============================lexer:{}", lexer);
            } catch (InstantiationException e) {
                log.error("ShardingDebug test=6==============================lexer:{}", lexer, e);
            } catch (IllegalAccessException e) {
                log.error("ShardingDebug test=7==============================lexer:{}", lexer, e);
            } catch (InvocationTargetException e) {
                log.error("ShardingDebug test=8==============================lexer:{}", lexer, e);
            } catch (NoSuchMethodException e) {
                log.error("ShardingDebug test=9==============================lexer:{}", lexer, e);
            }
            CommonTokenStream lexerCommonTokenStream = new CommonTokenStream(lexer);
            log.warn("ShardingDebug test=10==============================lexerCommonTokenStream:{}", lexerCommonTokenStream);
            SQLParser sqlParser = null;
            try {
                log.warn("ShardingDebug test=11.0==============================sqlParser:{}", each.getParserClass());
                log.warn("ShardingDebug test=11.1==============================sqlParser:{}", each.getParserClass().getConstructor(TokenStream.class));
                sqlParser = each.getParserClass().getConstructor(TokenStream.class).newInstance(lexerCommonTokenStream);
                log.warn("ShardingDebug test=11==============================sqlParser:{}", sqlParser);
            } catch (InstantiationException e) {
                log.warn("ShardingDebug test=12==============================sqlParser:{}", sqlParser, e);
            } catch (IllegalAccessException e) {
                log.warn("ShardingDebug test=13==============================sqlParser:{}", sqlParser, e);
            } catch (InvocationTargetException e) {
                log.warn("ShardingDebug test=14==============================sqlParser:{}", sqlParser, e);
            } catch (NoSuchMethodException e) {
                log.warn("ShardingDebug test=15==============================sqlParser:{}", sqlParser, e);
            }
            break;
        }
    }
} catch (Exception ex) {
    log.error("ShardDebugJob failed", ex);
}
}

我把這三行代碼拆解的非常細,希望盡量減少發布,排查出問題的原因。

結果日志只打印了第一行,剩下的都沒打印。說明沒有進入for循環。也就說明了。

NewInstanceServiceLoader.newServiceInstances(SQLParserConfiguration.class)

沒有加載到東西。再看這一行的源碼:

public static <T> Collection<T> newServiceInstances(final Class<T> service) {
      Collection<T> result = new LinkedList<>();
      if (null == SERVICE_MAP.get(service)) {
          return result;
      }
      for (Class<?> each : SERVICE_MAP.get(service)) {
          result.add((T) each.newInstance());
      }
      return result;
  }

這說明SERVICE_MAP里沒有對應的實現類。再看SERVICE_MAP賦值的源碼:

public static <T> void register(final Class<T> service) {
    for (T each : ServiceLoader.load(service)) {
        registerServiceClass(service, each);
    }
}


private static <T> void registerServiceClass(final Class<T> service, final T instance) {
    Collection<Class<?>> serviceClasses = SERVICE_MAP.get(service);
    if (null == serviceClasses) {
        serviceClasses = new LinkedHashSet<>();
    }
    serviceClasses.add(instance.getClass());
    SERVICE_MAP.put(service, serviceClasses);
}

本質上值都是ServiceLoader.load(service)加載來的。這就要考察Java功力了。

這行代碼本質是什么呢?

3、原理分析

本質是使用了Java的SPI功能。

Java SPI(Service Provider Interface)是一種服務發現機制,它允許服務提供者為API定義標準接口,而實現者可以通過配置文件來注冊自己的實現。如果在使用SPI時出現“java SPI沒有加載到實現類”的錯誤,通常意味著以下幾種情況之一:

  • 實現類沒有正確地被打包到jar中,或者沒有被放置在正確的目錄下。
  • 配置文件(通常是META-INF/services/接口全限定名)中沒有列出實現類的全限定名。
  • 類加載器沒有正確加載到實現類的路徑。

解決方法:

  • 確保實現類的jar包已經被正確打包,并且實現類的包結構和接口包結構一致。
  • 檢查META-INF/services目錄下對應接口的文件中是否有實現類的全限定名。
  • 如果是在web容器或者OSGi環境中,確保類加載器的路徑設置正確,實現類應該可見。
  • 如果使用的是第三方庫,確保依賴已經正確引入。
  • 清除可能存在的緩存,比如重新編譯或重啟應用。

這次的問題是屬于哪一種呢?很遺憾,都不是。我為了確認問題,將預發布環境打的運行jar包下載到本地,解壓查看確認,都是沒有問題的。

為了確認可以加載到,我再一次發布預發布環境,這一次手動執行加載看看:

Class<?> mySQLParserConfiguration = Thread.currentThread().getContextClassLoader().loadClass(MySQLParserConfiguration.class.getName());
log.info("ShardingDebug test=0.0==============================loadClass:{}", mySQLParserConfiguration);

結果正常打印了實現類的全限定名。

這里為什么我會想到Thread.currentThread().getContextClassLoader()這個類加載器呢?很簡單。這個類加載器就是ServiceLoader.load源碼里使用的類加載器。

@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}

4、問題解決

到這里,解決方案也呼之欲出:既然是可以加載到的,那應該就是沒有在注冊服務代碼執行前加載。手動讓類加載在注冊服務前運行即可。

Class<?> mySQLParserConfiguration = Thread.currentThread().getContextClassLoader().loadClass(MySQLParserConfiguration.class.getName());
log.info("ShardingDebug test=0.0==============================loadClass:{}", mySQLParserConfiguration);
try {
    NewInstanceServiceLoader.register(SQLParserConfiguration.class);
} catch (Throwable e) {
    log.error("ShardingDebug test=0.011==============================register", e);
}

先執行這個,再執行最初的:

NewInstanceServiceLoader.newServiceInstances(SQLParserConfiguration.class)

就可以加載到對應的實例了。

4、分析總結

這次問題出現在sharding-jdbc的SQL解析階段,可以通過源碼上下文看到問題發生在與MySQL服務端交互之前,可排除受服務端的影響。并且可以確定問題發生在JVM內部。

可通過ServiceLoader.load(service)確定是使用了Java的SPI機制時發生問題。SPI的本質是通過META-INF/services目錄下對應接口的文件找到實現類。

驗證實現類可被JVM正常加載我使用了與源碼相同的類加載器并發布到預發布環境進行驗證。因為不同類加載器有不同的使用條件。比如:

ClassLoader.getSystemClassLoader()
在本機會正常運行,但是服務器上會因為運行的是打好的 jar 包,路徑發生變化,服務器上運行報「找不到類」異常。

整個排查過程也有一些怎樣搜索答案的思考,比如只是根據最初的異常來搜索,發現網上搜的都不是本質問題。后來雖然我用更接近本質的問題:

「找不到spi的實現類怎么解決」也沒找到正確的答案,但是問題是更接近真相的。
責任編輯:武曉燕 來源: 編程一生
相關推薦

2021-04-21 07:37:19

JVM復盤 日志

2024-04-25 08:21:36

Java對象計數法

2017-07-27 11:15:24

云存儲攝像機SD卡

2023-09-28 10:21:44

CSS前端

2018-09-28 09:20:56

輸入法

2024-12-02 09:01:23

Java虛擬機內存

2011-04-25 09:37:03

2015-08-06 10:14:15

造輪子facebook

2022-08-15 08:27:02

基站網絡

2013-03-12 14:30:09

Ubuntu操作系統

2019-05-16 08:10:42

無線路由器WiFi網絡

2021-09-05 07:55:36

DDIA Raft 場景

2020-05-07 10:44:05

MySQL數據庫程序員

2018-06-21 09:30:50

比特幣區塊鏈擴容

2024-04-03 09:23:31

ES索引分析器

2021-08-31 07:57:21

輪詢鎖多線編程Java

2010-07-13 10:56:43

Perl print

2014-08-25 10:00:18

開源

2015-05-12 11:04:42

Java EE學習Java EE

2019-11-27 10:25:15

SaaS云端IT架構
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久小视频 | 久久69精品久久久久久久电影好 | 精品1区2区 | 亚洲欧美一区二区三区在线 | 欧美日本一区 | 91成人精品 | 天天干天天玩天天操 | 国产高清精品一区二区三区 | 激情欧美一区二区三区中文字幕 | 日本人麻豆 | 黄色一级大片在线免费看产 | 国产精品久久久久久久久久久新郎 | 国产毛片在线看 | 欧美电影一区 | 精品一区电影 | 久久aⅴ乱码一区二区三区 亚洲欧美综合精品另类天天更新 | 国产精品久久久久一区二区三区 | 99精品久久久久久中文字幕 | 69热视频在线观看 | 性天堂网 | 国产精品久久久久久久久久免费 | www.天天干.com | 日本在线看 | 午夜天堂精品久久久久 | 欧美成人手机视频 | 国产福利资源在线 | 91成人免费电影 | 日韩av成人 | 日本国产一区二区 | 久久国产精品免费 | 成人国产精品入口免费视频 | 久草.com| 国产999精品久久久影片官网 | 北条麻妃一区二区三区在线观看 | 天堂综合网 | 精品国产乱码一区二区三 | 中文在线一区二区 | 免费一级毛片 | 在线中文字幕日韩 | 亚洲国产精品福利 | 精品国产欧美一区二区三区成人 |