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注解的方法,所有非事务方法不会以事务方法执行;事务方法的调用对象是原对象,是不会触发通知(事务)的。

其他


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

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

友情链接更多精彩内容