异步异常处理器AsyncUncaughtExceptionHandler

碰到一个异常遗漏处理的场景:常规的@RestControllerAdvice没有感知到我的异步方法里的异常。最后使用异步异常处理器AsyncUncaughtExceptionHandler解决

文章目录

      • 线程池配置
      • 异步异常处理器
      • 全局异常处理器
      • Controller快速测试
      • 总结

线程池配置

(定义了默认线程池,常用业务线程池,异步异常处理器)


/**
 * 线程池配置
 */

@Slf4j
@Configuration
@EnableAsync
public class SyncConfiguration implements AsyncConfigurer {

    public SyncConfiguration() {
        log.info("线程池配置 SyncConfiguration 已加载");
    }

    @Autowired
    AsyncExceptionHandler asyncExceptionHandler;

    /**
     * 使用@Async注解且未指明线程池时,会默认用这个线程池,避免直接用默认线程池 SimpleAsyncTaskExecutor
     * SimpleAsyncTaskExecutor 是 Spring 提供的默认线程池,它并不维护线程池中的线程,而是每次执行异步任务时都会创建一个新线程。
     * 它不支持线程重用,因此不适合长期高效的异步任务处理。
     * @return
     */
    @Override
    public Executor getAsyncExecutor() {
        return getDefaultThreadPoolTaskExecutor();
    }

    /**
     * 异步异常捕获、打印。异步异常如果没有手动捕获任由往外抛的话,外层方法是无法感知这个异常的
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return asyncExceptionHandler;
    }

    /**
     * 使用异步注解时默认的线程池
     * @return
     */
    public ThreadPoolTaskExecutor getDefaultThreadPoolTaskExecutor() {

        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(100);
        //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(140);
        //缓存队列
        taskExecutor.setQueueCapacity(600);
        //允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(60);
        //异步方法内部线程名称
        taskExecutor.setThreadNamePrefix("default-Service-");

        /**
         * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
         * 通常有以下四种策略:
         * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
         * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
         * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
         * ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.setTaskDecorator(new ContextCopyingDecorator());//装饰器
        taskExecutor.initialize();
        return taskExecutor;
    }


    @Bean(name = "aaaPoolTaskExecutor")
    public ThreadPoolTaskExecutor getAaaPoolTaskExecutor() {

        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(100);
        //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(140);
        //缓存队列
        taskExecutor.setQueueCapacity(600);
        //允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(60);
        //异步方法内部线程名称
        taskExecutor.setThreadNamePrefix("aaaPoolTaskExecutor-");
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.setTaskDecorator(new ContextCopyingDecorator());//装饰器

        taskExecutor.setThreadFactory(runnable -> {
            Thread thread = new Thread(runnable);
            thread.setUncaughtExceptionHandler((t, e) -> {
                log.info("异步线程未捕捉异常, 线程名称 = {},  e = {}", t.getName(), e);
            });
            return thread;
        });//不知道为什么,这个异常捕获不会起效

        taskExecutor.initialize();
        return taskExecutor;
    }

    @Bean(name = "bbbPoolTaskExecutor")
    public ThreadPoolTaskExecutor getBbbPoolTaskExecutor() {

        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(100);
        //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(140);
        //缓存队列
        taskExecutor.setQueueCapacity(600);
        //允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(60);
        //异步方法内部线程名称
        taskExecutor.setThreadNamePrefix("bbbPoolTaskExecutor-");
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.setTaskDecorator(new ContextCopyingDecorator());//装饰器
        taskExecutor.initialize();
        return taskExecutor;
    }


    @Bean(name = "postTaskExecutor")
    public ThreadPoolTaskExecutor postTaskExecutor() {

        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(300);
        //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(300);
        //缓存队列
        taskExecutor.setQueueCapacity(300);
        //允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(60);
        //异步方法内部线程名称
        taskExecutor.setThreadNamePrefix("postCall-");
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }

}


异步异常处理器

/**
 * 异步异常处理器
 */
@Slf4j
@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Autowired
    private DefaultExceptionHandler defaultExceptionHandler;

    @Override
    public void handleUncaughtException(Throwable e, Method method, Object... params) {
        log.error("异步方法执行异常, e = {}", e);
        // 调用自定义异常处理
        defaultExceptionHandler.defaultServerError(e);
    }
}

全局异常处理器


/**
 * 全局异常处理器
 */
@Component
@RestControllerAdvice
public class DefaultExceptionHandler {
	private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionHandler.class);


	@ExceptionHandler(value = ApiException.class)
	public Result serverError(ApiException e) {
		//logger.error("自定义异常 = {}", e);
		if (ApiExceptionEnum.containCode(e.getCode())) {
			return Result.error(e.getCode() + "  " + e.getMsg());
		} else {
			return Result.error(e.getCode(), e.getMsg());
		}
	}


	@ExceptionHandler(value = Throwable.class)
	public Result defaultServerError(Throwable e) {

		logger.error("未定义异常 = {}", e);

		//TODO 入参校验失败会进入这里
		if (e instanceof MethodArgumentNotValidException) {
			StringBuilder errorMessage = null;
			try {
				MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
				BindingResult bindingResult = ex.getBindingResult();

				// 从 BindingResult 中提取所有字段的验证错误
				List<FieldError> fieldErrors = bindingResult.getFieldErrors();

				// 获取第一个字段错误的消息,或者根据业务需要处理多个错误
				errorMessage = new StringBuilder();
				for (FieldError fieldError : fieldErrors) {
					// 这里获取的是注解上的message,如"acqChannel can not null"
					errorMessage.append(fieldError.getField()).append(" : ").append(fieldError.getDefaultMessage()).append("; ");
				}

				/*{
					"code": 500,
					"msg": "appOrderId : appOrderId can not null; ",
				}*/
				logger.error("入参异常 = {}", errorMessage.toString());
				return Result.error(500, errorMessage.toString());


			} catch (Exception exception) {
				logger.error("打印日志时异常", exception);
				return Result.error(500, "系统繁忙, 请稍后再试");
			}

		}

		return Result.error(ApiExceptionEnum.DEFAULT_FAIL.getCode() + " 系统繁忙, 请稍后再试"); //兜底响应

		/*
		{
			"code": 0,
			"message": "100009 系统繁忙, 请稍后再试",
			"data": null
		}*/
	}

}

Controller快速测试

@Autowired
    @Qualifier("aaaPoolTaskExecutor")
    private ThreadPoolTaskExecutor aaaPoolTaskExecutor;

    @Async
    //@Async("aaaPoolTaskExecutor")
    @GetMapping("/testThreadException")
    public void testThreadException() {
        //aaaPoolTaskExecutor.execute(new Runnable() {
        //    @Override
        //    public void run() {
        //        log.info("测试工厂类定义runnable异常处理");
        //        int i = 10/0;
        //        System.out.println("--");
        //    }
        //});
        int i = 10/0;

        //aaaPoolTaskExecutor.execute(new TaskTest());

    }

总结

1.异步异常最好自行捕获,如果没有自行捕获要用异步异常处理器处理。或者用Future.get处理
2.使用@Async的注意事项。最好指定线程池。如果没有指定线程池就加个默认线程池的配置,避免直接用spring默认的SimpleAsyncTaskExecutor。这个线程池只会创建新线程、不会进行线程回收,极端情况下会出现资源耗尽问题
3.思路:一般异常处理的教程讲完try-catch-finally就结束了。但同步异常和异步异常的处理方式不一样
4.UncaughtExceptionHandler 提供了一种回调处理异步线程执行失败的思想
5.异步要注意的问题是:配置,异常处理,拒绝策略,日常监控,线上中断(而不是靠服务重启)【线程池监控和中断方式暂时不是很清楚】
6.异步配置好像只对@Async注解生效。如果自行注入线程池,好像又不能用了【自己跑demo验证吧】
@Autowired
    @Qualifier("aaaPoolTaskExecutor")
    private ThreadPoolTaskExecutor aaaPoolTaskExecutor;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/951146.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

apollo内置eureka dashboard授权登录

要确保访问Eureka Server时要求输入账户和密码&#xff0c;需要确保以下几点&#xff1a; 确保 eurekaSecurityEnabled 配置为 true&#xff1a;这个配置项控制是否启用Eureka的安全认证。如果它被设置为 false&#xff0c;即使配置了用户名和密码&#xff0c;也不会启用安全认…

【Dify】Dify自定义模型设置 | 对接DMXAPI使用打折 Openai GPT 或 Claude3.5系列模型方法详解

一、Dify & DMXAPI 1、Dify DIFY&#xff08;Do It For You&#xff09;是一种自动化工具或服务&#xff0c;旨在帮助用户简化操作&#xff0c;减少繁琐的手动操作&#xff0c;提升工作效率。通过DIFY&#xff0c;用户能够快速完成任务、获取所需数据&#xff0c;并且可以…

【深度学习】布匹寻边:抓边误差小于3px【附完整链接】

布匹寻边 项目简介 布匹寻边是指布料裁剪过程中&#xff0c;通过AI寻边技术自动识别布匹的边缘&#xff0c;将检测到的边缘信息输出&#xff0c;确保裁剪的准确性&#xff0c;减少浪费&#xff0c;并提高生产效率。 项目需求 将打满针眼的布匹边缘裁剪掉&#xff0c;且误差小…

http range 下载大文件分片

摘自&#xff1a;https://www.jianshu.com/p/32c16103715a 上传分片下载也能分 HTTP 协议范围请求允许服务器只发送 HTTP 消息的一部分到客户端。范围请求在传送大的媒体文件&#xff0c;或者与文件下载的断点续传功能搭配使用时非常有用。 检测服务器端是否支持范围请求 假…

解决WordPress出现Fatal error: Uncaught TypeError: ftp_nlist()致命问题

错误背景 WordPress版本&#xff1a;wordpress-6.6.2-zh_CN WooCommerce版本&#xff1a;woocommerce.9.5.1 WordPress在安装了WooCommerce插件后&#xff0c;安装的过程中没有问题&#xff0c;在安装完成后提示&#xff1a; 此站点遇到了致命错误&#xff0c;请查看您站点管理…

用户使用LLM模型都在干什么?

Anthropic 对用户与 Claude 3.5 Sonnet 的大量匿名对话展开分析&#xff0c;主要发现及相关情况如下&#xff1a; 使用用途分布 软件开发主导&#xff1a;在各类使用场景中&#xff0c;软件开发占比最高&#xff0c;其中编码占 Claude 对话的 15% - 25%&#xff0c;网页和移动应…

【巨实用】Git客户端基本操作

本文主要分享Git的一些基本常规操作&#xff0c;手把手教你如何配置~ ● 一个文件夹中初始化Git git init ● 为了方便以后提交代码需要对git进行配置&#xff08;第一次使用或者需求变更的时候&#xff09;&#xff0c;告诉git未来是谁在提交代码 git config --global user.na…

腾讯云AI代码助手编程挑战赛:自动生成漂亮的网页

在当今数字化时代&#xff0c;网页设计和开发已经成为一项至关重要的技能。在当今时代&#xff0c;借助AI的力量&#xff0c;这部分工作变得简单。本文借助腾讯云AI代码助手——“自动生成需要的网页”。本文将详细介绍如何利用AI代码助手生成网页素材&#xff0c;帮助你轻松打…

多台PC共用同一套鼠标键盘

当环境中有多个桌面 pc 需要操作的时候&#xff0c;在 多台 pc 之间切换会造成很多的不方便 可以通过远程进行连接&#xff0c;但是有一个更好的方案是让多台机器之间共用同一套键盘鼠标 常用的解决方案 synergy 和 sharemouse&#xff0c;通过移动光标在不同的 pc 间切换 s…

UOS系统mysql服务安装

UOS系统mysql服务安装 背景 1、安装环境&#xff1a;kvm虚拟机2、运行环境&#xff1a;uos server-1060e3、架构&#xff1a;x864、安装mysql版本&#xff1a;mysql-5.71、安装准备 # Mysql官网 https://downloads.mysql.com/archives/community/ # 下载安装包 wget -i -c …

Binlog实现MySQL主从同步

主从复制原理 ● Master 数据库只要发生变化&#xff0c;立马记录到Binary log 日志文件中 ● Slave数据库启动一个I/O thread连接Master数据库&#xff0c;请求Master变化的二进制日志 ● Slave I/O获取到的二进制日志&#xff0c;保存到自己的Relay log 日志文件中。 ● Sla…

matlab离线安装硬件支持包

MATLAB 硬件支持包离线安装 本文章提供matlab硬件支持包离线安装教程&#xff0c;因为我的matlab安装的某种原因&#xff08;破解&#xff09;&#xff0c;不支持硬件支持包的安装&#xff0c;相信也有很多相同情况的朋友&#xff0c;所以记录一下我是如何离线安装的&#xff…

C#进阶-在Ubuntu上部署ASP.NET Core Web API应用

随着云计算和容器化技术的普及&#xff0c;Linux 服务器已成为部署 Web 应用程序的主流平台之一。ASP.NET Core 作为一个跨平台、高性能的框架&#xff0c;非常适合在 Linux 环境中运行。本篇博客将详细介绍如何在 Linux 服务器上部署 ASP.NET Core Web API 应用&#xff0c;包…

从光子到图像——相机如何捕获世界?

引言 你是否想过为何我们按一下相机快门就可以将眼前广袤多彩的世界显示于一个小小的相机屏幕上&#xff1f;本期推文中将带着大家重现从光子转换为电子、电子转换为图像中数字驱动值的整个流程。 ▲人们通过相机捕获眼前的场景 从光子到电子的转换 光线首先通过光学镜头进入相…

C# 或 .NetCore 如何使用 NPOI 导出图片到 Excel 文件

今天在本文中&#xff0c;我们将尝试使用NPOI库将图像插入到 Excel 文件的特定位置。请将以下逻辑添加到您的写作方法中&#xff0c;在 Excel 文件中添加图像&#xff08;JPEG、PNG&#xff09;,我已经有一个示例 jpeg 文件 - Read-write-excel-npoi.jpg &#xff0c;我们将尝试…

OpenCV实现基于拉普拉斯算子的浮雕特效

图像浮雕效果的实现原理主要基于图像处理技术&#xff0c;特别是利用图像中像素之间的灰度差异来模拟立体感。以下是对该原理的详细解释&#xff1a; 一、浮雕效果的基本概念 浮雕是把所要呈现的图像突起于材质表面&#xff0c;根据凹凸的程度不同从而形成三维的立体感。在计…

前端用json-server来Mock后端返回的数据处理

<html><body><div class"login-container"><h2>登录</h2><div class"login-form"><div class"form-group"><input type"text" id"username" placeholder"请输入用户名&q…

【xLua】xLua-master签名、加密Lua文件

GitHub - Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linux, osx, etc. 如果你想在项目工程上操作&#xff0c;又发现项目工程并没导入Tools&#xff0c;可以从xLua-master工程拷贝到项目工程Assets…

Unity学习笔记(六)使用状态机重构角色移动、跳跃、冲刺

前言 本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记 整体状态框架(简化) Player 是操作对象的类&#xff1a; 继承了 MonoBehaviour 用于定义游戏对象的行为&#xff0c;每个挂载在 Unity 游戏对象上的脚本都需要继承自 MonoBehaviour&#x…

AIDD-人工智能药物设计-AlphaFold系列:全面回顾AF1-3的关键研究成果及其对科学界的影响

AlphaFold系列&#xff1a;全面回顾AF1-3的关键研究成果及其对科学界的影响 本文章将围绕 AlphaFold 系列模型在蛋白质结构预测领域的前沿研究展开&#xff0c;重点介绍 AlphaFold1、AlphaFold2 与 AlphaFold3 的关键研究成果&#xff0c;以及它们对科学界和制药工业的深远影响…