任何Bean通過實(shí)現(xiàn)ProxyableBeanAccessor接口即可獲得動(dòng)態(tài)靈活的獲取代理對(duì)象或原生對(duì)象的能力(最新推薦)
如果一個(gè)BEAN類上加了@Transactional,則默認(rèn)的該類及其子類的公開方法均會(huì)開啟事務(wù),但有時(shí)某些業(yè)務(wù)場(chǎng)景下某些公開的方法可能并不需要事務(wù),那這種情況該如何做呢?
常規(guī)的做法:
針對(duì)不同的場(chǎng)景及事務(wù)傳播特性,定義不同的公開方法【哪怕是同一種業(yè)務(wù)】,并在方法上添加@Transactional且指明不同的傳播特性,示例代碼如下:
@Service @Transactional public class DemoSerivce { //SUPPORTED 若無(wú)事務(wù)傳播則默認(rèn)不會(huì)有事務(wù),若有事務(wù)傳播則會(huì)開啟事務(wù) @Transactional(propagation = Propagation.SUPPORTED) public int getValue(){ return 0; } //默認(rèn)開啟事務(wù)(由類上的@Transactional決定的) public int getValueWithTx(){ return 0; } @Transactional(propagation = Propagation.NOT_SUPPORTED) public int getValueWithoutTx(){ return 0; } }
上述這樣的弊端就是:若是同一個(gè)邏輯但如果要精細(xì)控制事務(wù),則需要定義3個(gè)方法來(lái)支持,getValue、getValueWithTx、getValueWithoutTx 3個(gè)方法,分別對(duì)應(yīng)3種事務(wù)場(chǎng)景,這種代碼就顯得冗余過多,那有沒有簡(jiǎn)單一點(diǎn)的方案呢?其實(shí)是有的。
聲明式事務(wù)的本質(zhì)是通過AOP切面,在代理執(zhí)行原始方法【即:被標(biāo)注了@Transactional的公開方法】前開啟DB事務(wù),在執(zhí)行后提交DB事務(wù)(若拋錯(cuò)則執(zhí)行回滾),如果要想事務(wù)不生效,則讓AOP失效即可,即:調(diào)原生的service Bean公開方法而不是代理類的公開方法,那如何獲得原生的BEAN類呢,答案是:在原生BEAN方法內(nèi)部通過this獲取即可,如果沒理解,下面寫一個(gè)手寫代理示例,大家就明白了:
public class DemoService{ public int getValue(){ return 666; } public DemoService getReal(){ //這里的this指向的就是當(dāng)前的自己,并非代理對(duì)象 return this; } } public class DemoServiceProxy{ private DemoService target; public DemoServiceProxy(DemoService target){ this.target=target; } public int getValue(){ //增強(qiáng):開啟事務(wù) return target.getValue()+222; //增強(qiáng):提交事務(wù) } public DemoService getReal(){ //這里就會(huì)間接的把原生的對(duì)象傳遞返回 return target.getReal(); } } public static void main(String[] args) { DemoServiceProxy proxy=new DemoServiceProxy(new DemoService()); System.out.println("proxy class:" +proxy.getClass().getName()); System.out.println("real class:" +proxy.getReal().getClass().getName()); System.out.println("proxy getValue result:" + proxy.getValue() ); System.out.println("real getValue result:" + proxy.getReal().getValue() ); }
最終的輸出結(jié)果為:
proxy class:...DemoServiceProxy
real class:...DemoService →原始的對(duì)象
proxy getValue result:888
real getValue result:666
通過DEMO證實(shí)了通過避開代理的方案是正確的,而且非常簡(jiǎn)單,那么有了這個(gè)基礎(chǔ),再應(yīng)用到實(shí)際的代碼中則很簡(jiǎn)單,想控制有事務(wù)則取代理對(duì)象,想控制不要事務(wù)則取原生對(duì)象即可,就是這么簡(jiǎn)單。
下面貼出核心也是全部的ProxyableBeanAccessor代碼:(注意必需擴(kuò)展自RawTargetAccess,否則即使返回this也會(huì)被強(qiáng)制返回代理)
/** * @author zuowenjun * @date 2022/12/5 22:03 * @description 可代理BEAN訪問者接口(支持獲取代理的真實(shí)對(duì)象、獲取代理對(duì)象) */ public interface ProxyableBeanAccessor<T extends ProxyableBeanAccessor> extends RawTargetAccess { String CONTEXT_KEY_REAL_GET = "proxyable_bean_accessor_real_get"; /** * 獲取代理的真實(shí)對(duì)象(即:未被代理的對(duì)象) * 注:若調(diào)用未被代理的bean的公開方法,則均不會(huì)再走AOP切面 * * @return 未被代理的對(duì)象Bean */ @SuppressWarnings("unchecked") @Transactional(propagation = Propagation.NOT_SUPPORTED) default T getReal() { return (T) this; } /** * 獲取當(dāng)前類的代理對(duì)象(即:已被代理的對(duì)象) * 注:若調(diào)用已被代理的對(duì)象Bean的公開方法,則相關(guān)AOP切面均可正常攔截與執(zhí)行 * * @return 已被代理的對(duì)象Bean */ @SuppressWarnings("unchecked") @Transactional(propagation = Propagation.NOT_SUPPORTED) default T getProxy() { return (T) SpringUtils.getBean(this.getClass()); } /** * 將當(dāng)前BEAN轉(zhuǎn)換為代理對(duì)象或真實(shí)對(duì)象 * * @param realGet 是否轉(zhuǎn)換獲取真實(shí)對(duì)象 * @return 未被代理的對(duì)象Bean OR 已被代理的對(duì)象Bean */ @Transactional(propagation = Propagation.NOT_SUPPORTED) default T selfAs(Supplier<Boolean> realGet) { Boolean needGetReal = false; if (realGet == null) { if (ContextUtils.get() != null) { needGetReal = (Boolean) ContextUtils.get().getGlobalVariableMap().getOrDefault(CONTEXT_KEY_REAL_GET, false); } } else { needGetReal = realGet.get(); } return Boolean.TRUE.equals(needGetReal) ? getReal() : getProxy(); } }
其中,SpringUtils是一個(gè)獲取BEAN的工具類,代碼如下:
public SpringUtils implements ApplicationContextAware{ private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context=applicationContext; } public static <T> getBean(Class<T> clazz){ return context.getBean(clazz); } }
ContextUtils只是一個(gè)內(nèi)部定義了一個(gè)ThreadLocal的靜態(tài)map字段,用于存放線程上下文要傳遞的對(duì)象。
使用方法:只需將原來(lái)Service的子類或其它可能被切面代理的類 加上實(shí)現(xiàn)自ProxyableBeanAccessor即可,然后在這個(gè)類里面或外部調(diào)用均可通過getReal獲得原生對(duì)象、getProxy獲得代理對(duì)象、selfAs動(dòng)態(tài)根據(jù)條件來(lái)判斷是否需要代理或原生對(duì)象,使用示例如下:
//serive BEAN定義 @Service @Transactional public class DemoService implements ProxyableBeanAccessor<DemoService> { ... ... public Demo selectByMergerParam(Demo demo){ return getMapper().selectByMergerParam(demo); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public Demo selectByMergerParam2(Demo demo){ //通過getProxy獲取當(dāng)前類的代理BEAN,以便可以執(zhí)行事務(wù)切面 return getProxy().doSelectByMergerParam(demo); } public Demo doSelectByMergerParam(Demo demo){ return getMapper().selectByMergerParam(demo); } } //具體使用: @Autowired private DemoService demoService; Demo query = new Demo (); query.setWaybillNumber("123455667"); //示例一:獲取原生對(duì)象查詢,由于沒有代理,則無(wú)事務(wù) demoService.getReal().selectByMergerParam(query); //示例二:根據(jù)LAMBDA表達(dá)式的布爾值來(lái)決定是否獲取原生或代理(這里演示:如果是TIDB,則獲取原生對(duì)象,即:不開事務(wù)) demoService.selfAs(()-> TidbDataSourceSwitcher.isUsingTidbDataSource()).selectByMergerParam(query); //示例三:根據(jù)線程上下文設(shè)置的布爾值來(lái)決定是否獲取原生或代理(這里演示:如果是TIDB,則獲取原生對(duì)象,即:不開事務(wù)),這種方式主要是為了簡(jiǎn)化大批量的動(dòng)態(tài)邏輯判斷的場(chǎng)景, // 一次設(shè)置同線程的所有ProxyableBeanAccessor的子類的selfAs(null)均可自動(dòng)判斷 ContextUtils.get().addGlobalVariable(ProxyableBeanAccessor.CONTEXT_KEY_REAL_GET,TidbDataSourceSwitcher.isUsingTidbDataSource()); demoService.selfAs(null).selectByMergerParam(query); //示例四:獲取代理對(duì)象,一般用于BEAN內(nèi)部方法之間調(diào)用,外部調(diào)用其實(shí)本身就是代理無(wú)意義 demoService.getProxy().selectByMergerParam(query);
通過上述示例代碼可以看到,借助于ProxyableBeanAccessor接口默認(rèn)實(shí)現(xiàn)的getReal、getProxy、selfAs方法,可以很靈活的實(shí)現(xiàn)按需獲取代理或非代理對(duì)象。
到此這篇關(guān)于任何Bean通過實(shí)現(xiàn)ProxyableBeanAccessor接口即可獲得動(dòng)態(tài)靈活的獲取代理對(duì)象或原生對(duì)象的能力 的文章就介紹到這了,更多相關(guān)Bean ProxyableBeanAccessor接口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot實(shí)現(xiàn)ModbusTCP通信的示例詳解
ModbusTCP協(xié)議是Modbus由MODICON公司于1979年開發(fā),是一種工業(yè)現(xiàn)場(chǎng)總線協(xié)議標(biāo)準(zhǔn),本文主要介紹了Springboot實(shí)現(xiàn)ModbusTCP通信的相關(guān)知識(shí),需要的可以參考下2023-12-12Java實(shí)現(xiàn)在不同線程中運(yùn)行的代碼實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)在不同線程中運(yùn)行的代碼,結(jié)合具體實(shí)例形式分析了java多線程操作的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-04-04Java?easyExcel的復(fù)雜表頭多級(jí)表頭導(dǎo)入
最近在項(xiàng)目開發(fā)中遇到的一個(gè)excel復(fù)雜表頭的導(dǎo)入數(shù)據(jù)庫(kù)操作,下面這篇文章主要給大家介紹了關(guān)于Java?easyExcel的復(fù)雜表頭多級(jí)表頭導(dǎo)入的相關(guān)資料,需要的朋友可以參考下2022-06-06Spring Boot實(shí)現(xiàn)圖片上傳/加水印一把梭操作實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于Spring Boot實(shí)現(xiàn)圖片上傳/加水印一把梭操作的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11