同类中嵌套AOP--注解事物在同一类中嵌套调用不生效

 

  一、背景

   spring的注解事物没有生效,异常数据没有回滚。

  二、具体现象

   同一个类中有多个方法,A方法没有开启事物,B方法通过注解开启事物,B方法的事物注解没有生效。代码如下:

   

package com.test.transcation;

import org.springframework.transaction.annotation.Transactional;

/**
 * Created by shaobo on 2018/4/9.
 */
public class Insert {

    public void a(){
        this.b();
    }

    @Transactional
    public void b(){
        /**
         * 一通数据库操作
         */
        throw new RuntimeException();
    }
}

    执行方法a(),方法b()中的数据成功更新到了数据库中,预期结果为数据回滚。

   三、分析

    我们知道spring的事物是通过cglib来生成动态代理的。先来看JDK的动态代理。

    

package com.test.proxy;

/**
 * 接口
 */
public interface UserInterface {
     void update();
     void complex();
}

package com.test.proxy;

/**
 * 实现
 */
public class UserService implements UserInterface {
    @Override
    public void update() {
        System.out.println("userDao.update()");
    }

    @Override
    public void complex(){
        System.out.println("begin complex()");
        this.update();
        System.out.println("end complex()");
    }
}

package com.test.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * InvocationHandler
 */
public class JDKProxy implements InvocationHandler {

    private Object target;


    public  void bind(UserInterface userInterface){
        target = userInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before Method Invoke " + method.getName());
        Object object = method.invoke(target,args);
        System.out.println("After Method Invoke " + method.getName());
        return object;
    }
}

package com.test.proxy;

import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        UserService userService = new UserService();
        JDKProxy jdkProxy = new JDKProxy();
        jdkProxy.bind(userService);
        UserInterface userInterface =  (UserInterface)Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),jdkProxy);
        userInterface.complex();
    }
}

 执行结果:我们通过debug方式执行关键一下被代理对象

 


 

 

 


    我们可以看到this对象为实际对象,所以update方法并没有被拦截。

 

    接下来我们看一下cglib,

package com.test.cglib;

/**
 * 代理对象
 */
public class UserDao {
    public void update() {
        System.out.println("userDao.update()");
    }

    public void complex() {
        System.out.println("begin complex()");
        this.update();
        System.out.println("end complex()");
    }
}

package com.test.cglib;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 代理类
 */
public class DaoProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Before Method Invoke " + method.getName());
        methodProxy.invokeSuper(o, objects);
        System.out.println("After Method Invoke " + method.getName());
        return o;
    }
}

package com.test.cglib;

import net.sf.cglib.proxy.Enhancer;

public class Test {
    public static void main(String[] args) {
        DaoProxy daoProxy = new DaoProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserDao.class);
        enhancer.setCallback(daoProxy);
        UserDao dao = (UserDao)enhancer.create();
        dao.complex();
    }
}

    执行结果如下

 


 

 

    我们可以发现cglib中的this指向代理对象,所以也会执行拦截方法。

    spring aop的模型大致是这样的:

 

    这样会导致methodB()并不能被通知到。我想如果如下图这样的话就不会出现这种问题,但spring这样这样设计肯定有其理由,需要后续继续研究。

    

    四、解决办法

    知道了原因就好解决了,方法有如下两种。

    1、不要使用spring 中嵌套aop,将这种嵌套放在两个类中(推荐)。

    2、((UserInterface)AopContext.currentProxy()).update(),通过此方法获得代理对象直接调用。

    踩过的坑都是流过的泪。

  

posted @ 2018-04-09 17:43  vi-2525  阅读(3375)  评论(0编辑  收藏  举报