聊聊我是如何在面试别人Spring事务时“套路”对方的

戳蓝字“CSDN云计算”关注我们哦!

640?wx_fmt=jpeg


“中国最好面试官”


我希望把面试当作是一次交流,像朋友那样,而不是像一场Q & A。但也有人觉得,我对应聘者“太好了”,这完全没必要,反正最后他也不会来。

好吧,那这次我就“使点坏”,“套路”一下面试者。

记一次“带套路”的面试


与这个面试者聊了一会儿,咦,发现他水平还可以,我内心有点儿喜出望外,终于遇到一个“合格”的“陪聊者”了,我要用Spring事务“好好套路”他一下。

我:你在开发中,一般都把事务加到哪一层?

他:都加到Service层。

我:现在基本都是基于注解的配置了,那和事务相关的注解是哪个?

他:我不太会读那个单词,就是以@T开头的那个。

我:我明白你的意思,就是@Transactional。

他:是的。

我:与自己写代码来开启和提交事务相比,(先给他来个小的套路),这种通过注解来使用事务的方式叫什么?

他:(犹豫了两、三秒),不知道。

我:如果把写代码那种叫编程式事务,那与之相对的应该是什么式事务?

他:哦,声明式事务。

我:(先铺垫),不加注解,没有事务,加上注解,就有事务,可见事务和注解有莫大的关系。(开始套路),那加上注解后,到底发生了什么变化呢,就有了事务?

他:(犹豫了几秒钟),不知道。

我:(哈哈,意料之中),那我换一问法,Spring声明式事务的底层是怎么实现的?

他:是通过代理实现的。

我:(铺垫),代理这个词不仅计算机里有,现实生活中也经常见到代理,比如招华北地区总代理等等。(套路),那你能不能在生活中举一个代理的例子?

他:(想了一会儿),我没有想到什么好例子。

我:(开始聊会天),我看你老家离这还挺远的,你一般都什么时候回去啊?

他:一般都国庆节或春节会回去。其它时间假期短就不回去了。

我:(引子),国庆节和春节,人都很多啊,票不好买吧?

他:是啊,都在网上抢高铁票,不停地刷。

我:(引子),现在有了高铁,出行确实方便了很多。那你知道以前没有高铁、没有12306的时候,人们都是怎么买票的吗?

他:我虽然没有经历过,但是我知道。那时候春运,都在火车站售票大厅买票,人们排很长的队,有时需要等半天,还不一定有票。

我:(切入正题),除了火车站售票大厅外,你有没有见过在城市里分布的一些火车票代售点?

他:现在偶尔还能见到几个,但都已经关门了。

我:是啊,现在都网上买票了,代售点算是被历史抛弃了。(开始套路),那你觉得代售点算不算火车站售票大厅的代理呢?

他:火车站售票大厅可以买票,代售点也可以买票,应该算是代理吧。

我:从广义讲算是代理。但有两点需要注意:

一是,代售点卖的也是售票大厅的票,它自己是没有票的,它只是行使售票大厅的权利。

二是,它可以有属于自己的行为特征,比如不需要排队啊,每张硬座票收5元手续费啊等等。

我们平时听到的中间商/代理商,其实都差不多是一回事儿。

他:经你这么一说,我明白了。

我:那我们再说回到Spring中的代理,在Spring中生成代理的方式有几种?

他:两种,JDK动态代理和CGLIB。

我:那它们分别用于什么情况下?

他:JDK动态代理只能用于带接口的,CGLIB则带不带接口都行。

我:(铺垫),假如有个接口,它包含两个方法a和b,然后有一个类实现了该接口。在该实现类里在a上标上事务注解、b上不标,此时事务是怎样的?

他:a标注解了,肯定有事务,b没有注解,所以没有事务。

我:嗯,是这样的。(开始套路),现在来做个简单的修改,在方法b里调用方法a,其它保持不变,此时再调用方法b,会有事务吗?

他:应该有吧,虽然b没有注解,但a有啊。

我:(我需要带带他),假设现在你和我都不知道有没有事务,那我们来分析分析,看能不能找出答案。你有分析思路吗?

他:没有。

我:行吧,那我们开始。这是一个带接口的,那就假定使用JDK动态代理吧。从宏观上看,就是Spring使用JDK动态代理为这个类生成了一个代理,并为标有注解的方法添加了和事务相关的代码,所以就具有了事务。那你知道这个代理大概会是什么样子的吗?

他:这个不知道。

我:通过代售点的例子我们应该知道,所有的代理都具有以下特点:

代理是一个空壳,它背后才是真正的老板。

代理可以行使老板的权力,所以它看起来“很像”老板,除非仔细查看,否则不易区分。

代理自己可以按需加进去一些行为特征,除非仔细查看,否则老板都不一定知道这些。

那我们回到程序世界,使用接口和类再套一下上面的特点:

代理类是一个空壳(或外观),它背后才是真正的类,通常称为目标类。由此得出代理类要包含目标类。

对目标类和代理类的使用方式是一样的,甚至你都不知道它是代理类。由此得出代理类和目标类的类型要兼容,对外接口一致。所以目标类实现的接口,代理类也要实现。

代理类在把执行流程代理给目标类的过程中,可以添加一些行为代码,如开启事务、提交事务等。

他:经你这么一分析啊,我知道该怎么写代码了,应该是这样的,请仔细看下代码,虽然很简单:

//接口
interface Service {
    void doNeedTx();

    void doNotneedTx();
}

//目标类,实现接口
class ServiceImpl implements Service {

    @Transactional
    @Override
    public void doNeedTx() {
        System.out.println("execute doNeedTx in ServiceImpl");
    }

    //no annotation here
    @Override
    public void doNotneedTx() {
        this.doNeedTx();
    }
}

//代理类,也要实现相同的接口
class ProxyByJdkDynamic implements Service {

    //包含目标对象
    private Service target;

    public ProxyByJdkDynamic(Service target) {
        this.target = target;
    }

    //目标类中此方法带注解,进行特殊处理
    @Override
    public void doNeedTx() {
        //开启事务
        System.out.println("-> create Tx here in Proxy");
        //调用目标对象的方法,该方法已在事务中了
        target.doNeedTx();
        //提交事务
        System.out.println("<- commit Tx here in Proxy");
    }

    //目标类中此方法没有注解,只做简单的调用
    @Override
    public void doNotneedTx() {
        //直接调用目标对象方法
        target.doNotneedTx();
    }
}


我:目标类是我们自己写的,肯定是没有事务的。代理类是系统生成的,对带注解的方法进行事务增强,没有注解的方法原样调用,所以事务是代理类加上去的。

那回到一开始的问题,我们调用的方法不带注解,因此代理类不开事务,而是直接调用目标对象的方法。当进入目标对象的方法后,执行的上下文已经变成目标对象本身了,因为目标对象的代码是我们自己写的,和事务没有半毛钱关系,此时你再调用带注解的方法,照样没有事务,只是一个普通的方法调用而已。

他:所以这个问题的答案就是没有事务。

我:这是我们分析推理的结果,究竟对不对呢,还需要验证一下。验证过程如下:

找一个正常可用的Spring项目,把一个@Service的接口注入到一个@Controller类里面,进行检测,请仔细看下代码:

//是否是JDK动态代理
System.out.println("isJdkDynamicProxy => " + AopUtils.isJdkDynamicProxy(exampleService));
//是否是CGLIB代理
System.out.println("isCglibProxy => " + AopUtils.isCglibProxy(exampleService));
//代理类的类型
System.out.println("proxyClass => " + exampleService.getClass());
//代理类的父类的类型
System.out.println("parentClass => " + exampleService.getClass().getSuperclass());
//代理类的父类实现的接口
System.out.println("parentClass's interfaces => " + Arrays.asList(exampleService.getClass().getSuperclass().getInterfaces()));
//代理类实现的接口
System.out.println("proxyClass's interfaces => " + Arrays.asList(exampleService.getClass().getInterfaces()));
//代理对象
System.out.println("proxy => " + exampleService);
//目标对象
System.out.println("target => " + AopProxyUtils.getSingletonTarget(exampleService));
//代理对象和目标对象是不是同一个
System.out.println("proxy == target => " + (exampleService == AopProxyUtils.getSingletonTarget(exampleService)));
//目标类的类型
System.out.println("targetClass => " + AopProxyUtils.getSingletonTarget(exampleService).getClass());
//目标类实现的接口
System.out.println("targetClass's interfaces => " + Arrays.asList(AopProxyUtils.getSingletonTarget(exampleService).getClass().getInterfaces()));

System.out.println("----------------------------------------------------");

//自己模拟的动态代理的测试
Service target = new ServiceImpl();
ProxyByJdkDynamic proxy = new ProxyByJdkDynamic(target);
proxy.doNeedTx();
System.out.println("-------");
proxy.doNotneedTx();
System.out.println("-------");


以下是输出结果:

//是JDK动态代理
isJdkDynamicProxy => true
//不是CGLIB代理
isCglibProxy => false
//代理类的类型,带$的
proxyClass => class com.sun.proxy.$Proxy82
//代理类的父类
parentClass => class java.lang.reflect.Proxy
代理类的父类实现的接口
parentClass's interfaces => [interface java.io.Serializable]
//代理类实现的接口,包含了目标类的接口IExampleService,还有其它的
proxyClass'
s interfaces => [interface org.eop.sb.example.service.IExampleService,
interface org.springframework.aop.SpringProxy,
interface org.springframework.aop.framework.Advised,
interface org.springframework.core.DecoratingProxy]
//代理对象
proxy => org.eop.sb.example.service.impl.ExampleServiceImpl@54561bc9
//目标对象
target => org.eop.sb.example.service.impl.ExampleServiceImpl@54561bc9
//代理对象和目标对象输出的都是@54561bc9,还真有点懵逼
//进行测试后发现,其实不是同一个,只是toString()的问题
proxy == target => false
//目标类,我们自己写的
targetClass => class org.eop.sb.example.service.impl.ExampleServiceImpl
//目标类实现的接口,我们自己写的
targetClass's interfaces => [interface org.eop.sb.example.service.IExampleService]
----------------------------------------------------
//带注解的方法调用,有事务的开启和提交
-> create Tx here in Proxy
execute doNeedTx in ServiceImpl
<- commit Tx here in Proxy
-------
//没有注解的方法调用,是没有事务的
execute doNeedTx in ServiceImpl
-------


经过测试后,发现和我们推断的一模一样。

他:你真是打破砂锅问到底,把这个事情彻底弄明白了。

我:对于没有实现接口的类,只能使用CGLIB来生成代理。(开始套路),假设有这样一个类,它里面包含public方法,protected方法,private方法,package方法,final方法,static方法,我都给它们加上事务注解,哪些方法会有事务呢?

他:那我就现学现卖,事务是由代理加进去的,所以关键就是代理如何生成。按照上面所说的代理应该具备的特点来看,只能通过继承的方式生成一个子类来充当代理,看起来就是这样的:

class Target {

    @Transactional
    public void doNeedTx() {
        System.out.println("execute doNeedTx in Target");
    }

    //no annotation here
    public void doNotneedTx() {
        this.doNeedTx();
    }
}

class ProxyByCGLIB extends Target {

    private Target target;

    public ProxyByCGLIB(Target target) {
        this.target = target;
    }

    @Override
    public void doNeedTx() {
        System.out.println("-> create Tx in Proxy");
        target.doNeedTx();
        System.out.println("<- commit Tx in Proxy");
    }

    @Override
    public void doNotneedTx() {
        target.doNotneedTx();
    }
}


而且,必须在代理类里重写带注解方法以添加开启事务、提交事务的代码。从这个角度来说,private方法不能被继承,final方法不能被重写,static方法和继承不相干,所以它们3个的事务不起作用。

public方法,protected方法可以被重写以添加事务代码,对于package方法来说,如果生成的子类位于同一个包里,就可以被重写以添加事务代码。所以public方法事务肯定起作用,剩下那2个就不确定了,只能说它们有这个可能性。

我:你分析的很好,CGLIB确实是按照这种方式生成了子类作为代理,而且和父类在同一个包下。不过Spring选择让protected方法和package方法不支持事务,所以只有public方法支持事务。

使用和上面一样的方法进行了测试,结果如下:

//不是JDK动态代理
isJdkDynamicProxy => false
//是CGLIB代理
isCglibProxy => true
//生成的代理类的类型,带$$的
proxyClass => class org.eop.sb.example.service.impl.ExampleServiceImpl$$EnhancerBySpringCGLIB$$5320b86e
//代理类的父类,就是目标类
parentClass => class org.eop.sb.example.service.impl.ExampleServiceImpl
//父类实现的接口,就是我们自己写的接口
parentClass's interfaces => [interface org.eop.sb.example.service.IExampleService]
/**代理类实现的接口,并不包含目标类的接口*/
proxyClass'
s interfaces => [interface org.springframework.aop.SpringProxy,
interface org.springframework.aop.framework.Advised,
interface org.springframework.cglib.proxy.Factory]
//代理对象
proxy => org.eop.sb.example.service.impl.ExampleServiceImpl@1b2702b1
//目标对象
target => org.eop.sb.example.service.impl.ExampleServiceImpl@1b2702b1
//代理对象和目标对象不是同一个
proxy == target => false
//目标类,我们自己写的类
targetClass => class org.eop.sb.example.service.impl.ExampleServiceImpl
//目标类实现的接口
targetClass's interfaces => [interface org.eop.sb.example.service.IExampleService]


由于采用的是相同的测试代码,所以目标类是实现了接口的,不过这并不影响使用CGLIB来生成代理。可见,代理类确实继承了目标类以保持和目标类的类型兼容,对外接口相同。

注:只要是以代理方式实现的声明式事务,无论是JDK动态代理,还是CGLIB直接写字节码生成代理,都只有public方法上的事务注解才起作用。而且必须在代理类外部调用才行,如果直接在目标类里面调用,事务照样不起作用。

他:以前在网上也看到过有人说事务不生效的情况,我想,这个问题不会发生在我身上了。

640?wx_fmt=png

福利
扫描添加小编微信,备注“姓名+公司职位”,加入【云计算学习交流群】,和志同道合的朋友们共同打卡学习!



推荐阅读:
  • 重磅 | 华为发布绝杀计算战略!投15亿美元打造开放生态,全球最快AI训练集群Atlas 900,绝了!

  • 你需要知道的那些 redis 数据结构(前篇)

  • DeepMind悄咪咪开源三大新框架,深度强化学习落地希望再现

  • Python Web:Flask异步执行任务

  • 程序员为什么要懂物联网?

  • 鸿蒙 OS 的到来,能为我们改变什么?

  • 倒计时3天!dfuse,慢雾,MYKEY技术负责人齐聚, 一站掌握区块链数据架构的秘密!

真香,朕在看了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/521769.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA 雪花算法 唯一ID生成工具类

package com.gblfy;/*** Author&#xff1a;JCccc* Description&#xff1a;* Date&#xff1a; created in 15:31 2019/6/12*/ public class SnowflakeIdUtils {// Fields/** 开始时间截 (2015-01-01) */private final long twepoch 1420041600000L;/** 机器id所占的位数 */p…

华为 | 泰山之巅 鲲鹏展翅 扶摇直上九万里

戳蓝字“CSDN云计算”关注我们哦&#xff01; 文 | 阿晶、王银发于上海华为HC大会现场出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;华为在计算产业究竟要怎样LU起袖子加油干&#xff1f;五岳之首、泰山之巅、初升之日、鲲鹏展翅、昇腾万里、华为计…

前端 Leader 如何做好团队规划?阿里内部培训总结公开

摘要&#xff1a; "行成于思&#xff0c;毁于随"——韩愈 在阿里从一线前端工程师到技术 TL&#xff08;Team Leader&#xff09; 也三年有余了&#xff0c;最重要最难的就是做规划&#xff0c;你可能会遇到如下几个问题&#xff1a; 业务压力巨大&#xff0c;前端是…

印象笔记编辑pdf_做笔记就用印象笔记,支持录音做笔记( 附插件下载)

印象笔记是全球闻名的效率软件和知识管理工具&#xff0c;印象笔记可以帮助我们简化工作、学习与生活。你可以在手机、电脑、平板、网页等多种设备和平台间&#xff0c;无缝同步每天的见闻、灵感与思考。一站式完成知识信息的收集备份、高效记录、分享、多端同步和永久保存。下…

(vue基础试炼_07)Vue实例生命周期函数

文章目录一、生命周期图示二、常见的生命周期函数三、生命周期函数执行场景四、测试代码五、项目开源地址一、生命周期图示 二、常见的生命周期函数 常见的生命周期函数执行的时间beforeCreateVue初始化createdVue初始化beforeMount模板未渲染到页面上mounted模板已经渲染到页…

独家专访阿里高级技术专家北纬:Dubbo开源重启半年来的快意江湖

摘要&#xff1a; 罗毅&#xff0c;花名北纬。这个名字&#xff0c;如果是混过天涯论坛的大龄网民应该都不陌生&#xff0c;北纬67度3分周公子&#xff08;简称北纬&#xff09;虐杀易烨卿MM的世纪大战至今还是天涯神贴 &#xff0c;当时更是有看客赋诗形容&#xff1a;“目睹此…

实践心得:从读论文到复现到为开源贡献代码

摘要&#xff1a; 本文讲述了从在fast.ai库中读论文&#xff0c;到根据论文复制实验并做出改进&#xff0c;并将改进后的开源代码放入fast.ai库中。介绍去年我发现MOOC网上有大量的Keras和TensorKow教学视频&#xff0c;之后我从零开始学习及参加一些Kaggle比赛&#xff0c;并在…

FPGA资源平民化的新晋- F3 技术解析

摘要&#xff1a; FPGA (现场可编程门阵列)由于其硬件并行加速能力和可编程特性&#xff0c;在传统通信领域和IC设计领域大放异彩。一路走来&#xff0c;FPGA并非一个新兴的硬件器件&#xff0c;由于其开发门槛过高&#xff0c;硬件加速算法的发布和部署保护要求非常高&#xf…

Vue计算属性、方法、侦听器

文章目录一、基础计算模板二、计算属性computed三、方法methods四、侦听器watch五、总结六、源码地址一、基础计算模板 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Vue计算属性、方法、侦听器<…

漫画:五分钟看懂车联网

戳蓝字“CSDN云计算”关注我们哦&#xff01;福利扫描添加小编微信&#xff0c;备注“姓名公司职位”&#xff0c;加入【云计算学习交流群】&#xff0c;和志同道合的朋友们共同打卡学习&#xff01;推荐阅读&#xff1a;华为 | 泰山之巅 鲲鹏展翅 扶摇直上九万里聊聊我是如何在…

对数据科学家来说最重要的算法和统计模型

摘要&#xff1a; 本文提供了工业中常用的关键算法和统计技术的概要&#xff0c;以及与这些技术相关的短缺资源。作为一个在这个行业已经好几年的数据科学家&#xff0c;在LinkedIn和QuoLa上&#xff0c;我经常接触一些学生或者想转行的人&#xff0c;帮助他们进行机器学习的职…

华为愿出售5G技术渴望对手;苹果将向印度投资10亿美元;华为全联接大会首发计算战略;腾讯自研轻量级物联网操作系统正式开源……...

戳蓝字“CSDN云计算”关注我们哦&#xff01;嗨&#xff0c;大家好&#xff0c;重磅君带来的【云重磅】特别栏目&#xff0c;如期而至&#xff0c;每周五第一时间为大家带来重磅新闻。把握技术风向标&#xff0c;了解行业应用与实践&#xff0c;就交给我重磅君吧&#xff01;重…

数组元素反序

和前面的字符串逆向输出有异曲同工之妙 第一位和最后一位交换位置&#xff0c;然后用比大小循环 那么接下来修改一下这个程序&#xff0c;我们接下来解释一下p的概念 画图解释&#xff1a; 在最前面的 定义的时候&#xff0c;我们将p&#xff08;0&#xff09;定义在了1上&…

如何计算Java对象所占内存的大小

摘要&#xff1a; 本文以如何计算Java对象占用内存大小为切入点&#xff0c;在讨论计算Java对象占用堆内存大小的方法的基础上&#xff0c;详细讨论了Java对象头格式并结合JDK源码对对象头中的协议字段做了介绍&#xff0c;涉及内存模型、锁原理、分代GC、OOP-Klass模型等内容。…

hilbert谱 matlab,怎么在matlab中做信号hilbert边际谱分析

摘要&#xff1a;传统的数字滤波器的设计过程复杂&#xff0c;计算工作量大&#xff0c;滤波特性调整困难&#xff0c;影响了它的应用。本文介绍了一种利用MATLAB信号处理工具箱(Signal Processing Toolbox)快速有效的设计由软件组成的常规数字滤波器的设计方法。给出了使用MAT…

时间序列数据的处理

摘要&#xff1a; 随着云计算和IoT的发展&#xff0c;时间序列数据的数据量急剧膨胀&#xff0c;高效的分析时间序列数据&#xff0c;使之产生业务价值成为一个热门话题。阿里巴巴数据库事业部的HiTSDB团队为您分享时间序列数据的计算分析的一般方法以及优化手段。演讲嘉宾简介…

saas java框架_XMReport-提供web项目Java套打解决方案

简介XMReport是国内首款支持在线编辑&#xff0c;维护的控件式报表产品。XMReport报表产品分为设计器与引擎两个部分&#xff0c;其中报表设计器是完全基于HTML5技术&#xff0c;提供优秀跨平台的支持&#xff0c;用户无需安装客户端或者插件&#xff0c;仅使用浏览器即可进行报…

只有程序员才能读懂的西游记

戳蓝字“CSDN云计算”关注我们哦&#xff01;这其实一个有关计算机网络协议的故事一、我佛造经传极乐话说我佛如来为度化天下苍生&#xff0c;有三藏真经&#xff0c;可劝人为善。就如图中所示&#xff0c;真经所藏之处&#xff0c;在于云端。佛祖所管辖之下&#xff0c;有四个…

Logtail从入门到精通(四):正则表达式Java日志采集实战

摘要&#xff1a; 为简化日志接入门槛&#xff0c;我们提供了极简模式的日志解析方式&#xff08;如[开启日志采集之旅]()中的介绍&#xff09;。为了更好的对日志进行分析&#xff0c;我们还提供了其他解析方式&#xff0c;例如&#xff1a;分隔符模式、完整正则模式、JSON模式…

Vue中的条件渲染

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Vue中的条件渲染</title><!--引入vue.js库--><script src"../vue.js"></script> </head><body> <d…