基于Spring-AOP實(shí)現(xiàn)自定義分片工具詳解
1.背景
隨著數(shù)據(jù)量的增長(zhǎng),發(fā)現(xiàn)系統(tǒng)在與其他系統(tǒng)交互時(shí),批量接口會(huì)出現(xiàn)超時(shí)現(xiàn)象,發(fā)現(xiàn)原批量接口在實(shí)現(xiàn)時(shí),沒(méi)有做分片處理,當(dāng)數(shù)據(jù)過(guò)大時(shí)或超過(guò)其他系統(tǒng)閾值時(shí),就會(huì)出現(xiàn)錯(cuò)誤。由于與其他系統(tǒng)交互比較多,一個(gè)一個(gè)接口去做分片優(yōu)化,改動(dòng)量較大,所以考慮通過(guò)AOP解決此問(wèn)題。
2.Spring-AOP
AOP (Aspect Orient Programming),直譯過(guò)來(lái)就是 面向切面編程。AOP 是一種編程思想,是面向?qū)ο缶幊蹋∣OP)的一種補(bǔ)充。面向?qū)ο缶幊虒⒊绦虺橄蟪筛鱾€(gè)層次的對(duì)象,而面向切面編程是將程序抽象成各個(gè)切面。
Spring 中的 AOP 是通過(guò)動(dòng)態(tài)代理實(shí)現(xiàn)的。 Spring AOP 不能攔截對(duì)對(duì)象字段的修改,也不支持構(gòu)造器連接點(diǎn),我們無(wú)法在 Bean 創(chuàng)建時(shí)應(yīng)用通知。
3.功能實(shí)現(xiàn)
自定義分片處理分三個(gè)部分:自定義注解(MethodPartAndRetryer)、重試器(RetryUtil)、切面實(shí)現(xiàn)(RetryAspectAop)。
3.1 MethodPartAndRetryer
源碼
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodPartAndRetryer {
/**
* 失敗重試次數(shù)
* @return
*/
int times() default 3;
/**
* 失敗間隔執(zhí)行時(shí)間 300毫秒
* @return
*/
long waitTime() default 300L;
/**
* 分片大小
* @return
*/
int parts() default 200;
}
@interface說(shuō)明這個(gè)類(lèi)是個(gè)注解。
@Target是這個(gè)注解的作用域
public enum ElementType {
/** 類(lèi)、接口(包括注釋類(lèi)型)或枚舉聲明 */
TYPE,
/** 字段聲明(包括枚舉常量) */
FIELD,
/** 方法聲明 */
METHOD,
/** 正式的參數(shù)聲明 */
PARAMETER,
/** 構(gòu)造函數(shù)聲明 */
CONSTRUCTOR,
/** 局部變量聲明 */
LOCAL_VARIABLE,
/** 注釋類(lèi)型聲明*/
ANNOTATION_TYPE,
/** 程序包聲明 */
PACKAGE,
/**類(lèi)型參數(shù)聲明*/
TYPE_PARAMETER,
/**類(lèi)型的使用*/
TYPE_USE
}
@Retention注解的生命周期
public enum RetentionPolicy {
/** 編譯器處理完后不存儲(chǔ)在class中*/
SOURCE,
/**注釋將被編譯器記錄在類(lèi)文件中,但不需要在運(yùn)行時(shí)被VM保留。 這是默認(rèn)值*/
CLASS,
/**編譯器存儲(chǔ)在class中,可以由虛擬機(jī)讀取*/
RUNTIME
}
times():接口調(diào)用失敗時(shí),重試的次數(shù)。
waitTime():接口調(diào)用失敗是,間隔多長(zhǎng)時(shí)間再次調(diào)用。
int parts():進(jìn)行分片時(shí),每個(gè)分片的大小。
3.2 RetryUtil
源碼
public class RetryUtil<V> {
??????? public Retryer<V> getDefaultRetryer(int times,long waitTime) {
Retryer<V> retryer = RetryerBuilder.<V>newBuilder()
.retryIfException()
.retryIfRuntimeException()
.retryIfExceptionOfType(Exception.class)
.withWaitStrategy(WaitStrategies.fixedWait(waitTime, TimeUnit.MILLISECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(times))
.build();
return retryer;
}
}
說(shuō)明
- RetryerBuilder:是用于配置和創(chuàng)建Retryer的構(gòu)建器。
- retryIfException:拋出runtime異常、checked異常時(shí)都會(huì)重試,但是拋出error不會(huì)重試。
- retryIfRuntimeException:只會(huì)在拋runtime異常的時(shí)候才重試,checked異常和error都不重試。
- retryIfExceptionOfType:允許我們只在發(fā)生特定異常的時(shí)候才重試。
- withWaitStrategy:等待策略,每次請(qǐng)求間隔。
- withStopStrategy:停止策略,重試多少次后停止。
3.3 RetryAspectAop
源碼:
public class RetryAspectAop {
public Object around(final ProceedingJoinPoint point) throws Throwable {
Object result = null;
final Object[] args = point.getArgs();
boolean isHandler1 = isHandler(args);
if (isHandler1) {
String className = point.getSignature().getDeclaringTypeName();
String methodName = point.getSignature().getName();
Object firstArg = args[0];
List<Object> paramList = (List<Object>) firstArg;
//獲取方法信息
Method method = getCurrentMethod(point);
//獲取注解信息
MethodPartAndRetryer retryable = AnnotationUtils.getAnnotation(method, MethodPartAndRetryer.class);
//重試機(jī)制
Retryer<Object> retryer = new RetryUtil<Object>().getDefaultRetryer(retryable.times(),retryable.waitTime());
//分片
List<List<Object>> requestList = Lists.partition(paramList, retryable.parts());
for (List<Object> partList : requestList) {
args[0] = partList;
Object tempResult = retryer.call(new Callable<Object>() {
@Override
public Object call() throws Exception {
try {
return point.proceed(args);
} catch (Throwable throwable) {
log.error(String.format("分片重試報(bào)錯(cuò),類(lèi)%s-方法%s",className,methodName),throwable);
throw new RuntimeException("分片重試出錯(cuò)");
}
}
});
if (null != tempResult) {
if (tempResult instanceof Boolean) {
if (!((Boolean) tempResult)) {
log.error(String.format("分片執(zhí)行報(bào)錯(cuò)返回類(lèi)型不能轉(zhuǎn)化bolean,類(lèi)%s-方法%s",className,methodName));
throw new RuntimeException("分片執(zhí)行報(bào)錯(cuò)!");
}
result = tempResult;
} else if (tempResult instanceof List) {
if(result ==null){
result =Lists.newArrayList();
}
((List) result).addAll((List) tempResult);
}else {
log.error(String.format("分片執(zhí)行返回的類(lèi)型不支持,類(lèi)%s-方法%s",className,methodName));
throw new RuntimeException("不支持該返回類(lèi)型");
}
} else {
log.error(String.format("分片執(zhí)行返回的結(jié)果為空,類(lèi)%s-方法%s",className,methodName));
throw new RuntimeException("調(diào)用結(jié)果為空");
}
}
} else {
result = point.proceed(args);
}
return result;
}
private boolean isHandler(Object[] args) {
boolean isHandler = false;
if (null != args && args.length > 0) {
Object firstArg = args[0];
//如果第一個(gè)參數(shù)是list 并且數(shù)量大于1
if (firstArg!=null&&firstArg instanceof List &&((List) firstArg).size()>1) {
isHandler = true;
}
}
return isHandler;
}
private Method getCurrentMethod(ProceedingJoinPoint point) {
try {
Signature sig = point.getSignature();
MethodSignature msig = (MethodSignature) sig;
Object target = point.getTarget();
return target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
說(shuō)明:
getCurrentMethod:獲取方法信息即要做分片的批量調(diào)用的接口。
isHandler1:判斷是否要做分片處理,只有第一參數(shù)是list并且list 的值大于1時(shí)才做分片處理。
around:具體分片邏輯。
- 獲取要分片方法的參數(shù)。
- 判斷是否要做分片處理。
- 獲取方法。
- 獲取重試次數(shù)、重試間隔時(shí)間和分片大小。
- 生成重試器。
- 根據(jù)設(shè)置的分片大小,做分片處理。
調(diào)用批量接口并處理結(jié)果。
4.功能使用
4.1 配置文件

4.2 代碼示例
@MethodPartAndRetryer(parts=100) public Boolean writeBackOfGoodsSN(List<SerialDTO> listSerial,ObCheckWorker workerData)
只要在需要做分片的批量接口方法上,加上MethodPartAndRetryer注解就可以,重試次數(shù)、重試間隔時(shí)間和分片大小可以在注解時(shí)設(shè)置,也可以使用默認(rèn)值。
5.小結(jié)
通過(guò)自定義分片工具,可以快速地對(duì)老代碼進(jìn)行分片處理,而且增加了重試機(jī)制,提高了程序的可用性,提高了對(duì)老代碼的重構(gòu)效率。
到此這篇關(guān)于基于SpringAOP實(shí)現(xiàn)自定義分片工具詳解的文章就介紹到這了,更多相關(guān)SpringAOP自定義分片工具內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中File文件操作類(lèi)的超詳細(xì)使用教程
File類(lèi)在包java.io.File下、代表操作系統(tǒng)的文件對(duì)象(文件、文件夾),File類(lèi)提供了諸如:定位文件,獲取文件本身的信息、刪除文件、創(chuàng)建文件(文件夾)等功能,下面這篇文章主要給大家介紹了關(guān)于Java中File文件操作類(lèi)的超詳細(xì)使用教程,需要的朋友可以參考下2023-01-01
單例Bean注入多例Bean屬性失效問(wèn)題的四種解決方案
在實(shí)際的開(kāi)發(fā)過(guò)程中,我們有可能會(huì)遇到這樣一個(gè)場(chǎng)景:多例對(duì)象 A 需要作為屬性注入給單例對(duì)象 B,但是我們每次獲取 B 的時(shí)候,發(fā)現(xiàn)注入的 A 每次都是同一個(gè),并不是多例的,所以本文給大家介紹了如何解決單例Bean注入多例Bean屬性失效問(wèn)題,需要的朋友可以參考下2024-05-05
IDEA創(chuàng)建Java項(xiàng)目文件并運(yùn)行教程解析
這篇文章主要介紹了IDEA創(chuàng)建Java項(xiàng)目文件并運(yùn)行教程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
一文學(xué)習(xí)Java NIO的ByteBuffer工作原理
很多網(wǎng)友說(shuō)JDK又在寫(xiě)B(tài)ug!下面通過(guò)通過(guò)本文學(xué)習(xí)下為何Java NIO的ByteBuffer這么垃圾,涉及到ByteBuf API 的優(yōu)點(diǎn)及工作原理解析,感興趣的朋友跟隨小編一起看看吧2021-05-05
SpringBoot+Mybatis使用Mapper接口注冊(cè)的幾種方式
本篇博文中主要介紹是Mapper接口與對(duì)應(yīng)的xml文件如何關(guān)聯(lián)的幾種姿勢(shì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07
java 算法之歸并排序詳解及實(shí)現(xiàn)代碼
這篇文章主要介紹了java 算法之歸并排序詳解及實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03
Kotlin基本類(lèi)型自動(dòng)裝箱出現(xiàn)問(wèn)題解決辦法
這篇文章主要介紹了Kotlin基本類(lèi)型自動(dòng)裝箱出現(xiàn)問(wèn)題解決辦法的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家遇到這樣的問(wèn)題順利解決,需要的朋友可以參考下2017-10-10

