异步编程——@Async与@EnableAsync使用

本文详细阐述了在SpringBoot项目中如何正确使用@Async和@EnableAsync注解进行异步编程,包括基本使用规范、常见问题如注解失效的原因及解决方法,以及如何处理接口和代理类的问题。
摘要由CSDN通过智能技术生成

使用思路

异步场景及优势在此不多赘述。异步思路无非是在原本请求链路执行到某个环节时,将部分无需同步执行的操作交由主线程以外的其它线程执行。因此针对标题中两个注解的使用,我们常常会将异步执行内容单独封装一个方法并通过@Async修饰,然后在启动类/配置类(在异步方法所在类等其它地方也行,但规范要求是写在这两处)开启@EnableAsync

@SpringBootApplication
@EnableAsync
@ComponentScan(value = {"com.ivan.*"})
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}
public interface DemoService {
    String testOne() throws InterruptedException;

    String testTwo() throws InterruptedException;

    String testThree();
}
@RestController
@RequestMapping("/test")
public class DemoServiceImpl implements DemoService {
    @Autowired
    ApplicationContext applicationContext;
    @Resource
    @Lazy
    private DemoServiceImpl demoServiceImpl;

    @Override
    @GetMapping("/test_one")
    public String testOne() throws InterruptedException {
        //获取HelloService代理对象
        DemoServiceImpl bean = applicationContext.getBean(DemoServiceImpl.class);
        System.out.println("当前对象是否是代理对象:" + AopUtils.isAopProxy(bean));
        System.out.println("是否是cglib代理对象:" + AopUtils.isCglibProxy(bean));
        System.out.println("是否是jdk代理对象:" + AopUtils.isJdkDynamicProxy(bean));
        System.out.println(bean == this);
        //调用代理对象的异步方法
        bean.asyncMethod();
        return LocalDateTime.now().toString();
    }

    @Override
    @GetMapping("/test_two")
    public String testTwo() throws InterruptedException {
//        asyncMethod();
        System.out.println("当前对象是否是代理对象:" + AopUtils.isAopProxy(demoServiceImpl));
        System.out.println("是否是cglib代理对象:" + AopUtils.isCglibProxy(demoServiceImpl));
        System.out.println("是否是jdk代理对象:" + AopUtils.isJdkDynamicProxy(demoServiceImpl));
        demoServiceImpl.asyncMethod();
        return LocalDateTime.now().toString();
    }

    @Override
    @GetMapping("/test_three")
    public String testThree() {
        new Thread(() -> {
            try {
                Thread.sleep(10000);
                System.out.println("睡眠结束");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        return LocalDateTime.now().toString();
    }

    @Async
    //不能声明为private
    //返回值只能为void或者Future
    public void asyncMethod() throws InterruptedException {
        Thread.sleep(10000);
        System.out.println("睡眠结束");
    }
}

基本使用规范

  • @Async标注的异步方法,返回值只能为void或者Future,且方法不能声明为private,否则注解会失效。当其它线程调用这个方法时,就会开启一个新的子线程去异步处理该业务逻辑。

    切记:异步方法返回值为Future时,在调用方法的同时不要直接.get(),否则虽然开启了额外的线程,但主方法其实也堵塞在get()这行代码了,相当于就还是同步方法了。应当先调用异步方法返回Future<Object>在通过Future对象.get()获取结果数据

  • 使用此注解的方法的类对象,必须是spring管理下的bean对象 (如被@Service、@Component等修饰的Bean对象)
  • @Async注解还可用于修饰类,表示该类中的所有方法都是异步的
  • 在Spring项目中需要进行异步编程时,一定要开启异步。在启动类/配置类添加@EnableAsync

常见问题——@Async无效

  • 最常见情况——忘记加@EnableAsync注解
  • @Async标注的异步方法声明为private
  • 没有走Spring的代理类。(即Service里面方法A调用方法B,会不生效!!) 方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,需要先获取其代理类,通过代理类调用异步方法——异步方法所在类使用了异步注解@Async 但是没有实现接口的时候,代码底层走 Cglib 动态代理,DemoServiceImpl控制类注入到 SpringMVC 容器中。Cglib 动态代理生成的代理对象是采用继承目标对象(DemoServiceImpl)的模式生成代理类的,而目标对象(DemoServiceImpl)中有@RestController 注解,所以注解会被继承过来,这样Cglib 生成的代理对象就可以注入到SpringMVC 容器中。因为@Async注解方法与控制类在同一个类中导致没有走代理类,所以@Async 注解失效。

    解决方法一:新建一个类,将@Async标注的异步方法放入该类
    解决方法二:从spring上下文中取得代理对象DemoServiceImpl bean = applicationContext.getBean(DemoServiceImpl.class);
    解决方法三: 在同一个类内部使用self-invocation的方式来调用被@Async修饰的方法
    @Resource
    @Lazy
    private DemoServiceImpl demoServiceImpl;

常见问题——添加@EnableAsync注解后接口404

异步方法所在类实现了接口且使用了异步注解@Async,代码底层会走 JDK 动态代理,导致 OrderServiceImpl 控制类没有注入到 SpringMVC 容器中。因为 JDK 动态代理技术是基于接口实现的,而接口中没有@RestController注解,所以导致代理对象无法注册到SpringMVC容器中。DemoServiceImpl做为代理类实现 DemoService接口,代理类(DemoServiceImpl)发现 DemoService接口上面没有 RestController 注解,所以导致了代理类(DemoServiceImpl)不会注入到SpringMVC容器中。

  • @Async标注的异步方法所在类实现了接口并重写了方法且@EnableAsync注解没有手动注明属性proxyTargetClass = true

    解决方法:@EnableAsync(proxyTargetClass = true)

Spring源码:

/**  初始化 bean 对象
     * Detects handler methods at initialization.
     * @see #initHandlerMethods
     */
    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }

    /**
     * Scan beans in the ApplicationContext, detect and register handler methods.
     * @see #getCandidateBeanNames()  获取到所有的 bean 对象
     * @see #processCandidateBean
     * @see #handlerMethodsInitialized
     */
    protected void initHandlerMethods() {
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
/**
     * Determine the type of the specified candidate bean and call
     * {@link #detectHandlerMethods} if identified as a handler type.
     * <p>This implementation avoids bean creation through checking
     * {@link org.springframework.beans.factory.BeanFactory#getType}
     * and calling {@link #detectHandlerMethods} with the bean name.
     * @param beanName the name of the candidate bean
     * @since 5.1
     * @see #isHandler
     * @see #detectHandlerMethods
     */
    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            beanType = obtainApplicationContext().getType(beanName);
        }
        catch (Throwable ex) {
            // An unresolvable bean type, probably from a lazy bean - let's ignore it.
            if (logger.isTraceEnabled()) {
                logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
            }
        }
        if (beanType != null && isHandler(beanType)) {
            detectHandlerMethods(beanName);
        }
    }
/**
     * {@inheritDoc} 判断类上面是否有 Controller、RequestMapping 注解。
     * <p>Expects a handler to have either a type-level @{@link Controller}
     * annotation or a type-level @{@link RequestMapping} annotation.
     */
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

参考文章
【异步任务】@Async注解使用方法及注解失效解决办法
SpringBoot中@EnableAsync和@Async注解的使用
@Async 注解失效解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值