美文网首页【每天学点Spring】
【每天学点Spring】Spring @Async

【每天学点Spring】Spring @Async

作者: 伊丽莎白2015 | 来源:发表于2022-04-19 22:36 被阅读0次

@Async在之前的文章Spring Event里介绍过(https://www.jianshu.com/p/6799984d2093)3.2章。位于spring-context包中,3.1版本开始支持。

@Async的主要作用是支持异步调用方法,他会单独跑在另一个线程中,所以调用方无需等待Async方法,节省了时间。常用的场景如:通知,发邮件等。

文章内容:


Spring Async.jpg

1. 简单demo

@EnableAsync,表示项目开始异步方法调用,该注解有一些参考,下文有介绍。

@SpringBootApplication
@EnableAsync
public class SpringProjectApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringProjectApplication.class, args);
    }
}

AsyncOrderService是调用方。
AsyncOrderNotifyService提供异步方法。

@Slf4j
@Service
public class AsyncOrderService {
    @Autowired
    private AsyncOrderNotifyService asyncOrderNotifyService;

    public void saveOrderAndNotify() {
        asyncOrderNotifyService.notifyUser();
        log.info("end save order...");
    }
}

@Slf4j
@Service
public class AsyncOrderNotifyService {
    @Async
    public void notifyUser() {
        ThreadUtils.sleep(3000L);
        log.info("finished notifying...");
    }
}

测试方法:

    @Test
    public void saveOrderAndNotify() {
        asyncOrderService.saveOrderAndNotify();
        ThreadUtils.sleep(5000L); // wait for async method finish
    }

测试Log: 可以看出Notify方法的线程名称不是main,并且OrderService并没有等Notify结果,而是先执行完毕了。

2022-04-19 13:54:36.209 INFO 24992 --- [ main] AsyncOrderService : end save order...
2022-04-19 13:54:39.218 INFO 24992 --- [ task-1] AsyncOrderNotifyService : finished notifying...

2. @EnableAsync配置详解

这里特别要注意的就是配置proxyTargetClass影响的是Spring所有的代理,会影响@Transactional那块的功能。

@EnableAsync.jpg

举例:可自定义CustomAsync注解,用法同@Async一样:
注:如果配置了annotation类,那么原来的@Async注解会失效。

@EnableAsync(annotation = CustomAsync.class)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomAsync {
}

3. 重点介绍@Async

3.1 @Async注解不起作用的情况

由于用到了proxy,所以思路与Transactional注解失效思路差不多:

  1. 只针对public方法有效,private方法IDE也会提示报错。
  2. 只自己调用自己的方法也会无效。
    举例:
@Slf4j
@Service
public class AsyncOrderService {
    public void saveOrderAndNotify() {
        notifyUser();
        log.info("end save order...");
    }

    @Async
    public void notifyUser() {
        ThreadUtils.sleep(3000L);
        log.info("finished notifying...");
    }
}

测试log:都在主线程下执行,并没有异步:

2022-04-19 14:59:15.049 INFO 25118 --- [ main] AsyncOrderService : finished notifying...
2022-04-19 14:59:15.050 INFO 25118 --- [ main] AsyncOrderService : end save order...

3.2 @Async无返回值

第1章简单demo中的例子就是无返回值的@Async方法。

3.3 @Async有返回值

这里可以理解为:

  1. 调用者调用了异步方法。
  2. 不需要等待异步方法执行,而是自己执行自己的代码逻辑。
  3. 最后再拿异步方法的返回值(节省了时间)

举例:先是异步方法有返回值,需要用Future类封装:

    @Async
    public Future<Boolean> notifyUserAndReturn() {
        ThreadUtils.sleep(3000L);
        log.info("finished notifying and return true...");

        return new AsyncResult<>(true);
    }

调用方调用异步方法,以及拿到异步方法执行结果:

    public void saveOrderAndNotifyWithReturn() {
        Future<Boolean> future = asyncOrderNotifyService.notifyUserAndReturn();
        log.info("end save order...");

        try {
            while (true) {
                if (future.isDone()) {
                    boolean result = future.get().booleanValue();
                    log.info("get notify result : {}", result);
                    break;
                }
            }
        } catch (InterruptedException | ExecutionException e) {
            log.error("met exception.", e);
        }
    }

测试log:

2022-04-19 15:13:04.785 INFO 25124 --- [ main] AsyncOrderService : end save order...
2022-04-19 15:13:07.789 INFO 25124 --- [ task-1] AsyncOrderNotifyService : finished notifying and return true...
2022-04-19 15:13:07.791 INFO 25124 --- [ main] AsyncOrderService : get notify result : true

Future的异常处理(多线程的内容):
如果上述方法notifyUserAndReturn()报IllegalArgumentException异常,那么在调用方的代码中,会在future.get()方法中抛出。即在future.get().booleanValue()抛出。

4. Executor(重要):

4.1 异步方法执行需要另外的线程,Spring如何找线程池?
  1. Spring会搜索TaskExecutor接口的bean。
  2. 如果没有找到,则会找name=“taskExecutor"的bean。
  3. 如果上述1# 2#都没有,那么会自定义一个SimpleAsyncTaskExecutor的bean。不推荐,主要原因有:
    3.1 关于SimpleAsyncTaskExecutor的实现,不能很好的返回Exception异常。
    3.2 每次都为task新创建一个线程,不能复用线程。如果想要更好的管理线程,可以考虑自己实现一个TaskExecutor。
4.2 方法层自定义线程池:
@Configuration
public class AsyncConfig {
    @Bean(name = "testThreadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 线程名前缀:
        executor.setThreadNamePrefix("Test Pool - ");
        return executor;
    }
}

Spring支持多个Executor bean(看上述AsyncConfig配置),异步方法可以指定使用哪个线程池:

@Slf4j
@Service
public class AsyncOrderNotifyService {
    // 指定使用哪个线程池:
    @Async("testThreadPoolTaskExecutor")
    public void notifyUser() {
        ThreadUtils.sleep(3000L);
        log.info("finished notifying...");
    }
}

测试log:

2022-04-19 21:08:38.772 INFO 25385 --- [ main] AsyncOrderService : end save order...
2022-04-19 21:08:41.779 INFO 25385 --- [ Test Pool - 1] AsyncOrderNotifyService : finished notifying...

4.3 全局定义:

实现AsyncConfigurer接口,可以在方法getAsyncExecutor()自定义线程池:

@Configuration
public class GlobalAsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("Global Pool - ");
        executor.initialize();
        return executor;
    }
}

测试log:

2022-04-19 21:51:28.500 INFO 25470 --- [ main] AsyncOrderService : end save order...
2022-04-19 21:51:31.505 INFO 25470 --- [Global Pool - 1] AsyncOrderNotifyService : finished notifying...

5. 异常处理

5.1 返回值类型是Future:

在上述3.3所述,由于返回值类型是Future,异常处理就简单了:Future.get()会抛出异常。

5.2 自定义ExceptionHandler:

实现AsyncUncaughtExceptionHandler接口:
在实现AsyncConfigurer后,可以选择实现接口方法getAsyncExecutor()以及方法getAsyncUncaughtExceptionHandler(),因为这两个方法的修饰符是default,当然也可以自定义AsyncUncaughtExceptionHandler类:

@Configuration
public class GlobalAsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
    // 同上
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }
}

@Slf4j
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... params) {
        log.info("Exception message: " + throwable.getMessage());
        log.info("Method name: " + method.getName());
        for (Object param : params) {
            log.info("Parameter value: " + param);
        }
    }
}

测试,异步方法主动报错:

    @Async
    public void notifyUser() {
        ThreadUtils.sleep(3000L);
        if (1 == 1) {
            // 主动抛错:
            throw new IllegalArgumentException("illegal argument.");
        }
        log.info("finished notifying...");
    }

测试log:可以看到异常已经被自定义的CustomAsyncExceptionHandler捕获:

2022-04-19 22:06:12.278 INFO 25478 --- [ main] AsyncOrderService : end save order...
2022-04-19 22:06:15.280 INFO 25478 --- [Global Pool - 1] CustomAsyncExceptionHandler : Exception message: illegal argument.
2022-04-19 22:06:15.280 INFO 25478 --- [Global Pool - 1] CustomAsyncExceptionHandler : Method name: notifyUser


参考:
[baeldung] Spring Async:https://www.baeldung.com/spring-async#enable-async-support
Spring-boot异步(写的很好的文章):https://mongoding.github.io/2017/08/22/springboot-%E5%BC%82%E6%AD%A5/
ThreadPoolTaskScheduler not initialized问题分析:https://blog.csdn.net/blueheart20/article/details/103061426

相关文章

网友评论

    本文标题:【每天学点Spring】Spring @Async

    本文链接:https://www.haomeiwen.com/subject/xbsjertx.html