最近在閱讀到了Spring源碼對(duì)于兩種動(dòng)態(tài)代理使用在不同場(chǎng)景下的使用,兩種方式各有利弊寫一篇文加深自己的認(rèn)識(shí)。文中對(duì)于源碼的涉及較少,更多的是作者自己的理解和舉例,然后通過部分源碼驗(yàn)證。
成都創(chuàng)新互聯(lián)自2013年創(chuàng)立以來,公司以成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、系統(tǒng)開發(fā)、網(wǎng)絡(luò)推廣、文化傳媒、企業(yè)宣傳、平面廣告設(shè)計(jì)等為主要業(yè)務(wù),適用行業(yè)近百種。服務(wù)企業(yè)客戶近千家,涉及國(guó)內(nèi)多個(gè)省份客戶。擁有多年網(wǎng)站建設(shè)開發(fā)經(jīng)驗(yàn)。為企業(yè)提供專業(yè)的網(wǎng)站建設(shè)、創(chuàng)意設(shè)計(jì)、宣傳推廣等服務(wù)。 通過專業(yè)的設(shè)計(jì)、獨(dú)特的風(fēng)格,為不同客戶提供各種風(fēng)格的特色服務(wù)。首先看兩個(gè)面試經(jīng)常會(huì)遇到的關(guān)于Spring的問題:
@Configuration和@Component注解的不同
@Configuration修飾的類會(huì)被Cglib動(dòng)態(tài)代理,在類內(nèi)部方法相互調(diào)用添加了@Bean注解的方法時(shí)通過在切面方法中調(diào)用getBean()方法來保證調(diào)用該方法返回的都是同一個(gè)實(shí)例
@Component修飾的類不會(huì)被代理,每次方法內(nèi)部調(diào)用都會(huì)生成新的實(shí)例,這樣就不能保證其生成的對(duì)象是一個(gè)單例對(duì)象。
@Transactional失效的原因
@Transactional可以JDK或Cglib動(dòng)態(tài)代理實(shí)現(xiàn)的事務(wù)(默認(rèn)JDK),在Bean創(chuàng)建時(shí)如果檢測(cè)到類中有@Transactional就會(huì)對(duì)其進(jìn)行動(dòng)態(tài)代理,如果類內(nèi)部沒有被@Transactional修飾的方法中調(diào)用了其它被@Transactional修飾的內(nèi)部方法,那么此時(shí)事務(wù)注解是不會(huì)生效的,原因在于只有外部調(diào)用才會(huì)走代理增強(qiáng)邏輯而內(nèi)部類的互相調(diào)用只是原對(duì)象的方法調(diào)用,沒有經(jīng)過代理類。
其實(shí)上面可以看出出Spring在使用兩種代理方式時(shí)的不同處理:@Configuration修飾的類被Cglib動(dòng)態(tài)代理后,類內(nèi)部方法調(diào)用也可以走增強(qiáng)邏輯,而含有@Transactional注解的類無論是Cglib還是JDK動(dòng)態(tài)代理都不能進(jìn)行方法內(nèi)部的相互調(diào)用。
兩種代理方式的調(diào)用邏輯 JDK動(dòng)態(tài)代理標(biāo)準(zhǔn)的用法
public interface TestInterface { void sayHello(); } public static class Test implements TestInterface { @Override public void sayHello() { System.out.println("hello"); } } //需要定義一個(gè)實(shí)現(xiàn)了InvocationHandler接口的對(duì)象,目的是獲取被代理類的實(shí)例和自定義增強(qiáng)方法 public static class MyInvocationHandler implements InvocationHandler{ / /被代理類的實(shí)例對(duì)象 protected Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("增強(qiáng)方法"); //調(diào)用被代理類的實(shí)例對(duì)象通過反射執(zhí)行目標(biāo)方法 Object result = method.invoke(target, args); return result; } } public static void main(String[] args) { Test test = new Test(); TestInterface testInterface = (TestInterface) Proxy.newProxyInstance(test.getClass().getClassLoader(), Test.class.getInterfaces(), new MyInvocationHandler(test)); testInterface.sayHello(); } 復(fù)制代碼
下面是代理類的邏輯代碼,這個(gè)代理類并不是用反編譯內(nèi)存中的代理類來獲取得,是作者自己整了一個(gè)類似的,如果要獲取真正的代理類代碼網(wǎng)上方法很多
//代理類的父類,里面有生成代理類的主要邏輯 public static class Proxy{ //被代理對(duì)象實(shí)例的調(diào)用對(duì)象 protected InvocationHandler h; } //生成的代理類繼承Proxy主要是為了使用父類中的InvocationHandler對(duì)象來調(diào)用被代理類對(duì)象的目標(biāo)方法 //實(shí)現(xiàn)共同接口是為了獲取需要增強(qiáng)的目標(biāo)方法 public static class TestProxy extends Proxy implements TestInterface{ protected TestProxy(InvocationHandler h) { super(h); } @Override public void sayHello() { try { //這里對(duì)獲取接口方法做了簡(jiǎn)化處理 //調(diào)用父類中存儲(chǔ)的被代理對(duì)象的handler執(zhí)行代理邏輯 super.h.invoke(this, this.getClass().getInterfaces()[0].getMethods()[0],null); } catch (Throwable e) { throw new RuntimeException(e); } } } 復(fù)制代碼
邏輯圖
Cglib動(dòng)態(tài)代理標(biāo)準(zhǔn)的用法
public static class Test implements TestInterface { @Override public void sayHello() { System.out.println("hello"); } } //實(shí)現(xiàn)MethodInterceptor接口注冊(cè)回調(diào)函數(shù)對(duì)代理類中所有方法進(jìn)行攔截增強(qiáng) public static class MyInvocationHandler implements MethodInterceptor { //o為繼承了被代理類的代理類對(duì)象,method為執(zhí)行方法,objects為方法參數(shù) //methodProxy為代理對(duì)象方法,其中有被代理方法和代理方法的映射關(guān)系 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("增強(qiáng)方法"); return methodProxy.invokeSuper(o,objects); } } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Test.class); enhancer.setCallback(new MyInvocationHandler()); TestInterface testInterface = (TestInterface)enhancer.create(); testInterface.sayHello(); } 復(fù)制代碼
動(dòng)態(tài)生成代理類的偽代碼,省略了很多很多細(xì)節(jié)
//Cglib中都是通過代理類中的方法來替換被代理類,然后直接調(diào)用代理類對(duì)象的方法即可 public static class TestProxy extends Test{ public final void sayHello() { System.out.println("增強(qiáng)方法"); super.sayHello(); } } 復(fù)制代碼
邏輯圖
?==============================================================
從上面可以看出Cglib和JDK大的區(qū)別在于Cglib實(shí)現(xiàn)的動(dòng)態(tài)代理并沒有被代理類的實(shí)例對(duì)象,所有的方法調(diào)用都是通過代理類來實(shí)現(xiàn)的(子類方法 ->增強(qiáng)邏輯 ->子類代理方法 ->父類方法),而JDK則同時(shí)生成了被代理類和代理類的實(shí)例對(duì)象,然后在代理類中保存有被代理類的引用,目標(biāo)方法的調(diào)用還是被代理對(duì)象執(zhí)行的。Cglib方法調(diào)用時(shí)是使用代理類對(duì)象內(nèi)部方法的相互調(diào)用實(shí)現(xiàn)的,由于代理類的所有方法都進(jìn)行了改寫,所以內(nèi)部調(diào)用也會(huì)被增強(qiáng),JDK方法調(diào)用時(shí)是代理類對(duì)象和被代理類對(duì)象間方法的相互調(diào)用實(shí)現(xiàn)的,只有通過調(diào)用代理類對(duì)象的代理方法時(shí)才會(huì)走增強(qiáng)邏輯,而如果是被代理對(duì)象自己的內(nèi)部調(diào)用,被代理對(duì)象方法沒有改變,所以無法增強(qiáng)。 理解了這一點(diǎn)再看Spring動(dòng)態(tài)代理的使用就好理解了
Spring源碼驗(yàn)證調(diào)用@Configuration注解的類時(shí)會(huì)用到的代理類攔截器
//Spring中Enhancer對(duì)象注冊(cè)的三種攔截器 //回調(diào)數(shù)組,根據(jù)CALLBACK_FILTER中accept方法返回的索引值去從該數(shù)組中選擇對(duì)應(yīng)的Callback private static final Callback[] CALLBACKS = new Callback[] { new BeanMethodInterceptor(), new BeanFactoryAwareMethodInterceptor(), NoOp.INSTANCE }; //BeanMethodInterceptor的intercept方法,對(duì)父類中所有帶有@Bean注解的方法都進(jìn)行攔截增強(qiáng) //無論是Spring通過反射實(shí)例化Bean還是配置類中方法的內(nèi)部調(diào)用,都會(huì)通過BeanFactory來生成和獲取Bean實(shí)例 public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable { //是否為Spring通過反射調(diào)用 if (isCurrentlyInvokedFactoryMethod(beanMethod)) { //調(diào)用父類方法生成新的Bean對(duì)象,并將其注冊(cè)到Spring上下文中 return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); } //類方法的內(nèi)部調(diào)用,從BeanFactory中獲取bean //即使通過內(nèi)部方法直接調(diào)用為能保證獲取的對(duì)象為同一實(shí)例 return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName); } 復(fù)制代碼
Cglib對(duì)于@Transactional注解采用的代理類攔截器DynamicAdvisedInterceptor
@Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object oldProxy = null; boolean setProxyContext = false; Class>targetClass = null; Object target = null; try { if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } //Spring緩存了被代理類的實(shí)例 //獲取被代理類實(shí)例 target = getTarget(); if (target != null) { targetClass = target.getClass(); } //獲取目標(biāo)方法的攔截器鏈,被@Transactional修飾的方法會(huì)有緩存方法和調(diào)用鏈關(guān)系 List
JDK的處理邏輯同樣是調(diào)用被代理類來執(zhí)行未加@Transactional注解的方法,就不多寫了。
小結(jié)Cglib動(dòng)態(tài)代理與JDK動(dòng)態(tài)代理的區(qū)別本質(zhì)上應(yīng)該對(duì)于代理對(duì)象的調(diào)用方式有差別,Cglib是直接將代理類對(duì)象作為目標(biāo)對(duì)象使用,增強(qiáng)邏輯直接寫入代理類的子類方法中,調(diào)用方法時(shí)只需一個(gè)代理類對(duì)象即可,而JDK則是將被代理類對(duì)象引用存放在代理類對(duì)象中,增強(qiáng)邏輯在代理對(duì)象中存放而實(shí)際執(zhí)行方法還需要調(diào)用被代理對(duì)象。當(dāng)然Cglib通過緩存被代理類的實(shí)例對(duì)象也可以做到JDK的效果。 兩種代理方式一個(gè)通過繼承獲取類方法信息,一種通過接口獲取類方法信息,在理解其原理后,如何選型使用還是看業(yè)務(wù)場(chǎng)景和兩種方式的執(zhí)行效率來決定。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧