同類中嵌套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(),通過此方法獲得代理對象直接調用。

    踩過的坑都是流過的淚。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM