您好,登錄后才能下訂單哦!
這篇文章主要介紹“什么是靜態(tài)代理與動態(tài)代理”,在日常操作中,相信很多人在什么是靜態(tài)代理與動態(tài)代理問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”什么是靜態(tài)代理與動態(tài)代理”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
開場
一位穿著藍(lán)色襯衫,牛仔褲,拿著一個白色保溫杯的中年男子急匆匆地坐在你對面,看樣子是項目上的東西很急,估摸面試時間不會太長,這樣一想心情放松了許多......(后來我就被打臉了)
面試開始
面試官:小伙子,我看你的簡歷上說精通java基礎(chǔ)對吧,那我先簡單來問幾個java基礎(chǔ)。
好的好的,面試官你問。(一聽到簡單兩個字就內(nèi)心竊喜......)
面試官:你知道Java中有個東西叫代理嗎?
知道知道,代理就是通過代理對象去訪問實際的目標(biāo)對象,比如我們在生活中租房,可以直接找房東,也可以通過某些租房平臺去租房,通過租房平臺的這種方式就是代理。在java中這種租房平臺就被叫做代理類,代理類不僅能實現(xiàn)目標(biāo)對象,還能增加一些額外的功能。據(jù)我所知java中的代理方式有靜態(tài)代理和動態(tài)代理。(這個時候面試官很大概率會問你這兩種代理模式)。
面試官:沒想到你還能通過生活中的現(xiàn)象去理解代碼,不錯不錯,我看你提到了靜態(tài)代理和動態(tài)代理,那你給我說說什么是靜態(tài)代理吧
(果然問了,還好我做了準(zhǔn)備)靜態(tài)代理就是在代碼運行之前,這個代理類就已經(jīng)存在了。還是以上面的租房為例,在代碼中會首先創(chuàng)建一個通用的租房接口:
public interface Room { void rent(); }
然后需要有一個被代理的類(或者稱為真實的類)和一個代理類:
public class RealRoom implements Room { private String roomname; public RealRoom(String roomname) { this.roomname = roomname; } public void rent() { System.out.println("租了"+roomname); } }
代理類如下:
public class ProxyClass implements Room { RealRoom realRoom; public ProxyClass(RealRoom realRoom) { this.realRoom = realRoom; } public void rent() { System.out.println("租房前收取中介費"); realRoom.rent(); System.out.println("租房后收取服務(wù)費"); } }
代理類可以在不改變被代理對象的情況下增加功能,最后我們測試一下這個靜態(tài)代理:
public class Main { public static void main(String[] args) { RealRoom realRoom =new RealRoom("碧桂園"); ProxyClass proxyClass=new ProxyClass(realRoom); proxyClass.rent(); } }
然后觀察結(jié)果:
租房前收取中介費 租了碧桂園 租房后收取服務(wù)費
面試官:既然靜態(tài)代理那么強大,那他有什么缺點嗎?
由于靜態(tài)代理在代碼運行之前就已經(jīng)存在代理類,因此對于每一個代理對象都需要建一個代理類去代理,當(dāng)需要代理的對象很多時就需要創(chuàng)建很多的代理類,嚴(yán)重降低程序的可維護(hù)性。用動態(tài)代理就可以解決這個問題。
面試官:那你給我講一講動態(tài)代理吧
動態(tài)代理是指代理類不是寫在代碼中,而是在運行過程中產(chǎn)生的,java提供了兩種實現(xiàn)動態(tài)代理的方式,分別是基于Jdk的動態(tài)代理和基于Cglib的動態(tài)代理。
面試官:基于JDK的動態(tài)代理我忘了,你給我復(fù)習(xí)復(fù)習(xí)。
(我???算了算了) 實現(xiàn)Jdk的動態(tài)代理需要實現(xiàn)InvocationHandler接口,然后實現(xiàn)其中的invoke方法。如果代理的方法被調(diào)用,那么代理便會通知和轉(zhuǎn)發(fā)給內(nèi)部的 InvocationHandler 實現(xiàn)類invoke,由它實現(xiàn)處理內(nèi)容。
public class ProxyHandler implements InvocationHandler { Object object; public ProxyHandler(Object object) { this.object = object; } //proxy 代理對象 //method 要實現(xiàn)的方法 //args 方法的參數(shù) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理執(zhí)行之前:"+method.getName()); Object invoke = method.invoke(object, args); System.out.println("代理執(zhí)行之后:"+method.getName()); return invoke; } }
接下來在main方法中執(zhí)行動態(tài)代理
public static void main(String[] args) { Room room=new RealRoom("碧桂園"); //obj.getClass().getClassLoader()類加載器 //obj.getClass().getInterfaces() 目標(biāo)類實現(xiàn)的接口 //InvocationHandler對象 InvocationHandler invocationHandler=new ProxyHandler(room); Room proxyRoom = (Room) Proxy.newProxyInstance(room.getClass().getClassLoader(), room.getClass().getInterfaces(), invocationHandler); proxyRoom.rent(); }
這段代碼的核心是Proxy.newProxyInstance,目的是運行期間生成代理類,最后通過代理類執(zhí)行被代理的方法。最后結(jié)果如下:
代理執(zhí)行之前:rent 租了碧桂園 代理執(zhí)行之后:rent
面試官:被你這么一說我想起來動態(tài)代理了,那他的優(yōu)勢呢?
之前我講靜態(tài)代理的時候說靜態(tài)代理的缺點在于對于每一個被代理的對象,都需要建一個代理類。因為靜態(tài)代理是在項目運行前就寫好的。但是動態(tài)代理就不是這樣,由于動態(tài)代理在運行時才創(chuàng)建代理類,因此只需要寫一個動態(tài)代理類就好。比如我再創(chuàng)建一個被代理的對象賣房:
寫一個通用接口Sell
public interface Sell { void sellRoom(); }
接著還是寫一個被代理對象的類:
public class RealSell implements Sell { public void sellRoom() { System.out.println("賣房了"); } }
接下來在main方法中執(zhí)行動態(tài)代理
public static void main(String[] args) { Sell sell=new RealSell(); InvocationHandler invocationHandler=new ProxyHandler(sell); Sell proxysell= (Sell) Proxy.newProxyInstance(sell.getClass().getClassLoader(),sell.getClass().getInterfaces(),invocationHandler); proxysell.sellRoom(); }
最終實現(xiàn)結(jié)果如下:
代理執(zhí)行之前:sellRoom 賣房了 代理執(zhí)行之后:sellRoom
通過動態(tài)代理,我可以通過一個動態(tài)代理類,去代理多個對象。
面試官:如果我記的沒錯,通過這種方式只能代理接口吧,我看你上面的例子也都是代理接口,那我如果想代理類該怎么辦呢?
jdk動態(tài)代理確實只能代理接口,JDK動態(tài)代理是基于接口的方式,換句話來說就是代理類和目標(biāo)類都實現(xiàn)同一個接口。如果想要代理類的話可以使用CGLib,CGLib動態(tài)代理是代理類去繼承目標(biāo)類,然后實現(xiàn)目標(biāo)類的方法。
創(chuàng)建一個目標(biāo)類CGRoom
public class CGRoom { public void rent(String roomName){ System.out.println("租了"+roomName); } }
創(chuàng)建cglib的動態(tài)代理類,繼承MethodInterceptor ,實現(xiàn)其中的intercept方法
public class MyMethodInterceptor implements MethodInterceptor { public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("代理執(zhí)行之前:"+method.getName()); Object object=methodProxy.invokeSuper(o,objects); System.out.println("代理執(zhí)行之后:"+method.getName()); return object; } }
最后通過enhance對象來創(chuàng)建代理類
public static void main(String[] args) { //創(chuàng)建Enhancer對象,類似于JDK動態(tài)代理的Proxy類,下一步就是設(shè)置幾個參數(shù) Enhancer enhancer=new Enhancer(); //設(shè)置目標(biāo)類的字節(jié)碼文件 enhancer.setSuperclass(CGRoom.class); //設(shè)置回調(diào)函數(shù) enhancer.setCallback(new MyMethodInterceptor()); //創(chuàng)建代理對象 CGRoom proxy= (CGRoom) enhancer.create(); proxy.rent("碧桂園"); }
最終實現(xiàn)以下結(jié)果:
代理執(zhí)行之前:rent 租了碧桂園 代理執(zhí)行之后:rent
面試官:既然動態(tài)代理被你說的這么牛,那你平常工作中有使用到嗎?
平常我的業(yè)務(wù)代碼中雖然幾乎沒有使用過動態(tài)代理,但是我工作中使用的Spring系列框架中的AOP,以及RPC框架中都用到了動態(tài)代理,以AOP為例,AOP通過動態(tài)代理對目標(biāo)對象進(jìn)行了增強,比如我們最常用的前置通知、后置通知等。
面試官:不錯!下面再考你幾個基礎(chǔ),說說你對注解的理解,注解又解決了哪些問題?
Java語言中的類、方法、變量、參數(shù)和包都可以用注解標(biāo)記,程序運行過程中我們可以獲取到相應(yīng)的注解以及注解中定義的內(nèi)容,比如說 Spring 中如果檢測到說你的類被 @Component注解標(biāo)記的話,Spring 容器在啟動的時候就會把這個類歸為自己管理,這樣你就可以通過 @Autowired注解注入這個對象了。
面試官:那你知道如何自己去定義注解嗎?
知道知道,自定義注解主要有以下四步:
第一步通過@interface聲明注解:
public @interface Myannotation { String key() default ""; }
第二步通過四種元注解修飾注解:(面試的時候說出這四種注解就可以了)
元注解的作用就是負(fù)責(zé)其他注解,java中一共有四個元注解,分別是@Target,@Retention,@Documented,@Inherited,下面先介紹以下四種注解的作用:
@Target:Target說明了注解所修飾的對象范圍,取值(ElementType)有:
用于描述構(gòu)造器
用于描述屬性
用于描述局部變量
用于描述方法
用于描述包
用于描述參數(shù)
用于描述類、接口(包括注解類型)或者enum聲明
@Retention:Retention定義了注解的保留范圍,取值(RetentionPoicy)有:
在源文件中有效(即源文件保留)
在class文件中有效(即class保留)
在運行時有效(即運行時保留)
@Documented:Documented用于描述其它類型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標(biāo)記注解,沒有成員。
@Inherited:Inherited 元注解是一個標(biāo)記注解,@Inherited闡述了某個被標(biāo)注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用于一個class,則這個annotation將被用于該class的子類。
@Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface Myannotation { String key() default ""; }
第三步使用注解,因為定義Target時定義了MEHTOD和FIELD,因此可以在屬性和方法中使用這個注解:
public class MyannotationTest { @Myannotation(key = "javayz") private String username; }
第四步利用反射解析注解
public static void main(String[] args) { Class myclass=MyannotationTest.class; Field[] fields = myclass.getDeclaredFields(); for (Field field :fields){ if (field.isAnnotationPresent(Myannotation.class)){ System.out.println("配置了自定義注解"); Myannotation annotation = field.getAnnotation(Myannotation.class); System.out.println("屬性:"+field.getName()+"上的注解key為"+annotation.key()); } } }
輸出結(jié)果:
配置了自定義注解 屬性:username上的注解key為javayz
面試官:我看你上面第四步提到了反射是吧?那你給我講講什么是反射,它有啥特點:
(我暈,我就說了反射兩個字啊,還好有準(zhǔn)備)JAVA 反射機制是在運行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意一個方法和屬性;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為 java 語言的反射機制。
在上面第四步利用反射解析注解中,我通過MyannotationTest.class獲取到了MyannotationTest的類對象,又用myclass.getDeclaredFields();獲取到了所有的屬性。這就是反射。
到此,關(guān)于“什么是靜態(tài)代理與動態(tài)代理”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。