SpringBoot中@EnableAsync和@Async注解的使用

这篇具有很好参考价值的文章主要介绍了SpringBoot中@EnableAsync和@Async注解的使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

异步的优点:

  • 提高应用程序的响应能力
  • 提高系统的吞吐量
  • 节约资源:异步操作可以避免在请求处理期间占用过多的线程资源,减少服务器的负载。
  • 优化用户体验

需要注意的问题:

  • 事务问题:异步处理时,需要注意在事务没有结束时做异步操作,可能会导致读取不到甚至覆盖事务中新增或更新的数据内容。

在 Spring Boot 中,可以通过 @EnableAsync 注解来启动异步方法调用的支持,通过 @Async 注解来标识异步方法,让方法能够在异步线程中执行。下面分别介绍它们的使用方法。

1.@EnableAsync 注解

@EnableAsync 是一个 Spring Boot 中用于启动异步方法调用的注解。使用 @EnableAsync 注解时,需要将其放置在一个配置类上,并且在配置类中通过 @Bean 方法创建一个线程池。

下面举个例子:

1.1 配置类使用示例

AsyncTaskExecutorConfig 类通过 @EnableAsync 注解来启用异步方法调用,然后在配置类中通过 @Bean 方法创建了一个名为 asyncExecutor 的线程池,用于执行异步方法。

import com.demo.async.ContextCopyingDecorator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * <p> @Title AsyncTaskExecutorConfig
 * <p> @Description 异步线程池配置
 *
 * @author ACGkaka
 * @date 2023/4/24 19:48
 */
@EnableAsync
@Configuration
public class AsyncTaskExecutorConfig {

    /**
     * 核心线程数(线程池维护线程的最小数量)
     */
    private int corePoolSize = 10;
    /**
     * 最大线程数(线程池维护线程的最大数量)
     */
    private int maxPoolSize = 200;
    /**
     * 队列最大长度
     */
    private int queueCapacity = 10;

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix("MyExecutor-");
        // for passing in request scope context 转换请求范围的上下文
        executor.setTaskDecorator(new ContextCopyingDecorator());
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.initialize();
        return executor;
    }
}

1.2 复制请求上下文

ContextCopyingDecorator 类使用了装饰者模式,用于将主线程中的请求上下文拷贝到异步子线程中,并且在异步子线程执行之后清空请求的上下文。

import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.Map;

/**
 * <p> @Title ContextCopyingDecorator
 * <p> @Description 上下文拷贝装饰者模式
 *
 * @author ACGkaka
 * @date 2023/4/24 20:20
 */
public class ContextCopyingDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        try {
            // 从父线程中获取上下文,然后应用到子线程中
            RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
            Map<String, String> previous = MDC.getCopyOfContextMap();
            SecurityContext securityContext = SecurityContextHolder.getContext();
            return () -> {
                try {
                    if (previous == null) {
                        MDC.clear();
                    } else {
                        MDC.setContextMap(previous);
                    }
                    RequestContextHolder.setRequestAttributes(requestAttributes);
                    SecurityContextHolder.setContext(securityContext);
                    runnable.run();
                } finally {
                    // 清除请求数据
                    MDC.clear();
                    RequestContextHolder.resetRequestAttributes();
                    SecurityContextHolder.clearContext();
                }
            };
        } catch (IllegalStateException e) {
            return runnable;
        }
    }
}

2.用法1:@Async 注解

@Async 注解是一个 Spring Boot 中用于标识异步方法的注解,通过在方法上添加 @Async 注解,可以让该方法在异步线程中执行。

下面举个例子:

2.1 测试Controller

DemoController 类中声明了 /demo/test 接口,接口中调用了 demoService.testError() 方法。

import com.demo.common.Result;
import com.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p> @Title DemoController
 * <p> @Description 测试Controller
 *
 * @author ACGkaka
 * @date 2023/4/24 18:02
 */
@RestController
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private DemoService demoService;

    @RequestMapping("/test")
    public Result<Object> test() {
        Result<Object> result = Result.succeed();
        System.out.println("start...");
        demoService.testError();
        System.out.println("end...");
        return result;
    }
}

2.2 测试Service

/**
 * <p> @Title DemoService
 * <p> @Description 测试Service
 *
 * @author ACGkaka
 * @date 2023/4/24 18:13
 */
public interface DemoService {

    /**
     * 测试异常
     */
    void testError() throws InterruptedException;
}

2.3 测试ServiceImpl

DemoServiceImpl 类使用了 @Async 注解,用于异步调用,testError() 方法中抛出了异常,用于测试异步执行。

这里 @Async 注解的 value 值指定了我们在配置类中声明的 taskExecutor 线程池。

  • 假如只配置了一个线程池,直接用 @Async 注解就会用自定义的线程池执行。
  • 假如配置了多个线程池,用 @Async("name") 来指定使用哪个线程池,如果没有指定,会用默认的 SimpleAsyncTaskExecutor 来处理。
import com.demo.service.DemoService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * <p> @Title DemoServiceImpl
 * <p> @Description 测试ServiceImpl
 *
 * @author ACGkaka
 * @date 2023/4/24 18:14
 */
@Service
public class DemoServiceImpl implements DemoService {
    @Async("taskExecutor")
    @Override
    public void testError() throws InterruptedException {
        throw new RuntimeException("测试异常");
    }
}

2.4.测试

访问接口:http://localhost:8080/demo/test

访问结果如下,可见异常并没有接口返回正常的结果,异步测试成功。

SpringBoot中@EnableAsync和@Async注解的使用

4.用法2:直接使用 taskExecutor 做异步

由于我们在第1步中,将异步线程池注入到了 taskExecutor Bean 容器中,我们就可以直接通过 @Autowired 或者 @Resource 获取到线程池,然后使用。

我们通过直接使用 taskExecutor 线程池的方式,重新实现 DemoServiceImpl.java:

4.1 重新实现:测试ServiceImpl

import com.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;

/**
 * <p> @Title DemoServiceImpl
 * <p> @Description 测试ServiceImpl
 *
 * @author ACGkaka
 * @date 2023/4/24 18:14
 */
@Service
public class DemoServiceImpl implements DemoService {

    @Qualifier("taskExecutor")
    @Autowired
    private TaskExecutor taskExecutor;

    @Override
    public void testError() {
        taskExecutor.execute(() -> {
            throw new RuntimeException("测试异常");
        });
    }
}

4.2 测试

访问接口:http://localhost:8080/demo/test

访问结果如下,可见异常并没有接口返回正常的结果,异步测试成功,直接使用线程池的方式也可行。

SpringBoot中@EnableAsync和@Async注解的使用

SpringBoot中@EnableAsync和@Async注解的使用

5.@Async异步不生效原因

1)@SpringBootApplication 启动类或配置类当中没有添加 @EnableAsync 注解。

(补充:项目中除了启动类和配置类外,任何一个注入到 Bean 容器中的类添加 @EnableAsync 注解都可以,但是规范用法是在启动类和配置类中添加注解。)

2)异步方法使用注解@Async的返回值只能为void或者Future。

3)异步方法不能使用static修饰

4)异步类没有使用 @Component 注解(或其他注解)导致spring无法扫描到异步类

5)异步方法不能与异步方法在同一个类中

6)类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象

7)在 @Async 方法上标注 @Transactional 是没用的。 在 @Async 方法调用的方法上标注@Transactional 有效。

8)调用被 @Async 标记的方法的调用者不能和被调用的方法在同一类中不然不会起作用!!!!!!!

9)使用 @Async 时是获取不到方法的返回值的,拿到的值为 null,如果返回的值是原始类型int、double、long等(不能为 null),就会报错。

SpringBoot中@EnableAsync和@Async注解的使用

6.补充:使用@Async后项目启动报Bean注入异常

使用 @Async 后项目启动报Bean注入异常,提示 in its raw version as part of a circular reference, but has eventually been wrap

详细报错信息如下:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userInfoServiceImpl': Bean with name 'userInfoServiceImpl' has been injected into other beans [authServiceImpl, loginLogServiceImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:623)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1307)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1227)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
	... 74 common frames omitted

Disconnected from the target VM, address: '127.0.0.1:61563', transport: 'socket'

Process finished with exit code 1

主要是因为被 @Async 修饰后,项目启动时会生成一个代理对象,这个代理对象产生的实例和 Spring 注解的实例不一致,就会抛出这个异常。可以尝试使用 @Lazy 注解通过懒加载的方式进行修复,或者直接使用自定义线程池的方式进行异步操作。

整理完毕,完结撒花~ 🌻





参考地址:

1.@EnableAsync@Async基本使用方法,https://www.cnblogs.com/fzhblog/p/14012401.html

2.spring boot- @EnableAsync和@Async(Spring boot 注解@Async不生效 无效 不起作用),https://blog.csdn.net/inthat/article/details/111162059文章来源地址https://www.toymoban.com/news/detail-424535.html

到了这里,关于SpringBoot中@EnableAsync和@Async注解的使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • @EnableAsync的使用、进阶、源码分析

    使用@EnableAsync开启异步切面,然后在异步调用的方法上加上@Asyc注解即可 @Async注解是异步切面默认的异步注解,我们可以在@EnableAsync(annotation = AsyncCustom.class)开启异步切面时指定自定义的异步注解 配置默认线程池 当@Async注解的value没有指定线程池名称时,将会使用此线程池,

    2024年02月06日
    浏览(35)
  • 深入理解Spring的@Async注解:实现异步方法调用

    在当今高速发展的应用开发领域,对于提升系统性能和响应能力的需求越来越迫切。而异步编程作为一种解决方案,已经成为现代应用开发中的一项重要技术。本篇博客将带您深入探究 Java 中的 @Async 注解,揭示其强大的异步执行能力和精妙的实现机制。 异步编程是一种编程

    2024年02月05日
    浏览(47)
  • 提升Spring Boot应用性能的秘密武器:揭秘@Async注解的实用技巧

    在日常业务开发中,异步编程已成为应对并发挑战和提升应用程序性能的关键策略。传统的同步编程方式,由于会阻碍主线程执行后续任务直至程序代码执行结束,不可避免地降低了程序整体效率与响应速度。因此,为克服这一瓶颈,开发者广泛采用异步编程技术,将那些可

    2024年03月11日
    浏览(49)
  • SpringBoot - @ConditionalOnProperty注解使用详解

    写在前面 在开发基于SpringBoot框架的项目时,会用到下面的条件注解,有时会有需要控制配置类是否生效或注入到Spring上下文中的场景,可以使用@ConditionalOnProperty注解来控制@Configuration的注解是否生效。 实现原理 @ConditionalOnProperty通过havingValue与配置文件中的值进行对比,如果

    2023年04月26日
    浏览(86)
  • SpringBoot缓存相关注解的使用

    @CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置 @Cacheable:主要方法的返回值将被加入缓存。在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问 @CachePut:主要用于数据新增和修改操作 @CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中

    2024年01月19日
    浏览(38)
  • 多个springboot整合使用rabbitmq(使用注解的方式)

    先参考单个springboot使用rabbitmq和了解rabbitmq的五种模式 单个springboot整合rabbitmq_java-zh的博客-CSDN博客 1、先创建两个springboot项目, 一个做生产者,一个做消费者  2、导包(生产者和消费者对应的内容都是一样) 3、编写配置文件 这里的配置文件生产者和消费者都一样 这里以rab

    2024年02月11日
    浏览(30)
  • SpringBoot复习(30):@DateTimeFormat注解的使用

    一、实体类 二、控制器类:

    2024年02月13日
    浏览(39)
  • SpringBoot简单使用切面类(@aspect注解)

    简介 Spring Boot中的AOP(Aspect Oriented Programming, 面向切面编程)可以让我们实现一些与业务逻辑无关的功能,如日志、事务、安全等。 特点 把这些跨切面关注点抽取出来,实现解耦。 使用切面承载这些功能的实现,而不污染业务逻辑。 在定义好的切入点Join Point,执行这些功能,比如方

    2024年02月10日
    浏览(41)
  • 解决springboot中使用@NotNull注解无效

    我们在使用@NotNull注解的时候,一般在实体类的字段上加上@NouNull注解: 然后在controller类中的方法上添加上@Valid的注解(我们还可以给bean后紧跟一个BindingResult,就可以获取到校验的结果信息) springboot 2.3之前的集成在 spring-boot-starter-web 里了,所以不需要额外引入包 springboot

    2024年02月14日
    浏览(36)
  • 【SpringBoot】AOP 自定义注解的使用详解

            Spring 中的切面 Aspect,这是 Spring 的一大优势。面向切面编程往往让我们的开发更加低耦合,也大大减少了代码量,同时呢让我们更专注于业务模块的开发,把那些与业务无关的东西提取出去,便于后期的维护和迭代。         AOP 的全称为 Aspect Oriented Programming,

    2024年02月05日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包