动态代理的方式
springboot貌似2.0前后,采用的动态代理方案不太一样:
- 2.0之前:实现接口的类的代理采用jdk动态代理,没有实现接口的类采用CGLib动态代理
- 2.0以及以后:不管是否实现接口,都默认采用CGLib代理
ps:这个分界版本可能不太准,具体可以测试一下
jdk动态代理的小坑
1 | "abstractTaskImpl") ( |
上面的类中,doTaskOne
方法添加了@Async
注解,所以实际运行过程中会被代理,保证doTaskOne
方法会进行异步调用。
实现了接口,使用的是jdk动态代理(测试版本:springboot-1.5.8),所以代理类是实现了AbstractTaskInter
接口,注入的时候,通过接口的类型可以正常注入。
1 | @Autowired |
此时,我想给类添加一个非接口的异步调用方法,如下:
1 | "abstractTaskImpl") ( |
因为other
方法不是接口的方法,所以通过接口类型注入的时候,无法调用到该方法,所以需要通过实现类具体类型来注入:
1 | @Autowired |
此时运行程序的时候,会出现异常,显示abstractTaskImpl
这个bean的类型不是AbstractTaskImpl
:
1 | Error creating bean with name 'hong.study.utils.springboot.async.AsyncTaskTest': Unsatisfied dependency expressed through field 'abstractTaskInter'; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'abstractTaskImpl' is expected to be of type 'hong.study.utils.springboot.async.AbstractTaskImpl' but was actually of type 'com.sun.proxy.$Proxy79' |
原因在于,生成的代理类是通过jdk动态代理生成的,他是实现了AbstractTaskInter
接口的类,跟AbstractTaskImpl
类是平等的两个实现类,所以会出现上面的bean类型错误
小坑的解决方案
实现另一个接口
上面的AbstractTaskImpl
类去实现另一个接口OtherInter
,该接口中有方法other
:
1 | "abstractTaskImpl") ( |
注入的时候,通过这个新的接口类型进行注入,这样可以成功。
1 | @Autowired |
将动态代理的方式改为CGLib
CGLib的动态代理是通过生成被代理类的子类来实现的,代理类的类型就是被代理类,所以注入的时候通过AbstractTaskImpl
进行注入是可以的,不会出现上面的代理类和注入的类型不一致的情况。
1 | @Autowired |
开启CGLib
springboot2.0之后,默认都是CGLib的方式了,但是对于上面提到的springboot-1.5.8
版本,需要进行一些手动设置:
- 添加全局配置:
spring.aop.proxy-target-class=true
,但是这个经过测试不太好使,而且即使生效了也是全局修改,可能会影响到其他的类的动态代理的生成 - 给bean添加单独配置:类上添加
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
,只会对当前类生效,测试有效,也不会影响到其他类,推荐这个方式