spring aop内部嵌套调用

背景

论起名的重要性我们交代了一些问题

但是貌似没有小伙伴给我挑刺啊~ 事实上出错的方法是这样的

public TmPrintCount getPrintCountIfNull(TmPrintCount printCount) {    String idOwnOrg = WxbStatic.getOrg();
    TmPrintCount tmPrintCount = this.getPrintCountByBillId(printCount);
    if (null == tmPrintCount){
        printCount.setPkId(baseService.getUUid());
        printCount.setPrintCount(1);
        printCount.setIdOwnOrg(idOwnOrg);
        this.addPrintCount(printCount);
        return printCount;
    }
    return tmPrintCount;
}

有没有小伙伴发现其实调用了addprintCount方法呢???按照道理来说这边不应该是Required么?

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="getBillNo" read-only="false" propagation="REQUIRES_NEW"
                   timeout="20"/>
        <tx:method name="addBranch" read-only="false" rollback-for="java.lang.Exception"
                   timeout="100"/>
        <tx:method name="load*" read-only="true" propagation="SUPPORTS"
                   timeout="20"/>
        <tx:method name="find*" read-only="true" propagation="SUPPORTS"
                   timeout="20"/>
        <tx:method name="get*" read-only="true" propagation="SUPPORTS"
                   timeout="10"/>
        <tx:method name="select*" read-only="true" propagation="SUPPORTS"
                   timeout="10"/>
        <tx:method name="query*" read-only="true" propagation="SUPPORTS"
                   timeout="10"/>
        <tx:method name="is*" read-only="true" propagation="SUPPORTS"
                   timeout="5"/>
        <tx:method name="list*" read-only="true" propagation="SUPPORTS"
                   timeout="50"/>
        <tx:method name="loginCheck" read-only="true" propagation="SUPPORTS"
                   timeout="10"/>
        <tx:method name="selectDiff*" read-only="true" propagation="SUPPORTS"
                   timeout="120"/>
        <tx:method name="importBatch*" propagation="REQUIRED" rollback-for="java.lang.Exception"
                   timeout="180"/>
        <tx:method name="fastImport*" propagation="REQUIRED" rollback-for="java.lang.Exception"
                   timeout="180"/>
        <tx:method name="createMonitor*" propagation="REQUIRED" rollback-for="java.lang.Exception"
                   timeout="500"/>
        <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception"
                   timeout="50"/>
    </tx:attributes>

如果这边都OK的话那岂不是美滋滋~之前的问题不就没有了么???论起名的重要性

分析

带着这个疑问我们来看一下Spring是如何实现事务的~

事实上Spring是通过代理来实现很多业务的增强的

Proxy

动态代理实现的方式其实很简单 可以理解成如下

public class UserImpl implements Iuser {
  @Override
  public void eat(String s) {
    System.out.println("我要吃"+s);
  }
}
public class UserProxy implements Iuser {
  private Iuser user = new UserImpl();
  @Override
  public void eat(String s) {
    System.out.println("代理前置内容");
    user.eat(s);
    System.out.println("代理后置内容");
  }
}

从这边可以看到 事实上在userProxy中调用的eat最终是要调用到UserImpl的eat的方法===》关键在于此 这个时候的this是UserImpl 

从上边可以看出事务是同样的道理如果是写this.XXXX那么这边的this指的是UserImpl【所以才不会变成UserProxy】而真正的事务编织是在Aop对象中 因此事务自然不能通过嵌套调用来传递

CGLIB

爱学习的小伙伴们肯定想到了 我们常说java proxy是基于接口的 那么cglib是可以基于普通class的【cglib多么优秀】

从Java的学习中我们通常知道根据多态理论是完全可以调用到子类的实现方法的【也就是代理对象】那么这边是不是说在Spring中如果我们选择cglib那样就可以实现内部事务的嵌套调用呢?

从本质上来说这么讲是没有错的。但是呢,Spring aop并非使用的和我们想象中一样的套路~

我们想象中是这样

public class UserImpl implements Iuser {
  @Override
  public void eat(String s) {
    System.out.println("我要吃"+s);
       drink("水");
  }
    @Override
  public void drink(String s) {
    System.out.println("我要喝"+s);
  }
}
public class UserProxy implements Iuser {
  private Iuser user = new UserImpl();
  @Override
  public void eat(String s) {
    System.out.println("代理前置内容");
       System.out.println("我要吃"+s);
       drink("水");
       System.out.println("代理后置内容");
  }
    @Override
  public void drink(String s) {
       System.out.println("代理前置内容");
    System.out.println("我要喝"+s);
        System.out.println("代理后置内容");
  }
}

那么如果在调用eat的场景下同时也能调用到drink的代理===》打印出 代理前置内容

但是实质上在Spring中并不能够调用到内部嵌套的

看源码 

Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
 
Object var15;
try {
    if (this.advised.exposeProxy) {
        oldProxy = AopContext.setCurrentProxy(proxy);
        setProxyContext = true;
    }
 
    target = this.getTarget();
    if (target != null) {
        targetClass = target.getClass();
    }
 
    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
    Object retVal;
    if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
        Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
        retVal = methodProxy.invoke(target, argsToUse);
    } else {
        retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
    }
 
    retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
    var15 = retVal;
} finally {
    if (target != null) {
        this.releaseTarget(target);
    }
 
    if (setProxyContext) {
        AopContext.setCurrentProxy(oldProxy);
    }
 
}
 
return var15;

可以看到

target = this.getTarget();

事实上调用的时候都是取得target===》也就是上文中的UserImpl

那么其实和jdk proxy的原理一样了 因为最终调用的地方都是实际的target===》也就是被代理对象

private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
 
   private final MethodProxy methodProxy;
 
   private boolean protectedMethod;
 
   public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments,
         Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {
      super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
      this.methodProxy = methodProxy;
      this.protectedMethod = Modifier.isProtected(method.getModifiers());
   }
 
   /**
    * Gives a marginal performance improvement versus using reflection to
    * invoke the target when invoking public methods.
    */
   @Override
   protected Object invokeJoinpoint() throws Throwable {
      if (this.protectedMethod) {
         return super.invokeJoinpoint();
      }
      else {
         return this.methodProxy.invoke(this.target, this.arguments);
      }
   }
}

可以看到当方法是protected的时候调用

/**
 * Invoke the joinpoint using reflection.
 * Subclasses can override this to use custom invocation.
 * @return the return value of the joinpoint
 * @throws Throwable if invoking the joinpoint resulted in an exception
 */
protected Object invokeJoinpoint() throws Throwable {
   return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}

否则调用

return this.methodProxy.invoke(this.target, this.arguments);

有没有小伙伴考虑这个没有private调用么?

破案

正如上面分析 在getPrintCountIfNull方法中调用addPrintCount方法那么此时将无法执行aop切面 因此自然不能获得新的事务

解决方案

对于上述问题 显而易见的是不能使用this了 那么有几个简单的方案

  1. 规避此类问题 调用其他类里面的方法【大部分应当要规避】
  2. 使用AopProxy

    AopContext.currentProxy()
  3. 该方法需要开启expose-proxy="true"

  4. 通过某些方式 http://fyting.iteye.com/blog/109236

  5. 在Spring4.3中正式推出了self inject (以前不能注入自身 否则会循环依赖) 由于特殊对待 因此不能通过@Resource注入必须通过@Autowired  https://github.com/spring-projects/spring-framework/commit/4a0fa69ce469cae2e8c8a1a45f0b43f74a74481d
    比如 

    /**
     * Created by qixiaobo on 2018-02-01.
     */
    @Service
    @Transactional(rollbackFor = Exception.class, timeout = 1)
    public class TsInsuranceAttachmentServiceImpl extends AbstractService<TsInsuranceAttachment, TsInsuranceAttachmentVo, TsInsuranceAttachmentSo, Integer> implements TsInsuranceAttachmentService {
        @Resource
        private TsInsuranceAttachmentMapper tsInsuranceAttachmentMapper;
     
        @Resource
        private TsInsuranceAttachmentService self;
    }

其他问题

  1. cglib能否替代java proxy呢?
  2. 我们使用Spring aop也引入了一坨aspectj的jar 那么我们是否可以使用aspectj呢?我们一直在说cglib和动态proxy,如果没有使用aspectj是否可以去除这些jar,如果使用aspectj你是否在生产使用过aspectj呢?

猜你喜欢

转载自my.oschina.net/qixiaobo025/blog/1629705