java retry(重试) spring retry, guava retrying 详解
轉(zhuǎn)載 自?http://blog.51cto.com/9250070/2156431
系列說明
java retry 的一步步實(shí)現(xiàn)機(jī)制。
java-retry 源碼地址
情景導(dǎo)入
簡單的需求
產(chǎn)品經(jīng)理:實(shí)現(xiàn)一個(gè)按條件,查詢用戶信息的服務(wù)。
小明:好的。沒問題。
代碼
- UserService.java
- UserServiceImpl.java
談話
項(xiàng)目經(jīng)理:這個(gè)服務(wù)有時(shí)候會失敗,你看下。
小明:OutService?在是一個(gè) RPC 的外部服務(wù),但是有時(shí)候不穩(wěn)定。
項(xiàng)目經(jīng)理:如果調(diào)用失敗了,你可以調(diào)用的時(shí)候重試幾次。你去看下重試相關(guān)的東西
重試
重試作用
對于重試是有場景限制的,不是什么場景都適合重試,比如參數(shù)校驗(yàn)不合法、寫操作等(要考慮寫是否冪等)都不適合重試。
遠(yuǎn)程調(diào)用超時(shí)、網(wǎng)絡(luò)突然中斷可以重試。在微服務(wù)治理框架中,通常都有自己的重試與超時(shí)配置,比如dubbo可以設(shè)置retries=1,timeout=500調(diào)用失敗只重試1次,超過500ms調(diào)用仍未返回則調(diào)用失敗。
比如外部 RPC 調(diào)用,或者數(shù)據(jù)入庫等操作,如果一次操作失敗,可以進(jìn)行多次重試,提高調(diào)用成功的可能性。
V1.0 支持重試版本
思考
小明:我手頭還有其他任務(wù),這個(gè)也挺簡單的。5 分鐘時(shí)間搞定他。
實(shí)現(xiàn)
- UserServiceRetryImpl.java
V1.1 代理模式版本
易于維護(hù)
項(xiàng)目經(jīng)理:你的代碼我看了,功能雖然實(shí)現(xiàn)了,但是盡量寫的易于維護(hù)一點(diǎn)。
小明:好的。(心想,是說要寫點(diǎn)注釋什么的?)
代理模式
為其他對象提供一種代理以控制對這個(gè)對象的訪問。
在某些情況下,一個(gè)對象不適合或者不能直接引用另一個(gè)對象,而代理對象可以在客戶端和目標(biāo)對象之間起到中介作用。
其特征是代理與委托類有同樣的接口。
實(shí)現(xiàn)
小明想到以前看過的代理模式,心想用這種方式,原來的代碼改動量較少,以后想改起來也方便些。
- UserServiceProxyImpl.java
V1.2 動態(tài)代理模式
方便拓展
項(xiàng)目經(jīng)理:小明啊,這里還有個(gè)方法也是同樣的問題。你也給加上重試吧。
小明:好的。
小明心想,我在寫一個(gè)代理,但是轉(zhuǎn)念冷靜了下來,如果還有個(gè)服務(wù)也要重試怎么辦呢?
- RoleService.java
代碼實(shí)現(xiàn)
- DynamicProxy.java
- 測試代碼
V1.3 動態(tài)代理模式增強(qiáng)
對話
項(xiàng)目經(jīng)理:小明,你動態(tài)代理的方式是挺會偷懶的,可是我們有的類沒有接口。這個(gè)問題你要解決一下。
小明:好的。(誰?寫服務(wù)竟然不定義接口)
- ResourceServiceImpl.java
字節(jié)碼技術(shù)
小明看了下網(wǎng)上的資料,解決的辦法還是有的。
- CGLIB
CGLIB?是一個(gè)功能強(qiáng)大、高性能和高質(zhì)量的代碼生成庫,用于擴(kuò)展JAVA類并在運(yùn)行時(shí)實(shí)現(xiàn)接口。
- javassist
javassist?(Java編程助手)使Java字節(jié)碼操作變得簡單。
它是Java中編輯字節(jié)碼的類庫;它允許Java程序在運(yùn)行時(shí)定義新類,并在JVM加載類文件時(shí)修改類文件。
與其他類似的字節(jié)碼編輯器不同,Javassist提供了兩個(gè)級別的API:源級和字節(jié)碼級。
如果用戶使用源代碼級API,他們可以編輯類文件,而不需要了解Java字節(jié)碼的規(guī)范。
整個(gè)API只使用Java語言的詞匯表進(jìn)行設(shè)計(jì)。您甚至可以以源文本的形式指定插入的字節(jié)碼;Javassist動態(tài)編譯它。
另一方面,字節(jié)碼級API允許用戶直接編輯類文件作為其他編輯器。
- ASM
ASM?是一個(gè)通用的Java字節(jié)碼操作和分析框架。
它可以用來修改現(xiàn)有的類或動態(tài)地生成類,直接以二進(jìn)制形式。
ASM提供了一些通用的字節(jié)碼轉(zhuǎn)換和分析算法,可以從這些算法中構(gòu)建自定義復(fù)雜的轉(zhuǎn)換和代碼分析工具。
ASM提供與其他Java字節(jié)碼框架類似的功能,但主要關(guān)注性能。
因?yàn)樗脑O(shè)計(jì)和實(shí)現(xiàn)都盡可能地小和快,所以非常適合在動態(tài)系統(tǒng)中使用(當(dāng)然也可以以靜態(tài)的方式使用,例如在編譯器中)。
實(shí)現(xiàn)
小明看了下,就選擇使用 CGLIB。
- CglibProxy.java
- 測試
V2.0 AOP 實(shí)現(xiàn)
對話
項(xiàng)目經(jīng)理:小明啊,最近我在想一個(gè)問題。不同的服務(wù),重試的時(shí)候次數(shù)應(yīng)該是不同的。因?yàn)榉?wù)對穩(wěn)定性的要求各不相同啊。
小明:好的。(心想,重試都搞了一周了,今天都周五了。)
下班之前,小明一直在想這個(gè)問題。剛好周末,花點(diǎn)時(shí)間寫個(gè)重試小工具吧。
設(shè)計(jì)思路
- 技術(shù)支持
spring
java 注解
- 注解定義
注解可在方法上使用,定義需要重試的次數(shù)
- 注解解析
攔截指定需要重試的方法,解析對應(yīng)的重試次數(shù),然后進(jìn)行對應(yīng)次數(shù)的重試。
實(shí)現(xiàn)
- Retryable.java
- RetryAspect.java
方法的使用
- fiveTimes()
當(dāng)前方法一共重試 5 次。
重試條件:服務(wù)拋出?AopRuntimeExption
- 測試日志
V3.0 spring-retry 版本
對話
周一來到公司,項(xiàng)目經(jīng)理又和小明談了起來。
項(xiàng)目經(jīng)理:重試次數(shù)是滿足了,但是重試其實(shí)應(yīng)該講究策略。比如調(diào)用外部,第一次失敗,可以等待 5S 在次調(diào)用,如果又失敗了,可以等待 10S 再調(diào)用。。。
小明:了解。
思考
可是今天周一,還有其他很多事情要做。
小明在想,沒時(shí)間寫這個(gè)呀。看看網(wǎng)上有沒有現(xiàn)成的。
spring-retry
Spring Retry?為 Spring 應(yīng)用程序提供了聲明性重試支持。 它用于Spring批處理、Spring集成、Apache Hadoop(等等)的Spring。
在分布式系統(tǒng)中,為了保證數(shù)據(jù)分布式事務(wù)的強(qiáng)一致性,大家在調(diào)用RPC接口或者發(fā)送MQ時(shí),針對可能會出現(xiàn)網(wǎng)絡(luò)抖動請求超時(shí)情況采取一下重試操作。 大家用的最多的重試方式就是MQ了,但是如果你的項(xiàng)目中沒有引入MQ,那就不方便了。
還有一種方式,是開發(fā)者自己編寫重試機(jī)制,但是大多不夠優(yōu)雅。
注解式使用
- RemoteService.java
重試條件:遇到?RuntimeException
重試次數(shù):3
重試策略:重試的時(shí)候等待 5S, 后面時(shí)間依次變?yōu)樵瓉淼?2 倍數(shù)。
熔斷機(jī)制:全部重試失敗,則調(diào)用?recover()?方法。
@Service public class RemoteService {private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);/*** 調(diào)用方法*/@Retryable(value = RuntimeException.class,maxAttempts = 3,backoff = @Backoff(delay = 5000L, multiplier = 2))public void call() {LOGGER.info("Call something...");throw new RuntimeException("RPC調(diào)用異常");}/*** recover 機(jī)制* @param e 異常*/@Recoverpublic void recover(RuntimeException e) {LOGGER.info("Start do recover things....");LOGGER.warn("We meet ex: ", e);}}- 測試
- 日志
三次調(diào)用的時(shí)間點(diǎn):
2018-08-08 16:03:26.409 2018-08-08 16:03:31.414 2018-08-08 16:03:41.416缺陷
spring-retry 工具雖能優(yōu)雅實(shí)現(xiàn)重試,但是存在兩個(gè)不友好設(shè)計(jì):
一個(gè)是重試實(shí)體限定為?Throwable?子類,說明重試針對的是可捕捉的功能異常為設(shè)計(jì)前提的,但是我們希望依賴某個(gè)數(shù)據(jù)對象實(shí)體作為重試實(shí)體,
但 sping-retry框架必須強(qiáng)制轉(zhuǎn)換為Throwable子類。
另一個(gè)就是重試根源的斷言對象使用的是 doWithRetry 的 Exception 異常實(shí)例,不符合正常內(nèi)部斷言的返回設(shè)計(jì)。
Spring Retry 提倡以注解的方式對方法進(jìn)行重試,重試邏輯是同步執(zhí)行的,重試的“失敗”針對的是Throwable,
如果你要以返回值的某個(gè)狀態(tài)來判定是否需要重試,可能只能通過自己判斷返回值然后顯式拋出異常了。
@Recover?注解在使用時(shí)無法指定方法,如果一個(gè)類中多個(gè)重試方法,就會很麻煩。
注解介紹
@EnableRetry
表示是否開始重試。
| 1 | proxyTargetClass | boolean | false | 指示是否要創(chuàng)建基于子類的(CGLIB)代理,而不是創(chuàng)建標(biāo)準(zhǔn)的基于Java接口的代理。 |
@Retryable
標(biāo)注此注解的方法在發(fā)生異常時(shí)會進(jìn)行重試
| 1 | interceptor | String | "" | 將 interceptor 的 bean 名稱應(yīng)用到 retryable() |
| 2 | value | Class[] | {} | 可重試的異常類型。 |
| 3 | label | String | "" | 統(tǒng)計(jì)報(bào)告的唯一標(biāo)簽。如果沒有提供,調(diào)用者可以選擇忽略它,或者提供默認(rèn)值。 |
| 4 | maxAttempts | int | 3 | 嘗試的最大次數(shù)(包括第一次失敗),默認(rèn)為3次。 |
| 5 | backoff | @Backoff | @Backoff() | 指定用于重試此操作的backoff屬性。默認(rèn)為空 |
@Backoff
| 1 | delay | long | 0 | 如果不設(shè)置則默認(rèn)使用 1000 milliseconds | 重試等待 |
| 2 | maxDelay | long | 0 | 最大重試等待時(shí)間 | |
| 3 | multiplier | long | 0 | 用于計(jì)算下一個(gè)延遲延遲的乘數(shù)(大于0生效) | |
| 4 | random | boolean | false | 隨機(jī)重試等待時(shí)間 |
@Recover
用于恢復(fù)處理程序的方法調(diào)用的注釋。一個(gè)合適的復(fù)蘇handler有一個(gè)類型為可投擲(或可投擲的子類型)的第一個(gè)參數(shù)br/>和返回與`@Retryable`方法相同的類型的值。
可拋出的第一個(gè)參數(shù)是可選的(但是沒有它的方法只會被調(diào)用)。
從失敗方法的參數(shù)列表按順序填充后續(xù)的參數(shù)。
方法式使用
注解式只是讓我們使用更加便捷,但是如果要更高的靈活性。可以使用各種提供的方法。
- SimpleDemo.java
- 執(zhí)行日志
spring-retry 結(jié)構(gòu)
概覽
-
RetryCallback: 封裝你需要重試的業(yè)務(wù)邏輯(上文中的doSth)
-
RecoverCallback:封裝在多次重試都失敗后你需要執(zhí)行的業(yè)務(wù)邏輯(上文中的doSthWhenStillFail)
-
RetryContext: 重試語境下的上下文,可用于在多次Retry或者Retry 和Recover之間傳遞參數(shù)或狀態(tài)(在多次doSth或者doSth與doSthWhenStillFail之間傳遞參數(shù))
-
RetryOperations : 定義了“重試”的基本框架(模板),要求傳入RetryCallback,可選傳入RecoveryCallback;
-
RetryListener:典型的“監(jiān)聽者”,在重試的不同階段通知“監(jiān)聽者”(例如doSth,wait等階段時(shí)通知)
-
RetryPolicy : 重試的策略或條件,可以簡單的進(jìn)行多次重試,可以是指定超時(shí)時(shí)間進(jìn)行重試(上文中的someCondition)
-
BackOffPolicy: 重試的回退策略,在業(yè)務(wù)邏輯執(zhí)行發(fā)生異常時(shí)。如果需要重試,我們可能需要等一段時(shí)間(可能服務(wù)器過于繁忙,如果一直不間隔重試可能拖垮服務(wù)器),
當(dāng)然這段時(shí)間可以是 0,也可以是固定的,可以是隨機(jī)的(參見tcp的擁塞控制算法中的回退策略)。回退策略在上文中體現(xiàn)為wait(); - RetryTemplate: RetryOperations的具體實(shí)現(xiàn),組合了RetryListener[],BackOffPolicy,RetryPolicy。
重試策略
-
NeverRetryPolicy:只允許調(diào)用RetryCallback一次,不允許重試
-
AlwaysRetryPolicy:允許無限重試,直到成功,此方式邏輯不當(dāng)會導(dǎo)致死循環(huán)
-
SimpleRetryPolicy:固定次數(shù)重試策略,默認(rèn)重試最大次數(shù)為3次,RetryTemplate默認(rèn)使用的策略
-
TimeoutRetryPolicy:超時(shí)時(shí)間重試策略,默認(rèn)超時(shí)時(shí)間為1秒,在指定的超時(shí)時(shí)間內(nèi)允許重試
-
ExceptionClassifierRetryPolicy:設(shè)置不同異常的重試策略,類似組合重試策略,區(qū)別在于這里只區(qū)分不同異常的重試
-
CircuitBreakerRetryPolicy:有熔斷功能的重試策略,需設(shè)置3個(gè)參數(shù)openTimeout、resetTimeout和delegate
- CompositeRetryPolicy:組合重試策略,有兩種組合方式,樂觀組合重試策略是指只要有一個(gè)策略允許重試即可以,
悲觀組合重試策略是指只要有一個(gè)策略不允許重試即可以,但不管哪種組合方式,組合中的每一個(gè)策略都會執(zhí)行
重試回退策略
重試回退策略,指的是每次重試是立即重試還是等待一段時(shí)間后重試。
默認(rèn)情況下是立即重試,如果需要配置等待一段時(shí)間后重試則需要指定回退策略BackoffRetryPolicy。
-
NoBackOffPolicy:無退避算法策略,每次重試時(shí)立即重試
-
FixedBackOffPolicy:固定時(shí)間的退避策略,需設(shè)置參數(shù)sleeper和backOffPeriod,sleeper指定等待策略,默認(rèn)是Thread.sleep,即線程休眠,backOffPeriod指定休眠時(shí)間,默認(rèn)1秒
-
UniformRandomBackOffPolicy:隨機(jī)時(shí)間退避策略,需設(shè)置sleeper、minBackOffPeriod和maxBackOffPeriod,該策略在[minBackOffPeriod,maxBackOffPeriod之間取一個(gè)隨機(jī)休眠時(shí)間,minBackOffPeriod默認(rèn)500毫秒,maxBackOffPeriod默認(rèn)1500毫秒
-
ExponentialBackOffPolicy:指數(shù)退避策略,需設(shè)置參數(shù)sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠時(shí)間,默認(rèn)100毫秒,maxInterval指定最大休眠時(shí)間,默認(rèn)30秒,multiplier指定乘數(shù),即下一次休眠時(shí)間為當(dāng)前休眠時(shí)間*multiplier
- ExponentialRandomBackOffPolicy:隨機(jī)指數(shù)退避策略,引入隨機(jī)乘數(shù)可以實(shí)現(xiàn)隨機(jī)乘數(shù)回退
guava-retrying
談話
小華:我們系統(tǒng)也要用到重試
項(xiàng)目經(jīng)理:小明前段時(shí)間用了 spring-retry,分享下應(yīng)該還不錯(cuò)
小明:spring-retry 基本功能都有,但是必須是基于異常來進(jìn)行控制。如果你要以返回值的某個(gè)狀態(tài)來判定是否需要重試,可能只能通過自己判斷返回值然后顯式拋出異常了。
小華:我們項(xiàng)目中想根據(jù)對象的屬性來進(jìn)行重試。你可以看下 guava-retry,我很久以前用過,感覺還不錯(cuò)。
小明:好的。
guava-retrying
guava-retrying?模塊提供了一種通用方法, 可以使用Guava謂詞匹配增強(qiáng)的特定停止、重試和異常處理功能來重試任意Java代碼。
- 優(yōu)勢
guava retryer工具與spring-retry類似,都是通過定義重試者角色來包裝正常邏輯重試,但是Guava retryer有更優(yōu)的策略定義,在支持重試次數(shù)和重試頻度控制基礎(chǔ)上,能夠兼容支持多個(gè)異常或者自定義實(shí)體對象的重試源定義,讓重試功能有更多的靈活性。
Guava Retryer也是線程安全的,入口調(diào)用邏輯采用的是?java.util.concurrent.Callable?的?call()?方法
代碼例子
入門案例
遇到異常之后,重試 3 次停止
- HelloDemo.java
- 日志
重試策略
- ExponentialBackoff.java
重試次數(shù):3
重試策略:固定等待 3S
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder().retryIfResult(Predicates.isNull()).retryIfExceptionOfType(IOException.class).retryIfRuntimeException().withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS)).withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try {retryer.call(callable);} catch (RetryException | ExecutionException e) {e.printStackTrace();}- 日志
guava-retrying 簡介
RetryerBuilder
RetryerBuilder 是一個(gè) factory 創(chuàng)建者,可以定制設(shè)置重試源且可以支持多個(gè)重試源,可以配置重試次數(shù)或重試超時(shí)時(shí)間,以及可以配置等待時(shí)間間隔,創(chuàng)建重試者 Retryer 實(shí)例。
RetryerBuilder 的重試源支持 Exception 異常對象和自定義斷言對象,通過retryIfException 和 retryIfResult 設(shè)置,同時(shí)支持多個(gè)且能兼容。
- retryIfException
retryIfException,拋出 runtime 異常、checked 異常時(shí)都會重試,但是拋出 error 不會重試。
- retryIfRuntimeException
retryIfRuntimeException 只會在拋 runtime 異常的時(shí)候才重試,checked 異常和error 都不重試。
- retryIfExceptionOfType
retryIfExceptionOfType 允許我們只在發(fā)生特定異常的時(shí)候才重試,比如NullPointerException 和 IllegalStateException 都屬于 runtime 異常,也包括自定義的error。
如:
retryIfExceptionOfType(Error.class)// 只在拋出error重試當(dāng)然我們還可以在只有出現(xiàn)指定的異常的時(shí)候才重試,如:
```java
.retryIfExceptionOfType(IllegalStateException.class)
.retryIfExceptionOfType(NullPointerException.class)
- retryIfResult
retryIfResult 可以指定你的 Callable 方法在返回值的時(shí)候進(jìn)行重試,如
// 返回false重試 .retryIfResult(Predicates.equalTo(false)) //以_error結(jié)尾才重試 .retryIfResult(Predicates.containsPattern("_error$"))- RetryListener
當(dāng)發(fā)生重試之后,假如我們需要做一些額外的處理動作,比如log一下異常,那么可以使用RetryListener。
每次重試之后,guava-retrying 會自動回調(diào)我們注冊的監(jiān)聽。
可以注冊多個(gè)RetryListener,會按照注冊順序依次調(diào)用。
.withRetryListener(new RetryListener { @Override public <T> void onRetry(Attempt<T> attempt) { logger.error("第【{}】次調(diào)用失敗" , attempt.getAttemptNumber()); } } )主要接口
| 1 | Attempt | 一次執(zhí)行任務(wù) | ? |
| 2 | AttemptTimeLimiter | 單次任務(wù)執(zhí)行時(shí)間限制 | 如果單次任務(wù)執(zhí)行超時(shí),則終止執(zhí)行當(dāng)前任務(wù) |
| 3 | BlockStrategies | 任務(wù)阻塞策略 | 通俗的講就是當(dāng)前任務(wù)執(zhí)行完,下次任務(wù)還沒開始這段時(shí)間做什么),默認(rèn)策略為:BlockStrategies.THREAD_SLEEP_STRATEGY |
| 4 | RetryException | 重試異常 | ? |
| 5 | RetryListener | 自定義重試監(jiān)聽器 | 可以用于異步記錄錯(cuò)誤日志 |
| 6 | StopStrategy | 停止重試策略 | ? |
| 7 | WaitStrategy | 等待時(shí)長策略 | (控制時(shí)間間隔),返回結(jié)果為下次執(zhí)行時(shí)長 |
| 8 | Attempt | 一次執(zhí)行任務(wù) | |
| 9 | Attempt | 一次執(zhí)行任務(wù) |
StopStrategy
提供三種:
- StopAfterDelayStrategy
設(shè)定一個(gè)最長允許的執(zhí)行時(shí)間;比如設(shè)定最長執(zhí)行10s,無論任務(wù)執(zhí)行次數(shù),只要重試的時(shí)候超出了最長時(shí)間,則任務(wù)終止,并返回重試異常RetryException;
- NeverStopStrategy
不停止,用于需要一直輪訓(xùn)知道返回期望結(jié)果的情況;
- StopAfterAttemptStrategy
設(shè)定最大重試次數(shù),如果超出最大重試次數(shù)則停止重試,并返回重試異常;
WaitStrategy
- FixedWaitStrategy
固定等待時(shí)長策略;
- RandomWaitStrategy
隨機(jī)等待時(shí)長策略(可以提供一個(gè)最小和最大時(shí)長,等待時(shí)長為其區(qū)間隨機(jī)值)
- IncrementingWaitStrategy
遞增等待時(shí)長策略(提供一個(gè)初始值和步長,等待時(shí)間隨重試次數(shù)增加而增加)
- ExponentialWaitStrategy
指數(shù)等待時(shí)長策略;
- FibonacciWaitStrategy
Fibonacci 等待時(shí)長策略;
- ExceptionWaitStrategy
異常時(shí)長等待策略;
- CompositeWaitStrategy
復(fù)合時(shí)長等待策略;
總結(jié)
優(yōu)雅重試共性和原理
正常和重試優(yōu)雅解耦,重試斷言條件實(shí)例或邏輯異常實(shí)例是兩者溝通的媒介。
約定重試間隔,差異性重試策略,設(shè)置重試超時(shí)時(shí)間,進(jìn)一步保證重試有效性以及重試流程穩(wěn)定性。
都使用了命令設(shè)計(jì)模式,通過委托重試對象完成相應(yīng)的邏輯操作,同時(shí)內(nèi)部封裝實(shí)現(xiàn)重試邏輯。
spring-retry 和 guava-retry 工具都是線程安全的重試,能夠支持并發(fā)業(yè)務(wù)場景的重試邏輯正確性。
優(yōu)雅重試適用場景
功能邏輯中存在不穩(wěn)定依賴場景,需要使用重試獲取預(yù)期結(jié)果或者嘗試重新執(zhí)行邏輯不立即結(jié)束。比如遠(yuǎn)程接口訪問,數(shù)據(jù)加載訪問,數(shù)據(jù)上傳校驗(yàn)等等。
對于異常場景存在需要重試場景,同時(shí)希望把正常邏輯和重試邏輯解耦。
對于需要基于數(shù)據(jù)媒介交互,希望通過重試輪詢檢測執(zhí)行邏輯場景也可以考慮重試方案。
談話
項(xiàng)目經(jīng)理:我覺得 guava-retry 挺好的,就是不夠方便。小明啊,你給封裝個(gè)基于注解的吧。
小明:……
總結(jié)
以上是生活随笔為你收集整理的java retry(重试) spring retry, guava retrying 详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java中final的意义
- 下一篇: RunTime.getRunTime()