javascript
聊聊我是如何在面试别人Spring事务时“套路”对方的
戳藍(lán)字“CSDN云計(jì)算”關(guān)注我們哦!
“中國最好面試官”
好吧,那這次我就“使點(diǎn)壞”,“套路”一下面試者。
記一次“帶套路”的面試
與這個(gè)面試者聊了一會兒,咦,發(fā)現(xiàn)他水平還可以,我內(nèi)心有點(diǎn)兒喜出望外,終于遇到一個(gè)“合格”的“陪聊者”了,我要用Spring事務(wù)“好好套路”他一下。
我:你在開發(fā)中,一般都把事務(wù)加到哪一層?
他:都加到Service層。
我:現(xiàn)在基本都是基于注解的配置了,那和事務(wù)相關(guān)的注解是哪個(gè)?
他:我不太會讀那個(gè)單詞,就是以@T開頭的那個(gè)。
我:我明白你的意思,就是@Transactional。
他:是的。
我:與自己寫代碼來開啟和提交事務(wù)相比,(先給他來個(gè)小的套路),這種通過注解來使用事務(wù)的方式叫什么?
他:(猶豫了兩、三秒),不知道。
我:如果把寫代碼那種叫編程式事務(wù),那與之相對的應(yīng)該是什么式事務(wù)?
他:哦,聲明式事務(wù)。
我:(先鋪墊),不加注解,沒有事務(wù),加上注解,就有事務(wù),可見事務(wù)和注解有莫大的關(guān)系。(開始套路),那加上注解后,到底發(fā)生了什么變化呢,就有了事務(wù)?
他:(猶豫了幾秒鐘),不知道。
我:(哈哈,意料之中),那我換一問法,Spring聲明式事務(wù)的底層是怎么實(shí)現(xiàn)的?
他:是通過代理實(shí)現(xiàn)的。
我:(鋪墊),代理這個(gè)詞不僅計(jì)算機(jī)里有,現(xiàn)實(shí)生活中也經(jīng)常見到代理,比如招華北地區(qū)總代理等等。(套路),那你能不能在生活中舉一個(gè)代理的例子?
他:(想了一會兒),我沒有想到什么好例子。
我:(開始聊會天),我看你老家離這還挺遠(yuǎn)的,你一般都什么時(shí)候回去啊?
他:一般都國慶節(jié)或春節(jié)會回去。其它時(shí)間假期短就不回去了。
我:(引子),國慶節(jié)和春節(jié),人都很多啊,票不好買吧?
他:是啊,都在網(wǎng)上搶高鐵票,不停地刷。
我:(引子),現(xiàn)在有了高鐵,出行確實(shí)方便了很多。那你知道以前沒有高鐵、沒有12306的時(shí)候,人們都是怎么買票的嗎?
他:我雖然沒有經(jīng)歷過,但是我知道。那時(shí)候春運(yùn),都在火車站售票大廳買票,人們排很長的隊(duì),有時(shí)需要等半天,還不一定有票。
我:(切入正題),除了火車站售票大廳外,你有沒有見過在城市里分布的一些火車票代售點(diǎn)?
他:現(xiàn)在偶爾還能見到幾個(gè),但都已經(jīng)關(guān)門了。
我:是啊,現(xiàn)在都網(wǎng)上買票了,代售點(diǎn)算是被歷史拋棄了。(開始套路),那你覺得代售點(diǎn)算不算火車站售票大廳的代理呢?
他:火車站售票大廳可以買票,代售點(diǎn)也可以買票,應(yīng)該算是代理吧。
我:從廣義講算是代理。但有兩點(diǎn)需要注意:
一是,代售點(diǎn)賣的也是售票大廳的票,它自己是沒有票的,它只是行使售票大廳的權(quán)利。
二是,它可以有屬于自己的行為特征,比如不需要排隊(duì)啊,每張硬座票收5元手續(xù)費(fèi)啊等等。
我們平時(shí)聽到的中間商/代理商,其實(shí)都差不多是一回事兒。
他:經(jīng)你這么一說,我明白了。
我:那我們再說回到Spring中的代理,在Spring中生成代理的方式有幾種?
他:兩種,JDK動態(tài)代理和CGLIB。
我:那它們分別用于什么情況下?
他:JDK動態(tài)代理只能用于帶接口的,CGLIB則帶不帶接口都行。
我:(鋪墊),假如有個(gè)接口,它包含兩個(gè)方法a和b,然后有一個(gè)類實(shí)現(xiàn)了該接口。在該實(shí)現(xiàn)類里在a上標(biāo)上事務(wù)注解、b上不標(biāo),此時(shí)事務(wù)是怎樣的?
他:a標(biāo)注解了,肯定有事務(wù),b沒有注解,所以沒有事務(wù)。
我:嗯,是這樣的。(開始套路),現(xiàn)在來做個(gè)簡單的修改,在方法b里調(diào)用方法a,其它保持不變,此時(shí)再調(diào)用方法b,會有事務(wù)嗎?
他:應(yīng)該有吧,雖然b沒有注解,但a有啊。
我:(我需要帶帶他),假設(shè)現(xiàn)在你和我都不知道有沒有事務(wù),那我們來分析分析,看能不能找出答案。你有分析思路嗎?
他:沒有。
我:行吧,那我們開始。這是一個(gè)帶接口的,那就假定使用JDK動態(tài)代理吧。從宏觀上看,就是Spring使用JDK動態(tài)代理為這個(gè)類生成了一個(gè)代理,并為標(biāo)有注解的方法添加了和事務(wù)相關(guān)的代碼,所以就具有了事務(wù)。那你知道這個(gè)代理大概會是什么樣子的嗎?
他:這個(gè)不知道。
我:通過代售點(diǎn)的例子我們應(yīng)該知道,所有的代理都具有以下特點(diǎn):
代理是一個(gè)空殼,它背后才是真正的老板。
代理可以行使老板的權(quán)力,所以它看起來“很像”老板,除非仔細(xì)查看,否則不易區(qū)分。
代理自己可以按需加進(jìn)去一些行為特征,除非仔細(xì)查看,否則老板都不一定知道這些。
那我們回到程序世界,使用接口和類再套一下上面的特點(diǎn):
代理類是一個(gè)空殼(或外觀),它背后才是真正的類,通常稱為目標(biāo)類。由此得出代理類要包含目標(biāo)類。
對目標(biāo)類和代理類的使用方式是一樣的,甚至你都不知道它是代理類。由此得出代理類和目標(biāo)類的類型要兼容,對外接口一致。所以目標(biāo)類實(shí)現(xiàn)的接口,代理類也要實(shí)現(xiàn)。
代理類在把執(zhí)行流程代理給目標(biāo)類的過程中,可以添加一些行為代碼,如開啟事務(wù)、提交事務(wù)等。
他:經(jīng)你這么一分析啊,我知道該怎么寫代碼了,應(yīng)該是這樣的,請仔細(xì)看下代碼,雖然很簡單:
//接口
interface?Service?{
????void?doNeedTx();
????void?doNotneedTx();
}
//目標(biāo)類,實(shí)現(xiàn)接口
class?ServiceImpl?implements?Service?{
????
????
????public?void?doNeedTx()?{
????????System.out.println("execute?doNeedTx?in?ServiceImpl");
????}
????//no?annotation?here
????
????public?void?doNotneedTx()?{
????????this.doNeedTx();
????}
}
//代理類,也要實(shí)現(xiàn)相同的接口
class?ProxyByJdkDynamic?implements?Service?{
????//包含目標(biāo)對象
????private?Service?target;
????public?ProxyByJdkDynamic(Service?target)?{
????????this.target?=?target;
????}
????//目標(biāo)類中此方法帶注解,進(jìn)行特殊處理
????
????public?void?doNeedTx()?{
????????//開啟事務(wù)
????????System.out.println("->?create?Tx?here?in?Proxy");
????????//調(diào)用目標(biāo)對象的方法,該方法已在事務(wù)中了
????????target.doNeedTx();
????????//提交事務(wù)
????????System.out.println("<-?commit?Tx?here?in?Proxy");
????}
????//目標(biāo)類中此方法沒有注解,只做簡單的調(diào)用
????
????public?void?doNotneedTx()?{
????????//直接調(diào)用目標(biāo)對象方法
????????target.doNotneedTx();
????}
}
我:目標(biāo)類是我們自己寫的,肯定是沒有事務(wù)的。代理類是系統(tǒng)生成的,對帶注解的方法進(jìn)行事務(wù)增強(qiáng),沒有注解的方法原樣調(diào)用,所以事務(wù)是代理類加上去的。
那回到一開始的問題,我們調(diào)用的方法不帶注解,因此代理類不開事務(wù),而是直接調(diào)用目標(biāo)對象的方法。當(dāng)進(jìn)入目標(biāo)對象的方法后,執(zhí)行的上下文已經(jīng)變成目標(biāo)對象本身了,因?yàn)槟繕?biāo)對象的代碼是我們自己寫的,和事務(wù)沒有半毛錢關(guān)系,此時(shí)你再調(diào)用帶注解的方法,照樣沒有事務(wù),只是一個(gè)普通的方法調(diào)用而已。
他:所以這個(gè)問題的答案就是沒有事務(wù)。
我:這是我們分析推理的結(jié)果,究竟對不對呢,還需要驗(yàn)證一下。驗(yàn)證過程如下:
找一個(gè)正常可用的Spring項(xiàng)目,把一個(gè)@Service的接口注入到一個(gè)@Controller類里面,進(jìn)行檢測,請仔細(xì)看下代碼:
//是否是JDK動態(tài)代理
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());
//代理類的父類實(shí)現(xiàn)的接口
System.out.println("parentClass's?interfaces?=>?"?+?Arrays.asList(exampleService.getClass().getSuperclass().getInterfaces()));
//代理類實(shí)現(xiàn)的接口
System.out.println("proxyClass's?interfaces?=>?"?+?Arrays.asList(exampleService.getClass().getInterfaces()));
//代理對象
System.out.println("proxy?=>?"?+?exampleService);
//目標(biāo)對象
System.out.println("target?=>?"?+?AopProxyUtils.getSingletonTarget(exampleService));
//代理對象和目標(biāo)對象是不是同一個(gè)
System.out.println("proxy?==?target?=>?"?+?(exampleService?==?AopProxyUtils.getSingletonTarget(exampleService)));
//目標(biāo)類的類型
System.out.println("targetClass?=>?"?+?AopProxyUtils.getSingletonTarget(exampleService).getClass());
//目標(biāo)類實(shí)現(xiàn)的接口
System.out.println("targetClass's?interfaces?=>?"?+?Arrays.asList(AopProxyUtils.getSingletonTarget(exampleService).getClass().getInterfaces()));
System.out.println("----------------------------------------------------");
//自己模擬的動態(tài)代理的測試
Service?target?=?new?ServiceImpl();
ProxyByJdkDynamic?proxy?=?new?ProxyByJdkDynamic(target);
proxy.doNeedTx();
System.out.println("-------");
proxy.doNotneedTx();
System.out.println("-------");
以下是輸出結(jié)果:
//是JDK動態(tài)代理
isJdkDynamicProxy?=>?true
//不是CGLIB代理
isCglibProxy?=>?false
//代理類的類型,帶$的
proxyClass?=>?class?com.sun.proxy.$Proxy82
//代理類的父類
parentClass?=>?class?java.lang.reflect.Proxy
代理類的父類實(shí)現(xiàn)的接口
parentClass's?interfaces?=>?[interface?java.io.Serializable]
//代理類實(shí)現(xiàn)的接口,包含了目標(biāo)類的接口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
//目標(biāo)對象
target?=>?org.eop.sb.example.service.impl.ExampleServiceImpl@54561bc9
//代理對象和目標(biāo)對象輸出的都是@54561bc9,還真有點(diǎn)懵逼
//進(jìn)行測試后發(fā)現(xiàn),其實(shí)不是同一個(gè),只是toString()的問題
proxy?==?target?=>?false
//目標(biāo)類,我們自己寫的
targetClass?=>?class?org.eop.sb.example.service.impl.ExampleServiceImpl
//目標(biāo)類實(shí)現(xiàn)的接口,我們自己寫的
targetClass's?interfaces?=>?[interface?org.eop.sb.example.service.IExampleService]
----------------------------------------------------
//帶注解的方法調(diào)用,有事務(wù)的開啟和提交
->?create?Tx?here?in?Proxy
execute?doNeedTx?in?ServiceImpl
<-?commit?Tx?here?in?Proxy
-------
//沒有注解的方法調(diào)用,是沒有事務(wù)的
execute?doNeedTx?in?ServiceImpl
-------
經(jīng)過測試后,發(fā)現(xiàn)和我們推斷的一模一樣。
他:你真是打破砂鍋問到底,把這個(gè)事情徹底弄明白了。
我:對于沒有實(shí)現(xiàn)接口的類,只能使用CGLIB來生成代理。(開始套路),假設(shè)有這樣一個(gè)類,它里面包含public方法,protected方法,private方法,package方法,final方法,static方法,我都給它們加上事務(wù)注解,哪些方法會有事務(wù)呢?
他:那我就現(xiàn)學(xué)現(xiàn)賣,事務(wù)是由代理加進(jìn)去的,所以關(guān)鍵就是代理如何生成。按照上面所說的代理應(yīng)該具備的特點(diǎn)來看,只能通過繼承的方式生成一個(gè)子類來充當(dāng)代理,看起來就是這樣的:
class?Target?{
????
????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;
????}
????
????public?void?doNeedTx()?{
????????System.out.println("->?create?Tx?in?Proxy");
????????target.doNeedTx();
????????System.out.println("<-?commit?Tx?in?Proxy");
????}
????
????public?void?doNotneedTx()?{
????????target.doNotneedTx();
????}
}
而且,必須在代理類里重寫帶注解方法以添加開啟事務(wù)、提交事務(wù)的代碼。從這個(gè)角度來說,private方法不能被繼承,final方法不能被重寫,static方法和繼承不相干,所以它們3個(gè)的事務(wù)不起作用。
public方法,protected方法可以被重寫以添加事務(wù)代碼,對于package方法來說,如果生成的子類位于同一個(gè)包里,就可以被重寫以添加事務(wù)代碼。所以public方法事務(wù)肯定起作用,剩下那2個(gè)就不確定了,只能說它們有這個(gè)可能性。
我:你分析的很好,CGLIB確實(shí)是按照這種方式生成了子類作為代理,而且和父類在同一個(gè)包下。不過Spring選擇讓protected方法和package方法不支持事務(wù),所以只有public方法支持事務(wù)。
使用和上面一樣的方法進(jìn)行了測試,結(jié)果如下:
//不是JDK動態(tài)代理
isJdkDynamicProxy?=>?false
//是CGLIB代理
isCglibProxy?=>?true
//生成的代理類的類型,帶$$的
proxyClass?=>?class?org.eop.sb.example.service.impl.ExampleServiceImpl$$EnhancerBySpringCGLIB$$5320b86e
//代理類的父類,就是目標(biāo)類
parentClass?=>?class?org.eop.sb.example.service.impl.ExampleServiceImpl
//父類實(shí)現(xiàn)的接口,就是我們自己寫的接口
parentClass's?interfaces?=>?[interface?org.eop.sb.example.service.IExampleService]
/**代理類實(shí)現(xiàn)的接口,并不包含目標(biāo)類的接口*/
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
//目標(biāo)對象
target?=>?org.eop.sb.example.service.impl.ExampleServiceImpl@1b2702b1
//代理對象和目標(biāo)對象不是同一個(gè)
proxy?==?target?=>?false
//目標(biāo)類,我們自己寫的類
targetClass?=>?class?org.eop.sb.example.service.impl.ExampleServiceImpl
//目標(biāo)類實(shí)現(xiàn)的接口
targetClass's?interfaces?=>?[interface?org.eop.sb.example.service.IExampleService]
由于采用的是相同的測試代碼,所以目標(biāo)類是實(shí)現(xiàn)了接口的,不過這并不影響使用CGLIB來生成代理。可見,代理類確實(shí)繼承了目標(biāo)類以保持和目標(biāo)類的類型兼容,對外接口相同。
注:只要是以代理方式實(shí)現(xiàn)的聲明式事務(wù),無論是JDK動態(tài)代理,還是CGLIB直接寫字節(jié)碼生成代理,都只有public方法上的事務(wù)注解才起作用。而且必須在代理類外部調(diào)用才行,如果直接在目標(biāo)類里面調(diào)用,事務(wù)照樣不起作用。
他:以前在網(wǎng)上也看到過有人說事務(wù)不生效的情況,我想,這個(gè)問題不會發(fā)生在我身上了。
福利掃描添加小編微信,備注“姓名+公司職位”,加入【云計(jì)算學(xué)習(xí)交流群】,和志同道合的朋友們共同打卡學(xué)習(xí)!
推薦閱讀:
重磅 | 華為發(fā)布絕殺計(jì)算戰(zhàn)略!投15億美元打造開放生態(tài),全球最快AI訓(xùn)練集群Atlas 900,絕了!
你需要知道的那些 redis 數(shù)據(jù)結(jié)構(gòu)(前篇)
DeepMind悄咪咪開源三大新框架,深度強(qiáng)化學(xué)習(xí)落地希望再現(xiàn)
Python Web:Flask異步執(zhí)行任務(wù)
程序員為什么要懂物聯(lián)網(wǎng)?
鴻蒙 OS 的到來,能為我們改變什么?
倒計(jì)時(shí)3天!dfuse,慢霧,MYKEY技術(shù)負(fù)責(zé)人齊聚, 一站掌握區(qū)塊鏈數(shù)據(jù)架構(gòu)的秘密!
總結(jié)
以上是生活随笔為你收集整理的聊聊我是如何在面试别人Spring事务时“套路”对方的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是纾困债
- 下一篇: Spring中的9种设计模式汇总