当前位置:首页 » 新媒体运营 » 正文

做代理怎么做,浅入浅出的代理模式与Spring事务管理

7028 人参与  2022年11月05日 15:45  分类 : 新媒体运营  评论

前言

最近在开发业务代码的时候,犯了一个事务注解的错误:在同一个类的非事务方法中调用了另一个事务方法,导致事务没有生效,如下所示:

public ConfirmOrderResultVO batchConfirmPurchaseOrders(Long taobaoUserId, List<String> bizOrderIds) throws TCException {

        ………………………………………………………………        for (String bizOrderId : bizOrderIds) {            // 推单成功进入successList,否则进入failedList            if (confirmPurchaseOrder(taobaoUserId, bizOrderId)){                successList.add(Long.valueOf(bizOrderId));

            }else {                failedList.add(Long.valueOf(bizOrderId));

            }

        }

        ………………………………………………………………

    }

其中confirmPurchaseOrder()是一个事务方法:

@Transactionalpublic Boolean confirmPurchaseOrder(Long taobaoUserId, String bizOrderId) throws TCException {

      logger.warn("|ConfirmPurchaseOrder|UserId:"+taobaoUserId+",orderId:"+bizOrderId);

      …………………………      return ture;

}

这样在直接调用
batchConfirmPurchaseOrders()方法时,如果confirmPurchaseOrder()方法发生了异常,是不会回滚的。原理在于Spring的事务管理依靠的动态代理模式,当在同一个类中调用一个非事务方法,是不会生成代理对象的,自然也不会触发事务。借此机会回顾一下代理模式和Spring事务管理的原理。

代理模式

网上讲解代理模式的文章千奇百怪,很多时候看完了也不明白讲的重点是什么。

事实上在生活中我们常常会遇到各种各样的代理模式,例如火车票代售点代理出售火车票,他在“帮忙”出售火车票的同时,收取了额外的手续费,记录了自己店里的流水等。

做代理怎么做,浅入浅出的代理模式与Spring事务管理-百度竞价优化_微商推广_今日头条自媒体_新媒体运营_剑谦网络


又比如班长代理老师来上交班费,他在“上交班费”的动作之前,还进行了检查班级同学是否到齐、向每一位同学收班费、核对班费金额,最后再上交班费。



做代理怎么做,浅入浅出的代理模式与Spring事务管理-百度竞价优化_微商推广_今日头条自媒体_新媒体运营_剑谦网络




总而言之,代理模式就是 代理其他对象,在完成原动作的基础上(前后)做一些额外的自定义的工作 。

聪明的朋友看到这里一定想到了:那我在一个方法中调用另一个方法不就是代理模式了吗?啊对对对,从功能上来讲是一样的,但是“模式”之所以为“模式”,以我拙见,其本质目的在于形成规范,以减少重复代码的编写。因此如果不能达到减少代码重复的本质目的,便不能称之为“模式”。

按照代理模式的实现方式,分为两种:静态代理和动态代理。那我们就拿刚刚说过的“写作业”这件事来做例子,讲解一下两种实现方式的区别。

▐静态代理

首先定义一个“作业”接口,里面有一个方法“做作业”

public interface Homework {    void doHomework();

}

小红实现了这个接口。但是小红是一个不爱学习的小学生,又因为师从马掌门成为了学校的扛把子,所以为了不让老师发现,他决定自己随便做做,把剩下的部分交给小弟们来做。

public class XiaoHong implements Homework{    @Override    public void doHomework() {

        System.out.println("XiaoHong did homework casually");

    }

}

其中小王、小张两个小弟成绩优异,一个负责数学作业,一个负责英语作业,于是他们两个自告奋勇实现了Homework接口,在代理完成作业的基础上,还不断学习提高自己的能力,并且对作业答案进行了校验。

小王:

public class XiaoWang implements Homework{    // 持有Homework属性    private Homework homework;    // 通过构造函数初始化Homework    public XiaoWang(Homework homework){        this.homework=homework;

    }    //代理实现

    @Override    public void doHomework() {

        doStudy();

        homework.doHomework();

        System.out.println("XiaoWang helps with MathHomework");

        doCheck();

    }    // 额外方法     private void doCheck() {

        System.out.println("XiaoWang is checking-----------------");

    }    // 额外方法     private void doStudy() {

        System.out.println("XiaoWang is studying---------------");

    }

}

小张:

public class XiaoZhang implements Homework{    

    // 持有Homework属性    private Homework homework;    // 通过构造函数初始化Homework    public XiaoZhang(Homework homework){        this.homework=homework;

    }    //代理实现

    @Override    public void doHomework() {

        doStudy();

        homework.doHomework();

        System.out.println("XiaoZhang helps with EngHomework");

        doCheck();

    }    // 额外方法    private void doCheck() {

        System.out.println("XiaoZhang is checking-----------------");

    }    // 额外方法    private void doStudy() {

        System.out.println("XiaoZhang is studying---------------");

    }

}

于是,小红可以放心地把作业交给小王和小张了:

 public static void main(String[] args) {        // 实例化一个目标对象

        XiaoHong xiaoHong = new XiaoHong();        // 把目标对象通过构造函数传递给代理对象

        XiaoZhang xiaoZhang =new XiaoZhang(xiaoHong);

        XiaoWang xiaoWang =new XiaoWang(xiaoHong);        // 调用代理对象的方法

        xiaoZhang.doHomework();

        xiaoWang.doHomework();

    }

输出:

XiaoZhang is studying---------------XiaoHong did homework casually

XiaoZhang helps with EngHomework

XiaoZhang is checking-----------------

  XiaoWang is studying---------------XiaoHong did homework casually

XiaoWang helps with MathHomework

XiaoWang is checking-----------------

问题来了,如果老师又布置了一个Book接口和readBook方法,但是readBook前后的动作是一样的(都需要study和check),如何通过代理来实现呢?

一个方案是让小王和小张都实现Book接口和readBook方法,并持有新的Book对象;另一个方案是再找一个小赵实现Book接口和readBook方法。

这样两种方案实际上都能达到效果,但是如果接口和方法增多起来呢?如果让一个代理类实现一堆方法,并持有一堆不同的对象,这个类势必会变得臃肿不堪;如果每一个新的方法都创建一个新的代码类,引用不同的对象,又会造成代码的大量重复。因此,动态代理就出现了。

▐动态代理

前文所讲的静态代理之所以为“静态”,是因为每个接口的每个方法,我们都要显式地去实现、创建、调用,所有的操作都是写死的、是静态的。而动态代理的巧妙之处在于,可以在程序的运行期间,动态的生成不同的代理类,来完成相同的工作。也就是说,如果你想实现一个方法,在所有其他方法执行前后做一些额外的相同的动作,例如打印日志、记录方法执行时间等,你就需要动态代理了(是不是想到了什么?)。

事实上所有的动态代理思想都是一致的,而目前最常用的动态代理实现有两种:JDK原生实现和Cglib开源实现。

Spring在.X之前默认的动态代理实现一直是JDK动态代理。但是从.X开始,Spring就开始默认使用Cglib来作为动态代理实现。并且SpringBoot从.X开始也转向了Cglib动态代理实现。

  • JDK实现动态代理

现在有一个牛X的机器人代理,可以帮所有人在完成老师布置的任何任务前后,进行学习和答案的核对,在JDK的动态代理实现中,他是这样编写的:

// 实现InvocationHandlerpublic class RobotProxy implements InvocationHandler {    // 持有一个Object类型的目标对象target    private Object target;  

    // 通过构造函数实例化目标对象target    public RobotProxy(Object target) {        this.target = target;

    }  

    // 重写invoke方法    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        doStudy();        // 执行目标对象的方法

        Object invoke = method.invoke(target, args);

        doCheck();        return invoke;

    }    

    // 额外动作    private void doStudy() {

        System.out.println("Robot is studying------------");

    }    // 额外动作    private void doCheck() {

        System.out.println("Robot is checking------------");

    }

}

然后这样调用机器人代理:

public static void main(String[] args) {  // 实例化一个目标对象

  XiaoHong xiaoHong = new XiaoHong();  // 传入实现的接口 new Class[]{Homework.class}  // 以及代理类 new RobotProxy(xiaoHong)

  Homework homework = (Homework)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Homework.class}, new RobotProxy(xiaoHong));

  homework.doHomework();

}

输出:

Robot is studying------------XiaoHong did homework casuallyRobot is checking------------

可以看到,JDK实现动态代理必须要依赖接口,只有实现了接口的目标类才可以被代理,而Cglib动态代理就可以消除接口的限制。

  • Cglib实现动态代理

创建一个升级版的机器人RovotV代理类:

// 实现MethodInterceptor方法(要引入cglib包)public class RobotVProxy implements MethodInterceptor {  

    // 重写intercept方法    @Override    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        doStudy();

        Object invoke = methodProxy.invokeSuper(o, objects);

        doCheck();        return invoke;

    }    // 额外动作    private void doStudy() {

        System.out.println("RobotV is studying------------");

    }    // 额外动作    private void doCheck() {

        System.out.println("RobotV is checking------------");

    }

}

调用方式:

public static void main(String[] args) {    // 创建增强类

    Enhancer enhancer = new Enhancer();    // 设置父类

    enhancer.setSuperclass(XiaoHong.class);    // 设置回调

    enhancer.setCallback(new RobotVProxy());    // 创建目标对象

    XiaoHong xiaoHong = (XiaoHong)enhancer.create();    // 调用目标方法

    xiaoHong.doHomework();

}

输出:

RobotV is studying------------XiaoHong did homework casuallyRobotV is checking------------

Cglib动态代理相比于JDK代理实现有几个优势:

  1. 基于字节码,生成真实对象的子类。

  2. 运行效率高于JDK代理

  3. 不需要实现接口

可以看到,动态代理只需要一个代理类,就可以代理所有需要同一种处理(如上文所述的study()、check())的类和方法,这就是动态代理与静态代理最大的区别。

Spring事务管理

OK,了解到了代理模式的好处之后,就可以自然而然的想到Spring事务管理的基本原理了。无非是借助动态代理,做一些额外的操作:在真正的方法执行之前开启事务,在方法顺利执行完成之后提交事务,如果方法执行过程中发生异常,要执行回滚操作。

一般有两种方法使用Spring的事务管理,一种是利用注解,一种是手动编程。本文因为使用的是注解型的事务管理,因此只对注解型事务的使用和原理进行探究,以找到事务没有生效的原因。

▐注解型事务
  • 注解型事务使用

注解型事务的使用非常简单,只需要在需要事务管理的类或方法上加上@Transactional(rollbackFor = Exception.class),其中rollbackFor指定的是遇到什么情况下执行回滚。

当然这个注解还有传播级别、隔离级别等我们背过的八股文属性,就不一一展开了。

本文链接:https://www.woshiqian.com/post/159683.html

百度分享获取地址:https://share.baidu.com/code
一般做代理是怎么代理  

我是钱微信/QQ:5087088

广告位、广告合作QQ:5087088

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

       

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。