zoukankan      html  css  js  c++  java
  • 记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)即可。

  • 相关阅读:
    《活着》--余华
    《麦田里的守望者》--[美]杰罗姆·大卫·塞林格
    《平凡的世界》--路遥
    彩色照片转换为黑白照片(Color image converted to black and white picture)
    《戴尔·卡耐基传记》--[美]戴尔·卡耐基
    Maven的第一个小程序
    C# RabbitMQ优先级队列实战项目演练
    控制WinForm中Tab键的跳转
    C#模板引擎NVelocity实战项目演练
    C#隐藏手机号中间四位为*
  • 原文地址:https://www.cnblogs.com/bzfsdr/p/14001334.html
Copyright © 2011-2022 走看看