异步中异常那点事

我接触异步概念最早的时候是在大学学习前端中,是一个很老的技术叫Ajax。异步编程很重要!很重要!很重要!在接口性能优化中我常用“sql优化、业务优化、算法优化”三把斧。其中“业务优化”中有“是否有可以异步进行的”这么一小则。今天的主题我们来看看Spring中相关异步中异常的那点事。

本文不是异步入门,默认读者有初步的异步知识。还不懂异步的读者请先网上查找相关资料先了解。

异步方法有两种形式:

  • 无返回值:void
  • 有返回值:可以声明常规的java.util.concurrent.Future返回类型,还可以声明 Spring 的org.springframework.util.concurrent.ListenableFuture,或者声明为 Spring 4.2 的 JDK 8 的java.util.concurrent.CompletableFuture:与异步任务进行更丰富的交互,并通过进一步的处理步骤立即进行组合。推荐CompletableFuture封装返回值

CompletableFuture是个好东西,网上资料也很多。推荐读者学习!

这两种方式的不同,导致了异步方法中异常管理不同。在有返回值的异步方法,调用者可以在调用get阻塞时捕获异步方法中的抛出的异常并进行处理。但是对于无返回值的异步方法,则不能如此了。那我们该怎么办呢?

我们先来看一下异步的例子。定义一个异步任务组件并交由spring管理

@Component
@Slf4j
public class AsyncTask {
  
    /**
     * 有返回值的异步方法
     */
    @Async("honeyAsyncExecutor")
    public CompletableFuture<Integer> asyncFuture() throws InterruptedException {
        log.info("start async task");
        Thread.sleep(1000)
        // 故意抛出异常
        int a = 1 % 0;
        log.info("end async task");
        return CompletableFuture.completedFuture(2);
    }


    /**
     * 无返回值的异步方法
     */
    @Async("honeyAsyncExecutor")
    public void async() throws InterruptedException {
        log.info("start async task");
        Thread.sleep(1000);
        // 故意抛出异常
        int a = 1 % 0;
        log.info("end async task");
    }
}

测试类

@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class AsyncTest {
    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void asyncTest() throws InterruptedException {
        log.info("start.....");
        CompletableFuture<Integer> future = asyncTask.asyncFuture();
        log.info("start main bs");
        Thread.sleep(2000);
        log.info("main bs end...");
        log.info("start request the result");
        try {
            Integer integer = future.get();
            log.info(String.valueOf(integer));
        } catch (ExecutionException exception) {
            log.info("catch the exception");
        }
    }
}

在这里插入图片描述
在结果中我们成功捕获了异常并进行了处理。注意:异步方法中抛出的异常会被包装成ExecutionException。

我们接着看一下没有返回值的异常该怎么捕获并处理

@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class AsyncTest {
    @Autowired
    private AsyncTask asyncTask;

	@Test
    public void testAsync() throws InterruptedException {
        log.info("start.....");
        asyncTask.async();
        log.info("start main bs");
        Thread.sleep(2000);
        log.info("main bs end...");
    }
}

在这里插入图片描述
结果中看到了SimpleAsyncUncaughtExceptionHandler这么个类。我们具体看一下这个类。
这个类是spring默认提供的针对没有返回值抛出异常时捕获到异常后,仅仅是打印,其他啥都没干。

public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

	private static final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class);


	@Override
	public void handleUncaughtException(Throwable ex, Method method, Object... params) {
		if (logger.isErrorEnabled()) {
			logger.error("Unexpected exception occurred invoking async method: " + method, ex);
		}
	}
}

相信读者看到这里,应该懂该怎么针对性处理无返回值异步方法的异常了。就是自定义一个我们的AsyncUncaughtExceptionHandler 。但是在这里,博主并不推荐直接像spring那样实现AsyncUncaughtExceptionHandler 接口来提供我们的异步未捕获异常处理器。如果我们使用异步,我们要养成这么一个良好习惯:我们要根据我们系统的运行情况、配置情况等配置AsyncExecutor。所以比较好的做法是实现AsyncConfigurer 接口,在接口里提供AsyncExecutor,并提供上面的AsyncUncaughtExceptionHandler 。并在异步方法上面的异步注解指定Executor。

getAsyncExecutor前面为什么加个@Bean是为了可以给@Async指定Executor

@Slf4j
@Configuration
public class HoneyAsyncConfigurer implements AsyncConfigurer {

    @Bean("honeyAsyncExecutor")
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(8);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(64);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setThreadNamePrefix("HoneyAsyncThread-");
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (Throwable ex, Method method, Object... params) -> {
            String errorBuilder = "Async execution error on method:" + method.toString() + " with parameters:"
                    + Arrays.toString(params);
            log.error(errorBuilder);
        };
    }
}

这里博主也没进行什么特殊处理,为了演示,仅仅是打印而已。在博主的Honey-OSS存储服务中,针对异步上传中也有涉及没有没有返回值的异步方法,在遇到预期中的异常时(像网络导致的链接异常)会利用重试机制进行重试。

说到这个重试,博主封装了基于注解的重试机制工具。后面分享出来。

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊杰eboy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值