Spring事务自调用失效原因和Spring AOP原理

前言


非事务方法的调用对象是代理对象,但是spring 事务机制只查看带有@Transcational注解的方法,所有非事务方法不会以事务方法执行;事务方法的调用对象是原对象,是不会触发通知(事务)的。

Spring事务自调用失效

@Transcational在某些场景下会失效,在实际生产中,这是个要注意的问题。一个类方法A调用该类的方法B,当用代理类执行方法A时,方法A是由代理对象反射完成的,而方法B并不是反射执行的。下面是一个Spring 事务的例子,

public class TestService {
    public int insertUsers(List<User> userList) {
        System.out.println("insertUsers ...");
        int count = 0;
        for (User user: userList) {
            count += insertUser(user);
        }
        return count;
    }
    @Transactional(isolation = Isolation.READ_COMMITED,
        propagation = Propogation.REQUIRES_NEW)
    public int insertUser(User user) {
        System.out.println("insertUser ...");
        return 1;
    }
}

非事务方法insertUsers中调用的自身类的事务方法insertUser方法,

  • 如果执行TestService.insertUser方法,insertUser方法体会走执行spring 事务。
  • 如果执行TestService.insertUsers方法,insertUser方法体不会执行spring事务。
    原因是,spring会扫描所有添加了@Transcational注解的方法,添加了@Transcational的类的对象都会生成一个代理对象,spring事务会判断代理类执行的方法是否是添加了@Transcational注解的,而非事务方法调用事务方法属于自调用,自调用方法并不是由代理通过反射来执行的,这里来看Spring aop的原理了。

Spring AOP原理

spring aop主要是通过代理类和反射来实现的,主要两个方式:JDK代理和cglib代理。区别在于代理类是否实现了接口,实现了接口一般用JDK实现,没有实现接口一般用cglib实现,这里主要介绍JDK代理原理。

(1)首先定义一个接口类HelloService

public interface HelloService {
    void sayHello(String name);
    void say(String word);
}

(2)定义一个接口实现类HelloServiceImp,也就是我们要代理的类

public class HelloServiceImp implements HelloService {

    @Override
    public void sayHello(String name) {
        System.out.println("hello " + name);
    }


    @Override
    public void say(String word) {
        System.out.println(word);
    }
}

(3)定义Interceptor接口

按照spring aop的切面来定义,分为五种

  • before:前置通知
  • after:后置通知
  • around:环绕通知
  • afterReturn:正常返回通知
  • afterThrow:异常返回通知
public interface Interceptor {
    boolean before();
    void after();
    Object around(Invocation invocation)
        throws InvocationTargetException, IllegalAccessException;
    void afterReturning();
    void afterThrowing();
}

(4)定义Interceptor实现类

Invocation类是反射执行方法,定义见(5)Invocation

public class MyInterceptor implements Interceptor {

    @Override
    public boolean before() {
        System.out.println("before ...");
        return true;
    }
    @Override
    public void after() {
        System.out.println("after ...");
    }

    /**
     * 
     * @param invocation 反射执行方法,定义见下Invocation类
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @Override
    public Object around(Invocation invocation)
        throws InvocationTargetException, IllegalAccessException {
        System.out.println("around before ...");
        Object obj = invocation.proceed();//反射执行方法
        System.out.println("around after ...");
        return obj;
    }
    @Override
    public void afterReturning() {
        System.out.println("afterReturning ...");
    }
    @Override
    public void afterThrowing() {
        System.out.println("afterThrowing ...");
    }
}

(5)定义Invocation,用于反射执行方法

public class Invocation {
    private Object[] params;//代理方法参数
    private Method method;//代理方法
    private Object target;//代理对象
    public Invocation(Object target, Method method, Object[] params) {
        this.target = target;
        this.method = method;
        this.params = params;
    }

    public Object proceed() 
        throws InvocationTargetException, IllegalAccessException {
        //反射执行方法
        return method.invoke(target, params);
    }
}

(4)代理类ProxyBean

代理类的实现主要两个jdk类:Proxy类和InvacationHandler接口
先通过InvocationHandler接口的invoke方法来定义(反射方法执行和各种aop通知)
再通过Proxy.newProxyInstance方法传入InvocationHandler对象获取代理对象。这样代理对象就会执行被代理对象的方法,同时还能在代理时发出aop(5种)通知。

public class ProxyBean implements InvocationHandler {
    private Object target = null;
    private Interceptor interceptor = null;
    public static Object getProxyBean(Object target, Interceptor interceptor) {
        ProxyBean proxyBean = new ProxyBean();
        proxyBean.target = target;
        proxyBean.interceptor = interceptor;
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
            target.getClass().getInterfaces(), proxyBean);
        return proxy;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
        throws Throwable {
        System.out.println("通过代理对象执行方法:" + method.getName());
        boolean exceptionFlag = false;
        Invocation invocation = new Invocation(target, method, args);
        Object retObj = null;
        try {
            if (this.interceptor.before()) {
                retObj = this.interceptor.around(invocation);
            } else {
                retObj = method.invoke(target, args);
            }
        } catch (Exception e) {
            exceptionFlag = true;
        }
        this.interceptor.after();
        if (exceptionFlag) {
            this.interceptor.afterThrowing();
        } else {
            this.interceptor.afterReturning();
            return retObj;//正常返回
        }
        return null;//异常返回
    }
}

(4)测试

public class Test {
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImp();
        HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, 
            new MyInterceptor());
        proxy.sayHello("Hinson");
    }
}

结果:

通过代理对象执行方法:sayHello
before ...
around before ...
hello Hinson
around after ...
after ...
afterReturning ...

这就是spring aop的原理,如果在HelloServiceImp中的sayHello方法调用say方法,那么say方法是由代理对象来执行的,还是由原对象执行的呢?
HelloServiceImp修改如下

public class HelloServiceImp implements HelloService {
    @Override
    public void sayHello(String name) {
//        System.out.println("hello " + name);
        say("hello " + name);
    }
    @Override
    public void say(String word) {
        System.out.println(word);
    }
}

执行结果:

通过代理对象执行方法:sayHello
before ...
around before ...
hello Hinson
around after ...
after ...
afterReturning ...

自调用方法,调用类是原对象,而aop中只有调用类是代理对象时,才能出发各种通知,spring 事务就是基于通知来实现的。所以spring非事务方法调用事务方法过程中,非事务方法的调用对象是代理对象,但是spring 事务机制只查看带有@Transcational注解的方法,所有非事务方法不会以事务方法执行;事务方法的调用对象是原对象,是不会触发通知(事务)的。

其他


本人也是在慢慢学习中,如有错误还请原谅、敬请指出,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 12,287评论 6 86
  • 感恩伟大的格西老师将2500年前的金刚智慧落地教授,感恩金华老师赠我爱种子这本书,让我有幸学习到里面的智慧,感恩祺...
    骞卉阅读 160评论 0 2
  • 我是娇娇,我的人生愿景是带动六万家长和孩子一起爱上阅读!陪伴孩子阅读,每天给到孩子他需要的心里营养! 九月阅读复盘...
    李娇青创客阅读 337评论 0 1
  • 孔夫子有时候以偏见看人很明显。 微生高家里没醋,他去邻居家淘了点给对方,有两点可能,第一是可能家里真没醋,但不好意...
    海水蓝阅读 218评论 0 1