上海貿(mào)易公司有哪些甘肅新站優(yōu)化
模擬 Spring 創(chuàng)建的動(dòng)態(tài)代理類
本文主要目的是從父類和子類繼承的角度去分析為什么在 @Service 標(biāo)注的業(yè)務(wù)類中使用 this 調(diào)用方法會(huì)造成事務(wù)失效。解釋在這種情況下 this 為什么是原始類對(duì)象而不是代理類對(duì)象。
問題描述
在 @Service 標(biāo)注的業(yè)務(wù)類中,如果調(diào)用本類中的方法,那么會(huì)造成事務(wù)失效。原因是因?yàn)槭聞?wù)的功能是 @Transactional 注解通過 AOP 切面的方式對(duì)原始類進(jìn)行的增強(qiáng),因此事務(wù)功能是代理類對(duì)象中的方法才具備的。
現(xiàn)在問題來了,在 CGLib 的動(dòng)態(tài)代理模式中,代理類(假設(shè)為 UserServiceImplProxy)是繼承了 UserServiceImpl,也就是說代理類是原始類的子類,而通過 Spring 容器的 getBean 方法獲取到的也是代理類對(duì)象,那么在主方法中調(diào)用 userServiceImplProxy.transactionFailTest()
方法,那問題似乎變成了在父類中使用 this 關(guān)鍵字時(shí),this 代表的是子類對(duì)象還是父類對(duì)象?
先說結(jié)論,this 代表的對(duì)象是不確定的。
@Service
public class UserServiceImpl {@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserServiceImpl userServiceImpl;public void transactionFailTest() {System.out.println("this=" + this);System.out.println("this.getClass()=" + this.getClass());System.out.println("this.getClass().getSuperclass() = " + this.getClass().getSuperclass());// 重點(diǎn)是探究this對(duì)象到底是什么?為什么this不是代理類對(duì)象this.transactionTest();}public void transactionSuccessTest() {// 調(diào)用代理類中的方法userServiceImpl.transactionTest();}@Transactionalpublic void transactionTest() {userMapper.updatePasswordById(1L, "111111");// if (true) {// throw new RuntimeException("故意制造異常");// }userMapper.updatePasswordById(2L, "222222");}
}
繼承關(guān)系中的方法調(diào)用
在下面的測(cè)試案例中,同樣是在父類 Parent 中的方法中使用 this 關(guān)鍵字,而實(shí)際調(diào)用的是子類 Child 中的方法。這是因?yàn)?main 方法中方法的調(diào)用者就是一個(gè) Child 對(duì)象,所以無論是 Parent 類還是 Child 類中的 this,都是指向該調(diào)用對(duì)象的地址。
/*** 通過super調(diào)用父類方法*/
@Slf4j
public class SuperCallMainDemo {public static void main(String[] args) {Parent parent = new Child();log.error("main方法中的調(diào)用者對(duì)象={}", parent);parent.method01();}static class Child extends Parent {@Overridepublic void method01() {log.info("******************************************");super.method01();log.info("******************************************");}@Overridepublic void method02() {log.info("==========================================");super.method02();log.info("==========================================");}}static class Parent {public void method01() {log.info("Parent執(zhí)行method01方法, this={}", this);this.method02();}public void method02() {log.info("Parent執(zhí)行method02方法, this={}", this);}}
}
在繼承中使用反射進(jìn)行方法調(diào)用(模擬動(dòng)態(tài)代理類邏輯)
在下面的測(cè)試案例中,和 Spring 通過 CGLIB 動(dòng)態(tài)代理生成的動(dòng)態(tài)代理類的原理相同。雖然代理類是子類,但由于是動(dòng)態(tài)生成的,所以沒有辦法通過 super 關(guān)鍵字來直接調(diào)用父類中的同名方法,因此即使攔截到父類中的方法 m1、m2,也還是需要通過 invoke 反射的方式進(jìn)行調(diào)用。因此 this 關(guān)鍵字指向的是 invoke 方法傳遞過去的父類對(duì)象。
/*** 通過反射調(diào)用父類方法*/
@Slf4j
public class InvokeCallMainDemo {public static void main(String[] args) {Parent parent = new Parent();log.error("main方法中parent的地址={}", parent);Parent child = new Child(parent, Parent.class);log.error("main方法中child的地址={}", child);child.method01();}static class Child extends Parent {Parent target;Class<?> clazz;Method m1;Method m2;@SneakyThrowspublic Child(Parent target, Class<?> clazz) {this.target = target;this.clazz = clazz;// 這里模擬代理類攔截父類的所有方法m1 = clazz.getMethod("method01");m2 = clazz.getMethod("method02");}@SneakyThrows@Overridepublic void method01() {log.info("******************************************");// 實(shí)際上這里的方法是被攔截下來的m1.invoke(target);log.info("******************************************");}@SneakyThrows@Overridepublic void method02() {log.info("==========================================");m2.invoke(target);log.info("==========================================");}}static class Parent {public void method01() {log.info("Parent執(zhí)行method01方法, this={}", this);this.method02();}public void method02() {log.info("Parent執(zhí)行method02方法, this={}", this);}}
}
總結(jié)
- 無論是那種調(diào)用方式,
this
都表示實(shí)際調(diào)用的那個(gè)對(duì)象,不會(huì)因?yàn)槭褂?super
關(guān)鍵字而被更改。 - 在反射調(diào)用方式中,通過
method.invoke(target)
進(jìn)行調(diào)用方法時(shí),傳遞的對(duì)象就是 target,因此this
表示的就是 target 對(duì)象。(動(dòng)態(tài)代理類只能選擇這種方式) - Spring 中的代理類會(huì)保存原始類對(duì)象,通過反射的方式去調(diào)用原始類中的方法。這里通過模擬的方式實(shí)際上代理類中除了繼承隱式地保存一個(gè)原始類對(duì)象之外,還顯式地保存了一個(gè)原始類對(duì)象,因?yàn)?super 并不能夠和 this 一樣可以獨(dú)立作為一個(gè)對(duì)象引用來使用。