自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

數(shù)據(jù)庫 MySQL
究竟是哪一行呢?本地可以調(diào)試的話很簡單,Debug跟蹤一下,但是預(yù)發(fā)布環(huán)境不能Debug呀!當(dāng)然其實(shí)有些公司網(wǎng)是通的,可以做遠(yuǎn)程Debug,更多的是一個(gè)規(guī)范的問題。

1、問題背景

我們項(xiàng)目中引入了sharding-jdbc,本機(jī)運(yùn)行、開發(fā)環(huán)境運(yùn)行、測試環(huán)境運(yùn)行都沒有問題,結(jié)果到了預(yù)發(fā)布環(huán)境發(fā)生了一個(gè)異常:

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)

而我們除了本機(jī)環(huán)境各人使用上有些差異外,開發(fā)環(huán)境運(yùn)行、測試環(huán)境運(yùn)行和預(yù)發(fā)布環(huán)境上只有MySQL服務(wù)端版本是不同的,雖然是報(bào)錯(cuò)上看和MySQL服務(wù)端并沒有直接關(guān)系,但我們還是在開發(fā)環(huán)境還原了預(yù)發(fā)布環(huán)境的MySQL服務(wù)端版本,還原之后開發(fā)環(huán)境并沒有復(fù)現(xiàn)問題。

這就非常詭異了。也給我們解決帶來了一定的技術(shù)挑戰(zhàn):不能通過本地調(diào)試或者加JVM參數(shù)來做進(jìn)一步驗(yàn)證。

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

2、源碼分析

既然有明確的報(bào)錯(cuò)日志,首先要進(jìn)行代碼分析:

SQLParserFactory.newInstance(SQLParserFactory.java:55)

跟進(jìn)這一行報(bào)錯(cuò)的源碼:

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行拋出了日志中的異常。這說明問題就發(fā)生在2、3、4這三行中的一行。

究竟是哪一行呢?本地可以調(diào)試的話很簡單,Debug跟蹤一下,但是預(yù)發(fā)布環(huán)境不能Debug呀!當(dāng)然其實(shí)有些公司網(wǎng)是通的,可以做遠(yuǎn)程Debug,更多的是一個(gè)規(guī)范的問題。

在不能Debug的前提下,我把這三行代碼拷貝出來,分步打日志,再放到預(yù)發(fā)布環(huán)境運(yùn)行:

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

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

結(jié)果日志只打印了第一行,剩下的都沒打印。說明沒有進(jìn)入for循環(huán)。也就說明了。

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里沒有對應(yīng)的實(shí)現(xiàn)類。再看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);
}

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

這行代碼本質(zhì)是什么呢?

3、原理分析

本質(zhì)是使用了Java的SPI功能。

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

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

解決方法:

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

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

為了確認(rèn)可以加載到,我再一次發(fā)布預(yù)發(fā)布環(huán)境,這一次手動(dòng)執(zhí)行加載看看:

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

結(jié)果正常打印了實(shí)現(xiàn)類的全限定名。

這里為什么我會(huì)想到Thread.currentThread().getContextClassLoader()這個(gè)類加載器呢?很簡單。這個(gè)類加載器就是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、問題解決

到這里,解決方案也呼之欲出:既然是可以加載到的,那應(yīng)該就是沒有在注冊服務(wù)代碼執(zhí)行前加載。手動(dòng)讓類加載在注冊服務(wù)前運(yùn)行即可。

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

先執(zhí)行這個(gè),再執(zhí)行最初的:

NewInstanceServiceLoader.newServiceInstances(SQLParserConfiguration.class)

就可以加載到對應(yīng)的實(shí)例了。

4、分析總結(jié)

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

可通過ServiceLoader.load(service)確定是使用了Java的SPI機(jī)制時(shí)發(fā)生問題。SPI的本質(zhì)是通過META-INF/services目錄下對應(yīng)接口的文件找到實(shí)現(xiàn)類。

驗(yàn)證實(shí)現(xiàn)類可被JVM正常加載我使用了與源碼相同的類加載器并發(fā)布到預(yù)發(fā)布環(huán)境進(jìn)行驗(yàn)證。因?yàn)椴煌惣虞d器有不同的使用條件。比如:

ClassLoader.getSystemClassLoader()
在本機(jī)會(huì)正常運(yùn)行,但是服務(wù)器上會(huì)因?yàn)檫\(yùn)行的是打好的 jar 包,路徑發(fā)生變化,服務(wù)器上運(yùn)行報(bào)「找不到類」異常。

整個(gè)排查過程也有一些怎樣搜索答案的思考,比如只是根據(jù)最初的異常來搜索,發(fā)現(xiàn)網(wǎng)上搜的都不是本質(zhì)問題。后來雖然我用更接近本質(zhì)的問題:

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

2021-04-21 07:37:19

JVM復(fù)盤 日志

2024-04-25 08:21:36

Java對象計(jì)數(shù)法

2017-07-27 11:15:24

云存儲(chǔ)攝像機(jī)SD卡

2023-09-28 10:21:44

CSS前端

2024-12-02 09:01:23

Java虛擬機(jī)內(nèi)存

2011-04-25 09:37:03

2018-09-28 09:20:56

輸入法

2021-09-05 07:55:36

DDIA Raft 場景

2020-05-07 10:44:05

MySQL數(shù)據(jù)庫程序員

2019-05-16 08:10:42

無線路由器WiFi網(wǎng)絡(luò)

2013-03-12 14:30:09

Ubuntu操作系統(tǒng)

2015-08-06 10:14:15

造輪子facebook

2022-08-15 08:27:02

基站網(wǎng)絡(luò)

2010-07-13 10:56:43

Perl print

2021-08-31 07:57:21

輪詢鎖多線編程Java

2018-06-21 09:30:50

比特幣區(qū)塊鏈擴(kuò)容

2024-04-03 09:23:31

ES索引分析器

2019-09-30 07:50:51

ITOps云端ITOM

2018-05-23 00:20:29

2009-12-17 15:18:47

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)