记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)即可。

posted @ 2020-11-19 14:58  被猪附身的人  阅读(421)  评论(0编辑  收藏  举报