任何Bean通過(guò)實(shí)現(xiàn)ProxyableBeanAccessor接口即可獲得動(dòng)態(tài)靈活的獲取代理對(duì)象或原生對(duì)象的能力(最新推薦)
如果一個(gè)BEAN類(lèi)上加了@Transactional,則默認(rèn)的該類(lèi)及其子類(lèi)的公開(kāi)方法均會(huì)開(kāi)啟事務(wù),但有時(shí)某些業(yè)務(wù)場(chǎng)景下某些公開(kāi)的方法可能并不需要事務(wù),那這種情況該如何做呢?
常規(guī)的做法:
針對(duì)不同的場(chǎng)景及事務(wù)傳播特性,定義不同的公開(kāi)方法【哪怕是同一種業(yè)務(wù)】,并在方法上添加@Transactional且指明不同的傳播特性,示例代碼如下:
@Service
@Transactional
public class DemoSerivce {
//SUPPORTED 若無(wú)事務(wù)傳播則默認(rèn)不會(huì)有事務(wù),若有事務(wù)傳播則會(huì)開(kāi)啟事務(wù)
@Transactional(propagation = Propagation.SUPPORTED)
public int getValue(){
return 0;
}
//默認(rèn)開(kāi)啟事務(wù)(由類(lèi)上的@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)景,這種代碼就顯得冗余過(guò)多,那有沒(méi)有簡(jiǎn)單一點(diǎn)的方案呢?其實(shí)是有的。
聲明式事務(wù)的本質(zhì)是通過(guò)AOP切面,在代理執(zhí)行原始方法【即:被標(biāo)注了@Transactional的公開(kāi)方法】前開(kāi)啟DB事務(wù),在執(zhí)行后提交DB事務(wù)(若拋錯(cuò)則執(zhí)行回滾),如果要想事務(wù)不生效,則讓AOP失效即可,即:調(diào)原生的service Bean公開(kāi)方法而不是代理類(lèi)的公開(kāi)方法,那如何獲得原生的BEAN類(lèi)呢,答案是:在原生BEAN方法內(nèi)部通過(guò)this獲取即可,如果沒(méi)理解,下面寫(xiě)一個(gè)手寫(xiě)代理示例,大家就明白了:
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):開(kāi)啟事務(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
通過(guò)DEMO證實(shí)了通過(guò)避開(kāi)代理的方案是正確的,而且非常簡(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訪問(wèn)者接口(支持獲取代理的真實(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的公開(kāi)方法,則均不會(huì)再走AOP切面
*
* @return 未被代理的對(duì)象Bean
*/
@SuppressWarnings("unchecked")
@Transactional(propagation = Propagation.NOT_SUPPORTED)
default T getReal() {
return (T) this;
}
/**
* 獲取當(dāng)前類(lèi)的代理對(duì)象(即:已被代理的對(duì)象)
* 注:若調(diào)用已被代理的對(duì)象Bean的公開(kāi)方法,則相關(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的工具類(lèi),代碼如下:
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的子類(lèi)或其它可能被切面代理的類(lèi) 加上實(shí)現(xiàn)自ProxyableBeanAccessor即可,然后在這個(gè)類(lèi)里面或外部調(diào)用均可通過(guò)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){
//通過(guò)getProxy獲取當(dāng)前類(lèi)的代理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ì)象查詢,由于沒(méi)有代理,則無(wú)事務(wù)
demoService.getReal().selectByMergerParam(query);
//示例二:根據(jù)LAMBDA表達(dá)式的布爾值來(lái)決定是否獲取原生或代理(這里演示:如果是TIDB,則獲取原生對(duì)象,即:不開(kāi)事務(wù))
demoService.selfAs(()-> TidbDataSourceSwitcher.isUsingTidbDataSource()).selectByMergerParam(query);
//示例三:根據(jù)線程上下文設(shè)置的布爾值來(lái)決定是否獲取原生或代理(這里演示:如果是TIDB,則獲取原生對(duì)象,即:不開(kāi)事務(wù)),這種方式主要是為了簡(jiǎn)化大批量的動(dòng)態(tài)邏輯判斷的場(chǎng)景,
// 一次設(shè)置同線程的所有ProxyableBeanAccessor的子類(lèi)的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);通過(guò)上述示例代碼可以看到,借助于ProxyableBeanAccessor接口默認(rèn)實(shí)現(xiàn)的getReal、getProxy、selfAs方法,可以很靈活的實(shí)現(xiàn)按需獲取代理或非代理對(duì)象。
到此這篇關(guān)于任何Bean通過(guò)實(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年開(kāi)發(fā),是一種工業(yè)現(xiàn)場(chǎng)總線協(xié)議標(biāo)準(zhǔn),本文主要介紹了Springboot實(shí)現(xiàn)ModbusTCP通信的相關(guān)知識(shí),需要的可以參考下2023-12-12
Java實(shí)現(xiàn)在不同線程中運(yùn)行的代碼實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)在不同線程中運(yùn)行的代碼,結(jié)合具體實(shí)例形式分析了java多線程操作的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-04-04
Java詳解IO流創(chuàng)建讀取與寫(xiě)入操作
這篇文章主要介紹了Java IO流,同時(shí)也介紹了流中的一些相關(guān)的內(nèi)容,并且通過(guò)大量的案例供大家理解。最后通過(guò)一些經(jīng)典的案例幫助大家對(duì)前面所學(xué)的知識(shí)做了一個(gè)綜合的應(yīng)用,需要的朋友可以參考一下2022-05-05
Java?easyExcel的復(fù)雜表頭多級(jí)表頭導(dǎo)入
最近在項(xiàng)目開(kāi)發(fā)中遇到的一個(gè)excel復(fù)雜表頭的導(dǎo)入數(shù)據(jù)庫(kù)操作,下面這篇文章主要給大家介紹了關(guān)于Java?easyExcel的復(fù)雜表頭多級(jí)表頭導(dǎo)入的相關(guān)資料,需要的朋友可以參考下2022-06-06
Spring Boot實(shí)現(xiàn)圖片上傳/加水印一把梭操作實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于Spring Boot實(shí)現(xiàn)圖片上傳/加水印一把梭操作的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11

