👉 這是一個或許對你有用的社群
《專案實戰(影片)》:從書中學,往事上“練” 《網際網路高頻面試題》:面朝簡歷學習,春暖花開 《架構 x 系統設計》:摧枯拉朽,掌控面試高頻場景題 《精進 Java 學習指南》:系統學習,網際網路主流技術棧 《必讀 Java 原始碼專欄》:知其然,知其所以然

👉這是一個或許對你有用的開源專案國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERP、CRM、AI 大模型等等功能:
Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud 影片教程:https://doc.iocoder.cn 【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本

前言
SELECT
*
FROM
device
WHEREidIN
(
1
,
2
,
3
,
4
)
SELECT
*
FROM
device
WHEREidIN
(
1
,
2
)
SELECT
*
FROM
device
WHEREidIN
(
3
,
4
)
@SplitWorkAnnotation
(setThreadPool = LIST_DEVICE_EXECUTOR, splitLimit =
20
, splitGroupNum =
10
)
publiclistDeviceDetail(Long projectId,@NeedSplitParam List<Long> deviceId)
{
......
}
fun(a,b,bigList) = fun(a,b,bigListPart1) + fun(a,b,bigListPart2)
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
專案地址:https://github.com/YunaiV/ruoyi-vue-pro 影片教程:https://doc.iocoder.cn/video/
定義AOP註解
-
setThreadPool: 執行緒池,可能阻塞比較大,不要用公共的執行緒池最好自己定義一個 -
handlerReturnClass: 返回值回撥函式,對應不同返回值處理邏輯:可能是合併可能取前十條可能求和 -
splitLimit: 超過多少需要拆分 -
splitGroupNum: 拆分時每組多少個
@Target
(ElementType.METHOD)
@Retention
(RetentionPolicy.RUNTIME)
public@interface
SplitWorkAnnotation {
/**
* 設定執行緒池
*
*
@return
{
@link
ThreadPoolEnum}
*/
ThreadPoolEnum setThreadPool()
;
/**
* 返回值處理
*
*
@return
{
@link
Class}<{
@link
?} {
@link
extends} {
@link
HandleReturn}>
*/
Class<? extends HandleReturn> handlerReturnClass()
default
MergeFunction
.class
;
/**
* 超過多少開始拆分 >
*
*
@return
int
*/
intsplitLimit()default 1000
;
/**
* 拆分後每組多少
*
*
@return
int
*/
intsplitGroupNum()default 100
;
}
加在需要拆分的引數上,只支援一個。因為兩兩組合情況非常複雜,也一般不符合實際使用情況。
@Retention
(RetentionPolicy.RUNTIME)
@Target
(ElementType.PARAMETER)
public@interface
NeedSplitParam {
}
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
專案地址:https://github.com/YunaiV/yudao-cloud 影片教程:https://doc.iocoder.cn/video/
使用AOP實現拆分多執行緒併發呼叫合併邏輯
@Aspect
@Component
@Slf
4j
publicclassSplitWorkAspect
{
/**
* 切入點表示式,攔截方法上有
@NeedSplitParaAnnotation
註解的所有方法
*
*
@return
void
*
@author
tangsiqi
*
@date
2021/8/9 18:17
*/
@Pointcut
(
"@annotation( com.demo.SplitWorkAnnotation)"
)
publicvoidneedSplit()
{
}
/**
*
@param
pjp
*
@return
{
@link
Object}
*
@throws
Throwable
*/
@Around
(
"needSplit()"
)
public Object around(ProceedingJoinPoint pjp)throws Throwable
{
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method targetMethod = methodSignature.getMethod();
SplitWorkAnnotation splitWorkAnnotation = targetMethod.getAnnotation(SplitWorkAnnotation
.class)
;
Object[] args = pjp.getArgs();
int
splitLimit = splitWorkAnnotation.splitLimit();
int
splitGroupNum = splitWorkAnnotation.splitGroupNum();
if
(args ==
null
|| args.length ==
0
|| splitLimit <= splitGroupNum) {
return
pjp.proceed();
}
int
needSplitParamIndex = -
1
;
for
(
int
i =
0
; i < targetMethod.getParameters().length; i++) {
Parameter parameter = targetMethod.getParameters()[i];
NeedSplitParam needSplitParam = parameter.getAnnotation(NeedSplitParam
.class)
;
if
(needSplitParam !=
null
) {
needSplitParamIndex = i;
break
;
}
}
if
(needSplitParamIndex == -
1
) {
return
pjp.proceed();
}
Object needSplitParam = args[needSplitParamIndex];
//只能處理Object[] 和 Collection
if
(!(needSplitParam
instanceof
Object[]) && !(needSplitParam
instanceof
List) && !(needSplitParam
instanceof
Set)) {
return
pjp.proceed();
}
//如果目標引數長度小於拆分下限跳過
boolean
notMeetSplitLen = (needSplitParam
instanceof
Object[] && ((Object[]) needSplitParam).length <= splitLimit)
|| (needSplitParam
instanceof
List && ((List) needSplitParam).size() <= splitLimit)
|| (needSplitParam
instanceof
Set && ((Set) needSplitParam).size() <= splitLimit);
if
(notMeetSplitLen) {
return
pjp.proceed();
}
// 去重,這一步看情況也可以不要
if
(needSplitParam
instanceof
List) {
List<?> list = (List<?>) needSplitParam;
if
(list.size() >
1
) {
needSplitParam =
new
ArrayList<>(
new
HashSet<>(list));
}
}
//算出拆分成幾批次
int
batchNum = getBatchNum(needSplitParam, splitGroupNum);
if
(batchNum ==
1
) {
return
pjp.proceed();
}
CompletableFuture<?>[] futures =
new
CompletableFuture[batchNum];
ThreadPoolEnum threadPool = splitWorkAnnotation.setThreadPool();
if
(threadPool ==
null
) {
return
pjp.proceed();
}
try
{
for
(
int
currentBatch =
0
; currentBatch < batchNum; currentBatch++) {
int
finalNeedSplitParamIndex = needSplitParamIndex;
int
finalCurrentBatch = currentBatch;
Object finalNeedSplitParam = needSplitParam;
futures[currentBatch] = CompletableFuture.supplyAsync(() -> {
Object[] dest =
new
Object[args.length];
//這一步很重要!!!因為多執行緒執行不能用原理的引數列表了,不然會導致混亂
System.arraycopy(args,
0
, dest,
0
, args.length);
try
{
//將其他引數保持不變,將需要拆分的引數替換為part引數
dest[finalNeedSplitParamIndex] = getPartParam(finalNeedSplitParam, splitGroupNum, finalCurrentBatch);
return
pjp.proceed(dest);
}
catch
(Throwable e) {
thrownew
RuntimeException(e);
}
}, threadPool.getThreadPoolExecutor());
}
CompletableFuture<Void> all = CompletableFuture.allOf(futures);
all.get();
Class<? extends HandleReturn> handleReturn = splitWorkAnnotation.handlerReturnClass();
List<Object> resultList =
new
ArrayList<>(futures.length);
for
(CompletableFuture<?> future : futures) {
resultList.add(future.get());
}
//獲取到每個part的結果然後呼叫處理函式
return
handleReturn.getDeclaredMethods()[
0
].invoke(handleReturn.getDeclaredConstructor().newInstance(), resultList);
}
catch
(InterruptedException | ExecutionException e) {
thrownew
RuntimeException(e);
}
}
/**
* 獲取批次數目
*
*
@param
needSplitParam1
*
@param
splitGroupNum
*
@return
{
@link
Integer}
*/
public Integer getBatchNum(Object needSplitParam1, Integer splitGroupNum)
{
if
(needSplitParam1
instanceof
Object[]) {
Object[] splitParam = (Object[]) needSplitParam1;
return
splitParam.length % splitGroupNum ==
0
? splitParam.length / splitGroupNum : splitParam.length / splitGroupNum +
1
;
}
elseif
(needSplitParam1
instanceof
Collection) {
Collection<?> splitParam = (Collection<?>) needSplitParam1;
return
splitParam.size() % splitGroupNum ==
0
? splitParam.size() / splitGroupNum : splitParam.size() / splitGroupNum +
1
;
}
else
{
return1
;
}
}
/**
* 獲取當前批次引數
*
*
@param
needSplitParam
*
@param
splitGroupNum
*
@param
batch
*
@return
{
@link
Object}
*
@throws
NoSuchMethodException
*
@throws
InvocationTargetException
*
@throws
InstantiationException
*
@throws
IllegalAccessException
*/
public Object getPartParam(Object needSplitParam, Integer splitGroupNum, Integer batch)throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException
{
if
(needSplitParam
instanceof
Object[]) {
Object[] splitParam = (Object[]) needSplitParam;
int
end = Math.min((batch +
1
) * splitGroupNum, splitParam.length);
return
Arrays.copyOfRange(splitParam, batch * splitGroupNum, end);
}
elseif
(needSplitParam
instanceof
List) {
List<?> splitParam = (List<?>) needSplitParam;
int
end = Math.min((batch +
1
) * splitGroupNum, splitParam.size());
return
splitParam.subList(batch * splitGroupNum, end);
}
elseif
(needSplitParam
instanceof
Set) {
List splitParam =
new
ArrayList<>((Set) needSplitParam);
int
end = Math.min((batch +
1
) * splitGroupNum, splitParam.size());
//引數具體化了
Set<?> set = (Set<?>) needSplitParam.getClass().getDeclaredConstructor().newInstance();
set.addAll(splitParam.subList(batch * splitGroupNum, end));
return
set;
}
else
{
returnnull
;
}
}
}
定義處理返回值的介面
/**
* 處理返回結果介面
*
*
@author
: TangSiQi
*
@date
: 2021年08月13日15:42
**/
publicinterfaceHandleReturn
{
/**
* 處理返回結果方法
*
*
@param
t 拆分後多次請求結果
*
@return
R 處理後的返回結果
*
@author
tangsiqi
*
@date
2021/8/13 15:55
*/
Object handleReturn(List t)
;
}
/**
* 集合List等合併策略
*
*
@author
: TangSiQi
*
@date
: 2021年08月13日15:32
**/
publicclassMergeFunctionimplementsHandleReturn
{
@Override
public Object handleReturn(List results)
{
if
(results ==
null
) {
returnnull
;
}
if
(results.size() <=
1
) {
//todo
return
results.get(
0
);
}
//這裡自己要知道具體返回型別
List first = (List) results.get(
0
);
for
(
int
i =
1
; i < results.size(); i++) {
first.addAll((List) results.get(i));
}
return
first;
}
}





