本文源码基于SpringBoot 2.6.4
一直以来Spring-aop
对于实现了接口的Bean使用的是jdk的动态代理,对没有实现接口的Bean才使用Cglib代理。
但早在2019年3月看SpringBoot2.x的源码时发现,SpringBoot从2.0开始,SpringBoot将Spring-aop
默认设置成了Cglib动态代理实现。
难道是出于性能考虑?:实际上在jdk1.8中,jdk的动态代理并不比cglib动态代理性能差,甚至有可能优于cglib性能。
当时特地给SpringBoot官网提了issue:Why choose CGLIB proxying as the default after SpringBoot 2.0 ?
SpringBoot官网并没有在github上直接解答这个疑问。
而SpringBoot的核心成员Phil Webb
在stackoverflow上给出了答案!
Why choose CGLIB proxying as the default after SpringBoot 2.0
大概意思是说:基于jdk的动态代理特殊情况下可能导致ClassCastException
,使用jdk的动态代理可能无法通过原始的实现类来注入。
For example:
有个接口和一个实现类
public interface ChatMsgService {
}
@Service
public class ChatMsgServiceImpl implements ChatMsgService{
@Transactional
public int save(ChatMsg chatMsg) {
//...
}
}
当通过接口注入时,无论使用的哪种代理方式,都能正常注入。
@Autowired
private ChatMsgService chatMsgService;
当通过具体实现类注入时,如果使用的cglib代码也能正常注入
@Autowired
private ChatMsgServiceImpl chatMsgService;
但如果使用的是jdk的动态代理时则会抛出如下异常:
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'chatMsgServiceImpl' is expected to be of type 'com.bruce.integration.service.impl.ChatMsgServiceImpl' but was actually of type 'jdk.proxy2.$Proxy64'
at org.springframework.beans.factory.support.DefaultListableBeanFactory.checkBeanNotOfRequiredType(DefaultListableBeanFactory.java:1818)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1795)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1355)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:656)
... 74 more
为了避免这样的问题,SpringBoot2.0后默认将SpringAOP的动态默认设置成了cglib代码.
出现这个异常的原因其实也很简单:无论jdk还是cglib都是通过动态生成Class
的方式来实现。但是jdk生成的class是实现了接口,和接口的原先实现类没有直接关系,而cglib生成的class是继承了整个实现类,可以强制转化给实现类。
那如果在SpringBoot中想让Spring针对接口使用jdk的动态代理可以使用如下配置:
spring.aop.proxy-target-class=false
参见SpringBoot2.6.4中AopAutoConfiguration
的源码: