CompletableFuture 和 @Async 相互切换运用,异步任务切面处理token,非void类型异常处理
CompletableFuture 异常处理
private void asyncChildFaults(FaultTreeRequest request, List<FmeaTreeNodeModel> fmeaTreeNodeModels) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
String token = AccountContext.currentToken();
for (FmeaTreeNodeModel node : fmeaTreeNodeModels) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
AccountContext.saveToken(token);
setChildFault(request, node);
} catch (Exception exception) {
throw new CompletionException(exception);
} finally {
AccountContext.removeToken();
}
});
futures.add(future);
}
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
} catch (CompletionException exception) {
Throwable errorCause = exception.getCause();
throw new AppException(getCode(errorCause), errorCause.getMessage());
} catch (Exception exception) {
log.info("FmeaService::asyncChildFaults::{}", exception.getMessage());
}
}
private Integer getCode(Throwable errorCause) {
if (errorCause instanceof AppException) {
return ((AppException) errorCause).getCode();
}
return HttpStatus.BAD_REQUEST.value();
}
全局异常处理配置
@ResponseBody
@ExceptionHandler(value = ExecutionException.class)
public ResponseEntity<Result<Void>> executionExceptionHandler(ExecutionException exception) {
LogUtil.error("ExecutionException exception:", exception);
ResponseEntity<Result<Void>> response = getConcurrentExceptionResponse(exception);
if (response != null) {
return response;
}
return ResponseEntity.status(HttpStatus.OK).body(Result.fail(ResultCodeEnum.ASYNCHRONOUS_TASK_EXCEPTION));
}
@ResponseBody
@ExceptionHandler(value = CompletionException.class)
public ResponseEntity<Result<Void>> completionExceptionHandler(CompletionException exception) {
LogUtil.error("CompletionException exception:", exception);
ResponseEntity<Result<Void>> response = getConcurrentExceptionResponse(exception);
if (response != null) {
return response;
}
return ResponseEntity.status(HttpStatus.OK).body(Result.fail(ResultCodeEnum.ASYNCHRONOUS_TASK_EXCEPTION));
}
// AppException 自定义异常 需要处理的异常信息
@Nullable
private ResponseEntity<Result<Void>> getConcurrentExceptionResponse(Exception exception) {
Throwable errorCause = Optional.of(exception).map(Exception::getCause).orElseGet(Exception::new);
if (errorCause instanceof AppException) {
return ResponseEntity.status(HttpStatus.OK)
.body(Result.fail(((AppException) errorCause).getCode(), errorCause.getMessage()));
}
return null;
}
全局异常处理 上面业务代码改造
private void asyncChildFaults(FaultTreeRequest request, List<FmeaTreeNodeModel> fmeaTreeNodeModels) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
String token = AccountContext.currentToken();
fmeaTreeNodeModels.forEach(node -> {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
AccountContext.saveToken(token);
setChildFault(request, node);
});
futures.add(future);
});
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
切面处理token
注解
@Target( {ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AsyncToken {
String value() default "";
}
切面处理
@Aspect
@Component
public class AsyncTokenAspect {
// 配自己的注解路径
@Pointcut("@annotation(com.*.*.requirement.annotation.AsyncToken)")
public void asyncMethod() {
}
@Around("asyncMethod()")
public Object setToken(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
// token 位于第一个参数
Object firstParm = Stream.of(args).findFirst().orElseGet(Object::new);
String token = "";
if (firstParm instanceof String) {
token = (String) firstParm;
}
AccountContext.saveToken(token);
try {
// 执行原始方法
return joinPoint.proceed();
} finally {
// 清除token值
AccountContext.removeToken();
}
}
}
Async 异步方法
/**
* 获取设计模式列表
*
* @param token token
* @param flatList 设计模式列表 扁平化结构
* @return CompletableFuture<Void>
*/
@Async
@AsyncToken
public CompletableFuture<Void> getAllReliabilityModels(String token, List<ReliabilitySpecificationTreeModel> flatList) {
String docId = reliabilitySpecificationProperties.getDocId();
String modelItemId = reliabilitySpecificationProperties.getModelItemId();
// 这里调用了Feign接口需要token 认证
commonBusiness.getReliabilitySpecificationTreeModels(docId, modelItemId, RELIABILITY_MODEL, flatList);
return CompletableFuture.completedFuture(null);
}
@Override
public List<ReliabilitySpecificationTreeModel> getAllModelsAndFuncPoints() {
List<ReliabilitySpecificationTreeModel> allReliabilityModels = new ArrayList<>();
List<ReliabilitySpecificationTreeModel> allReliabilityFuncPoints = new ArrayList<>();
String token = AccountContext.currentToken();
CompletableFuture<Void> reliabilityModelsFuture = dfxAnalysisRelationAsyncService.getAllReliabilityModels(token,
allReliabilityModels);
CompletableFuture<Void> reliabilityFuncPointsFuture
= dfxAnalysisRelationAsyncService.getAllReliabilityFuncPoints(token, allReliabilityFuncPoints);
CompletableFuture.allOf(reliabilityModelsFuture, reliabilityFuncPointsFuture).join();
return Stream.of(allReliabilityModels, allReliabilityFuncPoints)
.flatMap(List::stream)
.collect(Collectors.toList());
}
CompletableFuture 转成 @Async
原来
xxxService类中
没有利用全局异常捕获
private void asyncChildFaults(FaultTreeRequest request, List<FmeaTreeNodeModel> fmeaTreeNodeModels) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
String token = AccountContext.currentToken();
fmeaTreeNodeModels.forEach(node -> {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
AccountContext.saveToken(token);
setChildFault(request, node);
});
try {
// 获取异常信息
future.get();
} catch (ExecutionException exception) {
Throwable errorCause = Optional.of(exception).map(Exception::getCause).orElseGet(Exception::new);
throw new AppException(getCode(errorCause), errorCause.getMessage());
} catch (Exception exception) {
log.info("FmeaService::asyncChildFaults::{}", exception.getMessage());
} finally {
AccountContext.removeToken();
}
futures.add(future);
});
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
改成全局异常捕获 代码更加简洁了
但是异步任务代码和普通业务代码糅合在一块了,代码篇幅比较繁杂
private void asyncChildFaults(FaultTreeRequest request, List<FmeaTreeNodeModel> fmeaTreeNodeModels) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
String token = AccountContext.currentToken();
fmeaTreeNodeModels.forEach(node -> {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
AccountContext.saveToken(token);
setChildFault(request, node);
});
futures.add(future);
});
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
private void setChildFault(String token, FaultTreeRequest request, FmeaTreeNodeModel node) {
request.setId(node.getId());
request.setType(node.getNodeType());
request.setName(node.getName());
FmeaObjectAggregateModel objectModel = Optional.ofNullable(integrationFeign.failureModeList(request))
.map(com.huawei.mideas.requirement.model.Result::getData)
.orElseGet(FmeaObjectAggregateModel::new);
if (StringUtils.isBlank(objectModel.getId())) {
return;
}
// set 分析对象属性
setObjectField(node, objectModel);
BeanUtils.copyProperties(objectModel, node);
if (CollectionUtils.isEmpty(objectModel.getChildren())) {
return;
}
List<FmeaTreeNodeModel> childNode = Utils.objectToList(objectModel.getChildren(), FmeaTreeNodeModel.class);
node.setChildren(childNode);
}
private void setObjectField(FmeaTreeNodeModel node, FmeaObjectAggregateModel objectModel) {
node.setAnalysisObjectId(objectModel.getId());
node.setCode(objectModel.getCode());
node.setVersion(objectModel.getVersion());
node.setCategory(objectModel.getCategory());
node.setStatus(objectModel.getStatus());
}
CompletableFuture 转成 @Async
新加一个 xxxAsyncService类,将异步代码逻辑写在这个类中
@Service
public class xxxAsyncService {
@Resource
private IntegrationFeign integrationFeign;
/**
* 封装失效模式数据
*
* @param token token
* @param request 入参请求体
* @param node 失效模式树节点
*/
@Async
@AsyncToken
public CompletableFuture<Void> setChildFault(String token, FaultTreeRequest request, FmeaTreeNodeModel node) {
request.setId(node.getId());
request.setType(node.getNodeType());
request.setName(node.getName());
FmeaObjectAggregateModel objectModel = Optional.ofNullable(integrationFeign.failureModeList(request))
.map(com.huawei.mideas.requirement.model.Result::getData)
.orElseGet(FmeaObjectAggregateModel::new);
if (StringUtils.isBlank(objectModel.getId())) {
return CompletableFuture.completedFuture(null);
}
// set 分析对象属性
setObjectField(node, objectModel);
BeanUtils.copyProperties(objectModel, node);
if (CollectionUtils.isEmpty(objectModel.getChildren())) {
return CompletableFuture.completedFuture(null);
}
List<FmeaTreeNodeModel> childNode = Utils.objectToList(objectModel.getChildren(), FmeaTreeNodeModel.class);
node.setChildren(childNode);
return CompletableFuture.completedFuture(null);
}
private void setObjectField(FmeaTreeNodeModel node, FmeaObjectAggregateModel objectModel) {
node.setAnalysisObjectId(objectModel.getId());
node.setCode(objectModel.getCode());
node.setVersion(objectModel.getVersion());
node.setCategory(objectModel.getCategory());
node.setStatus(objectModel.getStatus());
}
}
xxxService调用 异步方法
@Resource
private FmeaAsyncService fmeaAsyncService;
private void asyncChildFaults(FaultTreeRequest request, List<FmeaTreeNodeModel> fmeaTreeNodeModels) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
String token = AccountContext.currentToken();
fmeaTreeNodeModels.forEach(node -> {
CompletableFuture<Void> future = fmeaAsyncService.setChildFault(token, request, node);
futures.add(future);
});
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
赋值token优化
主线程token传递到子线程
1 方法局部变量
2 InheritableThreadLocal
3 线程池
4 全局变量
...
上面是第一种方法,方法局部变量,但是代码有点冗余
下面优化成第二种方法
public class ThreadLocalUtil {
// 可继承主线程
private static final ThreadLocal<String> INHERITABLE_TOKEN = new InheritableThreadLocal<>();
public static void setToken(String token) {
INHERITABLE_TOKEN.set(token);
}
public static String getToken() {
return INHERITABLE_TOKEN.get();
}
public static void removeToken() {
INHERITABLE_TOKEN.remove();
}
}
@Aspect
@Component
public class AsyncTokenAspect {
@Pointcut("@annotation(com.huawei.mideas.requirement.annotation.AsyncToken)")
public void asyncToken() {
}
@Around("asyncToken()")
public Object setToken(ProceedingJoinPoint joinPoint) throws Throwable {
AccountContext.saveToken(ThreadLocalUtil.getToken());
try {
// 执行原始方法
return joinPoint.proceed();
} finally {
// 清除token值
ThreadLocalUtil.removeToken();
AccountContext.removeToken();
}
}
}
@Async
@AsyncToken
public CompletableFuture<Void> getAllReliabilityModels(List<ReliabilitySpecificationTreeModel> flatList) {
String docId = reliabilitySpecificationProperties.getDocId();
String modelItemId = reliabilitySpecificationProperties.getModelItemId();
commonBusiness.getReliabilitySpecificationTreeModels(docId, modelItemId, RELIABILITY_MODEL, flatList);
return CompletableFuture.completedFuture(null);
}
public List<ReliabilitySpecificationTreeModel> getAllModelsAndFuncPoints() {
List<ReliabilitySpecificationTreeModel> allReliabilityModels = new ArrayList<>();
List<ReliabilitySpecificationTreeModel> allReliabilityFuncPoints = new ArrayList<>();
ThreadLocalUtil.setToken(AccountContext.currentToken());
CompletableFuture<Void> reliabilityModelsFuture = dfxAnalysisRelationAsyncService.getAllReliabilityModels(allReliabilityModels);
CompletableFuture<Void> reliabilityFuncPointsFuture
= dfxAnalysisRelationAsyncService.getAllReliabilityFuncPoints(allReliabilityFuncPoints);
CompletableFuture.allOf(reliabilityModelsFuture, reliabilityFuncPointsFuture).join();
return Stream.of(allReliabilityModels, allReliabilityFuncPoints)
.flatMap(List::stream)
.collect(Collectors.toList());
}
InheritableThreadLocal 问题
造成无法拿到父线程数据的原因可能有以下几种情况:
- 父线程没有设置值:如果在父线程中没有设置 InheritableThreadLocal 的值,子线程就无法继承任何值。
- 父线程在子线程创建之后修改了值:InheritableThreadLocal 的值是在子线程创建时继承的,如果父线程在子线程创建之后修改了值,子线程将无法获取到最新的值。
- 子线程在父线程设置值之前就开始执行:如果子线程在父线程设置 InheritableThreadLocal 的值之前就开始执行,那么子线程将无法获取到父线程的值。
- 使用了线程池:如果使用线程池来管理线程,线程池可能会重用线程。在这种情况下,子线程可能会继承之前线程的值,而不是父线程的值。
@Async 使用了线程池,容易造成token赋值不了还是null
蓝天和白云是标配。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端