记bean增加Spring事务后导致注入bean报错的问题
场景:
为了实现一个具有多种功能的逻辑,我创建了一个接口(IWithdraw),以及它的实现类(VcpWithdrawImpl、DefaultWithdrawImpl)。
但对于VcpWithdrawImpl来说具有自己独特的功能,所以在IWithdraw上肯定是不会定义的,故在使用这个功能是只能通过VcpWithdrawImpl来调用,如vcpWithdrawImpl.getOrderId()。
1 public interface IWithdraw { 2 3 /** 4 * 提现申请 5 */ 6 void apply() ; 7 8 }
1 @Service 2 public class DefaultWithdrawImpl implements IWithdraw { 3 4 /** 5 * 提现申请 6 */ 7 @Override 8 public void apply() { 9 10 } 11 12 }
1 @Service 2 public class VcpWithdrawImpl implements IWithdraw { 3 4 /** 5 * 提现申请 6 */ 7 @Override 8 public void apply() { 9 10 } 11 12 /** 13 * 获取订单号 14 */ 15 @Transactional(rollbackOn = Exception.class) 16 public String getOrderId() { 17 18 } 19 20 }
准备工作:
1、创建调用类
1 @Service 2 public class VcpWithdrawFactory { 3 4 @Autowired 5 private DefaultWithdrawImpl defaultWithdraw; 6 @Autowired 7 private VcpWithdrawImpl vcpWithdraw; 8 9 /** 10 * 获取提现具体的处理类 11 */ 12 public IWithdraw getWithdrawHandler(WithdrawProcessModeEnum process) { 13 if (WithdrawProcessModeEnum.isProductCenter(process.getCode())) { 14 return vcpWithdraw; 15 } 16 return defaultWithdraw; 17 } 18 19 }
遇到的坑:
启动时报错:
1 Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private xxxx.VcpWithdrawImpl xxxx.VcpWithdrawFactory.vcpWithdraw; nested exception is java.lang.IllegalArgumentException: Can not set xxxx.VcpWithdrawImpl field xxxx.VcpWithdrawFactory.vcpWithdraw to com.sun.proxy.$Proxy297 2 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561) 3 at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) 4 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331) 5 ... 458 more 6 Caused by: java.lang.IllegalArgumentException: Can not set xxxx.VcpWithdrawImpl field xxxx.VcpWithdrawFactory.vcpWithdraw to com.sun.proxy.$Proxy297
原因:VcpWithdrawImpl中使用了事务来代理,导致Spring在注入bean时找不到VcpWithdrawImpl这个类型。
这是为什么呢,首先Spring的动态代理实现有2种方式,一种就是JDK的动态代理,另一种则是cglib。
而Spring选择动态代理实现的逻辑如下:
1 public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { 2 if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { 3 Class targetClass = config.getTargetClass(); 4 if (targetClass == null) { 5 throw new AopConfigException("TargetSource cannot determine target class: " + 6 "Either an interface or a target is required for proxy creation."); 7 } 8 if (targetClass.isInterface()) { 9 return new JdkDynamicAopProxy(config); 10 } 11 if (!cglibAvailable) { 12 throw new AopConfigException( 13 "Cannot proxy target class because CGLIB2 is not available. " + 14 "Add CGLIB to the class path or specify proxy interfaces."); 15 } 16 return CglibProxyFactory.createCglibProxy(config); 17 } 18 else { 19 return new JdkDynamicAopProxy(config); 20 } 21 }
代码说明:简单说就是如果一个类有接口,则默认使用JDK的动态代理来代理,如果直接是一个类,则使用cglib代理。JDK动态代理与cglib动态代理均是实现Spring AOP的基础。
参考:https://blog.csdn.net/qq_43012792/article/details/107777429
解决方案:
https://blog.csdn.net/qq_43012792/article/details/107777429博客中提供了2中解决方案:
1、使用接口来注入:
1 @Service 2 public class VcpWithdrawFactory { 3 4 @Autowired 5 @Qualifier("defaultWithdrawImpl") 6 private IWithdraw defaultWithdraw; 7 @Autowired 8 @Qualifier("vcpWithdrawImpl") 9 private IWithdraw vcpWithdraw; 10 11 /** 12 * 获取提现具体的处理类 13 */ 14 public IWithdraw getWithdrawHandler(WithdrawProcessModeEnum process) { 15 if (WithdrawProcessModeEnum.isProductCenter(process.getCode())) { 16 return vcpWithdraw; 17 } 18 return defaultWithdraw; 19 } 20 21 }
当然@Autowired、@Qualifier改成@Resource(name = "beanName")是一样的效果。
2、强制开启使用 cglib的方式,在底层使用子类继承的方式去创建动态代理对象,此时用 @Autowired 、@Resource都是可以的。
因为我这边维护的项目比较老,且又有必须要用VcpWithdrawImpl自有函数的必要,所以以上两种方式均不适用,建议只针对某些类来使用cglib。
1 @Service 2 @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) 3 public class VcpWithdrawImpl implements IWithdraw { 4 5 /** 6 * 提现申请 7 */ 8 @Override 9 public void apply() { 10 11 } 12 13 /** 14 * 获取订单号 15 */ 16 @Transactional(rollbackOn = Exception.class) 17 public String getOrderId() { 18 19 } 20 21 }
也就是加上@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)即可。