行時(shí))
靜態(tài)分析代碼質(zhì)量開發(fā)工具【免費(fèi)下載鏈接】error-proneCatch common Java mistakes as compile-time errors項(xiàng)目地址https://gitcode.com/gh_mirrors/er/error-prone點(diǎn)擊查看免費(fèi)下載Error Prone 的MissingRuntimeRetention檢查器位于core/src/main/java/com/google/errorprone/bugpatterns/inject/MissingRuntimeRetention.java用于在編譯期發(fā)現(xiàn)「被依賴注入框架用作 Scope作用域或 Qualifier限定符的注解卻沒(méi)有運(yùn)行時(shí)保留策略RUNTIME retention」這一常見錯(cuò)誤。讀完本文你將理解 JSR-330 對(duì)注解保留期的硬性要求、該檢查器的觸發(fā)規(guī)則與豁免邏輯以及如何利用其自動(dòng)修復(fù)能力一鍵為注解補(bǔ)上Retention(RUNTIME)避免 Guice、Dagger 等框架在反射場(chǎng)景下靜默注入錯(cuò)誤對(duì)象。背景注解保留期Retention Policy與反射式依賴注入Java 注解的保留期由java.lang.annotation.Retention決定共有三檔保留策略作用范圍能否被反射讀取SOURCE僅源碼階段編譯后即被丟棄否CLASS寫入 class 文件但運(yùn)行時(shí)不可見默認(rèn)值否RUNTIME寫入 class 文件且運(yùn)行時(shí)可見是依賴注入框架分兩類一類依賴反射在運(yùn)行時(shí)讀取注解如 Guice 的運(yùn)行時(shí)注入、Provider 方法查找另一類在編譯期生成代碼如 Dagger。但關(guān)鍵在于只要注解沒(méi)有RUNTIME保留期反射讀取到的就是一個(gè)空殼框架會(huì)靜默地把它當(dāng)成「沒(méi)有標(biāo)注」處理。問(wèn)題場(chǎng)景一個(gè)會(huì)讓生產(chǎn)環(huán)境扣款「假處理器」的示例原始文檔給出了一個(gè)非常典型的 Guice 示例。假設(shè)你有一個(gè)CreditCardProcessor及其測(cè)試實(shí)現(xiàn)并用限定符注解區(qū)分「測(cè)試用處理器」與「正式處理器」class CreditCardProcessor { Inject CreditCardProcessor(...) } Qualifier interface ForTests Provides ForTests CreditCardProcessor providesTestProcessor() { return new TestCreditCardProcessor(...) } ... Inject MyApp(CreditCardProcessor processor) { processor.issueCharge(...); // Issues a charge against a fake! }由于ForTests這個(gè)限定符注解沒(méi)有運(yùn)行時(shí)保留期Guice 的 provider 方法在反射時(shí)看不到ForTests標(biāo)注于是會(huì)把本應(yīng)只用于測(cè)試的TestCreditCardProcessor也綁定到普通的CreditCardProcessor注入點(diǎn)上。最終生產(chǎn)代碼processor.issueCharge(...)對(duì)著一張「假卡」發(fā)起了扣款——這是典型的「編譯期不報(bào)錯(cuò)、運(yùn)行期出大事」的靜默失敗。這正是MissingRuntimeRetention存在的意義它把這類問(wèn)題提前到編譯期暴露而不是等到線上出現(xiàn)無(wú)法解釋的行為。JSR-330 規(guī)范即使「編譯期框架」也要求 RUNTIME原始文檔特別強(qiáng)調(diào)了一條常被誤解的規(guī)范要求即使對(duì)于傳統(tǒng)上被認(rèn)為是「編譯期依賴」的 DI 框架JSR-330 規(guī)范仍然要求Qualifier和Scope注解具備運(yùn)行時(shí)保留期RUNTIME retention。也就是說(shuō)javax.inject.Qualifier與javax.inject.Scope的語(yǔ)義設(shè)計(jì)本身就依賴反射可見性任何自定義限定符/作用域注解都應(yīng)顯式聲明Retention(RUNTIME)這是 JSR-330 使用方應(yīng)當(dāng)遵守的約定而非某個(gè)框架的個(gè)性化偏好。檢查器實(shí)現(xiàn)原理它究竟匹配哪些注解MissingRuntimeRetention是一個(gè)實(shí)現(xiàn)ClassTreeMatcher的BugChecker注解聲明為severity ERROR見 BugPattern.java 中的SeverityLevel其行為分為三步第一步必須是注解類型。matchClass首先判斷classTree.getKind().equals(ANNOTATION_TYPE)只有interface聲明才會(huì)進(jìn)入后續(xù)檢查。第二步必須攜帶受關(guān)注的 DI 元注解。相關(guān)注解集合定義在 InjectMatchers.javaSCOPE_ANNOTATIONScom.google.inject.ScopeAnnotation、javax.inject.Scope、jakarta.inject.ScopeQUALIFIER_ANNOTATIONScom.google.inject.BindingAnnotation、javax.inject.Qualifier、jakarta.inject.Qualifier額外的com.google.inject.multibindings.MapKey、dagger.MapKey以及 Google 內(nèi)部框架的com.google.apps.framework.annotations.ProcessorAnnotation第三步校驗(yàn)保留期。核心判定來(lái)自 ElementPredicates.java 的doesNotHaveRuntimeRetention()若注解未標(biāo)注RetentioneffectiveRetentionPolicy按默認(rèn)值CLASS處理只要最終生效策略不是RUNTIME即判定違規(guī)。從源碼結(jié)構(gòu)看該檢查器同時(shí)覆蓋 Guice 與 Dagger 兩套生態(tài)的注解風(fēng)格且對(duì)「默認(rèn)保留期」同樣報(bào)警——這是最容易踩坑的點(diǎn)因?yàn)楹芏嚅_發(fā)者以為「不寫 Retention 就沒(méi)問(wèn)題」?;砻鈋xemption邏輯什么時(shí)候不報(bào)警檢查器并非一刀切。在 MissingRuntimeRetention.java 的exemptInjectAnnotation方法中可以看到三組精心設(shè)計(jì)的豁免路徑源碼保留期SOURCE絕不豁免。如果注解顯式聲明了Retention(SOURCE)即使處于豁免場(chǎng)景也照樣報(bào)警——這是最嚴(yán)重的一種情況。Android 兼容模式豁免。當(dāng)編譯時(shí)傳入-XDandroidCompatibletrue時(shí)檢查器認(rèn)為 Android 應(yīng)用更可能不使用反射式 DI因此對(duì)未聲明保留期的 DI 注解放行。測(cè)試用例ignoredOnAndroid與sourceRetentionStillFiringOnAndroid精確驗(yàn)證了「豁免 SOURCE 例外」的邊界。Dagger 組件/模塊內(nèi)部嵌套的注解豁免。若注解類型嵌套聲明在dagger.Component、dagger.Subcomponent、dagger.Module含 Hilt 的DefineComponent等類型內(nèi)部由IS_DAGGER_COMPONENT_OR_MODULE匹配視為 Dagger 編譯期處理場(chǎng)景而放行。測(cè)試用例nestedQualifierInDaggerModule覆蓋了該分支。注意源碼注釋中明確標(biāo)注這是一個(gè) TODOpoor hack說(shuō)明豁免邏輯是工程權(quán)衡而非規(guī)范JSR 規(guī)范本身仍要求運(yùn)行時(shí)保留期。自動(dòng)修復(fù)SuggestedFix一鍵補(bǔ)齊 RUNTIME 保留期該檢查器自帶修復(fù)建議分兩種情況處理注解完全沒(méi)有Retention修復(fù)會(huì)在注解聲明后追加Retention(RUNTIME)同時(shí)自動(dòng)補(bǔ)上java.lang.annotation.Retention的 import 和java.lang.annotation.RetentionPolicy.RUNTIME的靜態(tài) import。已有非 RUNTIME 的Retention直接將該注解節(jié)點(diǎn)替換為Retention(RUNTIME)。對(duì)應(yīng)重構(gòu)行為由 MissingRuntimeRetentionTest.java 的refactoring()測(cè)試用例驗(yàn)證// 修復(fù)前 Qualifier Target({TYPE, METHOD}) public interface Anno {} // 修復(fù)后自動(dòng)補(bǔ) import 與注解 Qualifier Target({TYPE, METHOD}) Retention(RUNTIME) public interface Anno {}測(cè)試覆蓋觸發(fā)與不觸發(fā)場(chǎng)景一目了然倉(cāng)庫(kù)中的測(cè)試文件系統(tǒng)性地定義了該檢查器的行為邊界可作為編寫注解時(shí)的對(duì)照清單會(huì)報(bào)警positive casesScopeRetention(SOURCE)ScopeAnnotationRetention(SOURCE)QualifierRetention(SOURCE)BindingAnnotationRetention(SOURCE)BindingAnnotation不寫Retention走默認(rèn) CLASS 保留期dagger.MapKey、com.google.inject.multibindings.MapKey默認(rèn)保留期不報(bào)警negative cases上述各注解顯式聲明Retention(RUNTIME)與 DI 無(wú)關(guān)的普通注解即使只有 SOURCE 保留期也不受影響診斷消息統(tǒng)一包含Retention(RUNTIME)提示測(cè)試斷言BUG: Diagnostic contains: Retention(RUNTIME)與自動(dòng)修復(fù)建議保持一致。實(shí)戰(zhàn)建議與啟用方式在引入該檢查器的項(xiàng)目中最穩(wěn)妥的寫法是在自定義 Qualifier/Scope 注解上總是顯式聲明Retention(RUNTIME)不要依賴默認(rèn)保留期也不要寫成 SOURCE。若希望臨時(shí)關(guān)閉或調(diào)整該檢查器可通過(guò) Error Prone 標(biāo)準(zhǔn)的-Xep:MissingRuntimeRetention:OFF或WARN/ERROR編譯參數(shù)控制本檢查器默認(rèn)等級(jí)為ERROR即默認(rèn)配置下出現(xiàn)即編譯失敗。遷移存量代碼時(shí)可直接應(yīng)用自動(dòng)修復(fù)建議批量補(bǔ)齊Retention(RUNTIME)重構(gòu)測(cè)試保證了該修復(fù)只改動(dòng)注解聲明與 import不會(huì)誤傷業(yè)務(wù)代碼。總結(jié)MissingRuntimeRetention用最直接的方式守住 DI 注解的反射可見性底線。理解了它的觸發(fā)規(guī)則Scope/Qualifier/MapKey 家族 非 RUNTIME 保留期與豁免邏輯Android、Dagger 嵌套你就能在編譯階段攔截「注入到假對(duì)象」這類運(yùn)行時(shí)災(zāi)難而不是等到生產(chǎn)環(huán)境扣完款才發(fā)現(xiàn)問(wèn)題。贊分享靜態(tài)分析代碼質(zhì)量開發(fā)工具【免費(fèi)下載鏈接】error-proneCatch common Java mistakes as compile-time errors項(xiàng)目地址https://gitcode.com/gh_mirrors/er/error-prone點(diǎn)擊查看免費(fèi)下載相關(guān)推薦QuickRecorder macOS 錄屏3 分鐘裝好文件小 40%QuickRecorder macOS 錄屏3 分鐘裝好文件小 40% 錄一段 10 分鐘的教程文件 800MB發(fā)群里傳了半小時(shí)??ㄗ〉耐皇窃趺翠涭o態(tài)分析代碼質(zhì)量開發(fā)工具Error Prone Finalize 檢查為什么你不該重寫 Object.finalizeError Prone Finalize 檢查為什么你不該重寫 Object.finalize Error Prone 的 Finalize 檢查會(huì)在編譯期直靜態(tài)分析代碼質(zhì)量開發(fā)工具理解 Error Prone 的 AttemptedNegativeZero 檢查為什么 -0 不是浮點(diǎn)負(fù)零理解 Error Prone 的 AttemptedNegativeZero 檢查為什么 0 不是浮點(diǎn)負(fù)零 在 Java 中寫 0 時(shí)得到的其實(shí)是整數(shù) 0靜態(tài)分析代碼質(zhì)量開發(fā)工具上一篇為什么選擇DRAKVUF Sandbox8大核心特性碾壓傳統(tǒng)沙箱工具下一篇CSL編輯器學(xué)術(shù)引用樣式的專業(yè)定制工具完全指南創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考