记录一次因@Async注解动态代理引发的问题

一、问题背景

1.1 自己遇到的问题

自己最近遇到的一个问题,SyncInfoTask是一个通过@Scheduled控制的一个定时任务,里面只有execute()这一个方法
在这里插入图片描述

在这里插入图片描述
在外部通过一个手动触发的方法来主动执行这个任务

    public String userInfo() {
        SyncInfoTask task = SpringUtils.getBean(SyncInfoTask.class);
        task.execute();
        return "OK";
    }

突然有一天我再execute()这个方法上加了@Async这个注解之后,突然 userInfo()这个方法就报错了。在调用SpringUtils.getBean(SyncInfoTask.class);的时候报

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'com.bitmart.agency.task.user.SyncInfoTask' available

意思也就是说根据SyncInfoTask.Class找不到这个bean。问题是之前是可用的。于是我就尝试换了一下根据bean的名称来调用SpringUtils.getBean(“syncInfoTask”),发现又可以了!于是这个问题引发了我的思考

二、问题排查

2.1 思考过程

首先我心想,根据class获取不到这个bean,那肯定是这个bean的类型已经不是SyncInfoTask.class的了。进一步排查发现@Async原理是spring的AOP。而AOP是我们熟知的概念,利用的是动态代理生成一个代理对象,因此此刻要解决这个问题,可以通过以下几个办法:

  • 一是上面我提到过的,使用SpringUtils.getBean(String name)方法可以获得。毕竟这个bean肯定是在的
  • 二是通过某种方法去获取某个bean的代理对象bean
    当然是这两点都不是这篇的重点,重点是为什么会出现这种问题。

2.2 Spring中AOP的动态代理

AOP其实是一个老生常谈的八股文话题了。我们都知道有两种:JDK的动态代理以及cglib动态代理。
这两者主要区别有以下几个:

a、JDK动态代理

  • 基于接口实现。也就是当前bean需要实现接口
  • 利用反射技术生成匿名的代理类走 InvokeHandler回调方法实现增强

b、Cglib动态代理

  • 利用 asm字节码技术生成一个子类 覆盖其中的方法实现增强

因此可以看出来,使用cglib动态代理生成的代理类,可以通过SpringUtils.getBean(Class class)获取。
因此之前被八股文的时候,记住了这么一句话:如果实现了接口则采用的 Jdk动态代理。没有实现接口就用cglib动态代理。乍一看,貌似这句话确实能解释现在这个问题,因为上面的这个类确实实现了接口,可以看做是被jdk代理了,因为不能根据class与获取bean

2.3 反常的一个思考

但是突然想起一个问题,我们平常写service类的时候,不都习惯于抽象一个接口,然后通过xxxServiceImpl来实现逻辑吗,我们平常通过SpringUtils.getBean(Class class)方式来获取service的bean很明显是可以的。可以看出平常的service,即便实现了接口,依然是通过cglib代理的,而不是jdk。所以这到底是怎么一回事呢?

继续查询,发现了结论:

2.3.1 针对Spring:Spring 中的动态代理,具体用哪种,分情况:

  • 如果代理对象有接口,就用 JDK 动态代理,否则就是 Cglib 动态代理。
  • 如果代理对象没有接口,那么就直接是 Cglib 动态代理。
    可以看出,跟我们上面说的是一样的。Spring动态代理的策略是能用jdk就用jdk,不能则用cglib

然而,对于SpringBoot,却有点不同:

2.3.2 Spring Boot2.0 之前

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
			matchIfMissing = true)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = false)
	public static class CglibAutoProxyConfiguration {

	}

}

可以得出结论:在application.properties文件中

  • 如果设置了 spring.aop.proxy-target-class 为 false,则使用 JDK 代理。
  • 如果开发者设置了 spring.aop.proxy-target-class 为 true,则使用 Cglib 代理
  • 如果开发者一开始就没配置 spring.aop.proxy-target-class 属性,则使用 JDK 代理
    可以看出来,默认是使用JDK代理

2.3.3 Spring Boot2.0 之后

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
		AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {

	}

}

可以看出,新版本最大的区别在于:

  • 如果没有设置,则默认使用Cglib动态代理

现在,结论已经有了。我现在使用版本的springboot,默认是用cglib动态代理

2.4 矛盾点的地方

既然现在使用的springboot,默认是用cglib动态代理,为什么还是不能SpringUtils.getBean(Class class)获取bean呢,cglib代理都已经是生成子类了。这不是矛盾了吗?
最后,终于发现了问题:
在这里插入图片描述
原来,@Async默认值是false。也就是说这个注解,独立的片不用cglib代理。原因找到!

2.5 最后的解决方法

在主类上,添加这一行:

@EnableAsync(proxyTargetClass = true)

这样的话,@Async也会被强制使用cglib代理了,问题解决!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值