JAVA提高第八篇 動(dòng)態(tài)代理技術(shù)
對(duì)于動(dòng)態(tài)代理,學(xué)過(guò)AOP的應(yīng)該都不會(huì)陌生,因?yàn)榇硎菍?shí)現(xiàn)AOP功能的核心和關(guān)鍵技術(shù)。那么今天我們將開(kāi)始動(dòng)態(tài)代理的學(xué)習(xí):
一、引出動(dòng)態(tài)代理
生活中代理應(yīng)該是很常見(jiàn)的,比如你可以通過(guò)代理商去買(mǎi)電腦,也可以直接找廠商買(mǎi)電腦,最終都是買(mǎi)到了電腦。程序中也一樣存在代理的情況,比如要為已經(jīng)存在的多個(gè)具有相同接口的目標(biāo)類的各個(gè)方法增加一些系統(tǒng)功能,例如:異常處理、日志、計(jì)算方法耗時(shí)等等,那么我們會(huì)怎么做呢?
1.會(huì)編寫(xiě)一個(gè)與目標(biāo)類擁有相同接口的代理類,代理類的每個(gè)方法調(diào)用目標(biāo)類的相同方法,然后在調(diào)用方法前后加上系統(tǒng)功能所需要的代碼。
2.如果采用工廠模式或者配置文件的方式進(jìn)行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標(biāo)類、還是代理類,這樣以后很容易切換。
樣例如下:
public class X{ public void sayHello(){ syso:say hello; } }
現(xiàn)在我要在這個(gè)方法之前添加一個(gè)時(shí)間,方法之后添加一個(gè)時(shí)間,計(jì)算這個(gè)方法執(zhí)行的時(shí)間一共是多少.如果我沒(méi)有得到sayHello源碼,那么我怎么做呢?寫(xiě)一個(gè)代理:
public class XProxy { private X x; public void sayHello { startTime: x.syHello(); endTime; } }
說(shuō)明:上面的是偽代碼。
把開(kāi)始時(shí)間和結(jié)束時(shí)間放在這個(gè)方法的前后就可以了.
通常我們讓兩個(gè)方法實(shí)現(xiàn)同一個(gè)接口.那么client想用X也可以,想用XProxy也可以了.具體的原理圖,如下圖所示:
二、創(chuàng)建動(dòng)態(tài)代理類
現(xiàn)在試想一下,上面只是代理了一個(gè)目標(biāo)類,如果多個(gè)目標(biāo)類,那么是不是要?jiǎng)?chuàng)建N多個(gè)代理類呢?那樣不是代碼太不靈活且笨重了。當(dāng)然不會(huì)。
java虛擬機(jī)可以在運(yùn)行期間動(dòng)態(tài)生成類,這種類是以字節(jié)碼的形式生成出來(lái)的。這種動(dòng)態(tài)生成的類往往呢就是代理類。即動(dòng)態(tài)代理類。
JVM生成的動(dòng)態(tài)代理類必須滿足一定的條件,這就是必須實(shí)現(xiàn)一個(gè)或多個(gè)接口。所以JVM生成的動(dòng)態(tài)代理只能用作具有相同接口的目標(biāo)類的代理。(動(dòng)態(tài)生成的類不是代理,我們只是吧這個(gè)類當(dāng)成代理來(lái)用。)
Proxy動(dòng)態(tài)代理的API:
兩個(gè)參數(shù)應(yīng)該很容易理解:
第一個(gè)參數(shù):我們知道任何一個(gè)字節(jié)碼都是需要通過(guò)類加載器來(lái)加載的,那么這個(gè)動(dòng)態(tài)生成的字節(jié)碼也不例外,需要給它一個(gè)類加載器。
第二個(gè)參數(shù):就是動(dòng)態(tài)代理類生成,必須滿足的一個(gè)條件,需要實(shí)現(xiàn)一個(gè)或者多個(gè)接口,否則這個(gè)生成的類字節(jié)碼中就沒(méi)有方法了,沒(méi)有方法就失去了其功能意義。
下面我們動(dòng)手來(lái)創(chuàng)建一個(gè)動(dòng)態(tài)的代理類,大體思路為:
1.創(chuàng)建實(shí)現(xiàn)Collection接口的動(dòng)態(tài)類和查看其名稱,分析Proxy.getProxyClass方法的各個(gè)參數(shù)
2.編碼列出動(dòng)態(tài)類中的所有構(gòu)造方法和參數(shù)簽名
3.編碼列出動(dòng)態(tài)類中的所有方法和參數(shù)簽名
4.創(chuàng)建動(dòng)態(tài)類的實(shí)例對(duì)象:1)用反射獲取構(gòu)造方法 2)編寫(xiě)一個(gè)最簡(jiǎn)單的invocationHandle類 3)調(diào)用構(gòu)造方法創(chuàng)建動(dòng)態(tài)類的實(shí)例對(duì)象,并將編寫(xiě)的InvocationHandle類的實(shí)例對(duì)象傳遞進(jìn)去 4)打印創(chuàng)建對(duì)象和調(diào)用對(duì)象的沒(méi)有返回的方法和getClass方法,演示調(diào)用其他有返回值方法報(bào)告了異常。
5)將創(chuàng)建動(dòng)態(tài)類的實(shí)例對(duì)象的代理寫(xiě)成匿名內(nèi)部類方式,簡(jiǎn)化代碼。
樣例分步實(shí)現(xiàn)如下:
(1)首先我們來(lái)完成前面3步:
package study.javaenhance; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; public class ProxyTest { public static void main(String[] args) { Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class); System.out.println(clazzProxy1.getName()); //上面輸出的為一個(gè)類,那么一個(gè)類肯定有其構(gòu)造方法和方法,下面我們來(lái)列出來(lái) System.out.println("----------begin constructors list----------"); //1.列出構(gòu)造方法 Constructor[] constructors = clazzProxy1.getConstructors(); for (Constructor constructor : constructors) { String name = constructor.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = constructor.getParameterTypes(); for (Class clazzParam : clazzParams) { sBuilder.append(clazzParam.getName()).append(','); } if(clazzParams!=null && clazzParams.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } //2.列出這個(gè)類字節(jié)碼中的所有方法 System.out.println("----------begin methods list----------"); Method[] methods = clazzProxy1.getMethods(); for(Method method : methods){ String name = method.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = method.getParameterTypes(); for(Class clazzParam : clazzParams){ sBuilder.append(clazzParam.getName()).append(','); } if(clazzParams!=null && clazzParams.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } } }
輸出結(jié)果如下:
$Proxy0
----------begin constructors list----------
$Proxy0(java.lang.reflect.InvocationHandler)
----------begin methods list----------
add(java.lang.Object)
hashCode()
clear()
equals(java.lang.Object)
toString()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()
可以看到所有的方法均是來(lái)自Collection 和父類Object中的方法,符合我們的預(yù)期結(jié)果。接下來(lái)我們進(jìn)入第四步和第五步的實(shí)現(xiàn):
首先,我們來(lái)創(chuàng)建這個(gè)動(dòng)態(tài)代理類的實(shí)例。那么直接clazzProxy1.newInstance();可不可以呢?顯然是不可以的嘛.我們剛剛說(shuō)了,動(dòng)態(tài)生成的這個(gè)代理類只有一個(gè)構(gòu)造方法,有沒(méi)有無(wú)參構(gòu)造方法呢?沒(méi)有啊.所以,創(chuàng)建 一個(gè)參數(shù)的構(gòu)造方法.參數(shù)類型是java.lang.reflect.InvocationHandler。
下面我們來(lái)實(shí)現(xiàn):
1.定義一個(gè)上述接口的實(shí)例:
package study.javaenhance; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }
2.調(diào)用實(shí)現(xiàn)創(chuàng)建實(shí)例動(dòng)態(tài)類:
//3.創(chuàng)建實(shí)例對(duì)象 System.out.println("----------begin create instance object----------"); Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); Collection collection = (Collection) constructor.newInstance(myInvocationHandler); System.out.println(collection); collection.clear(); //collection.size(); //報(bào)錯(cuò),異常會(huì)發(fā)生,產(chǎn)生異常的原因在于其返回值為int類型,但是每一次調(diào)用一個(gè)方法都會(huì)調(diào)用到invoke方法,我們此時(shí)的invoke返回的為null,所以是沒(méi)有辦法轉(zhuǎn)換為int類型的。
3.我們采用匿名內(nèi)部類的方式優(yōu)化:
//3.1 采用匿名內(nèi)部類方式進(jìn)行創(chuàng)建 Collection collection2 = (Collection) constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); System.out.println(collection2); collection2.clear(); //collection2.size();
4.繼續(xù)優(yōu)化:思考如果我們每次都想上面方式去創(chuàng)建動(dòng)態(tài)的代理類實(shí)在有點(diǎn)重復(fù),那么這個(gè)是JDK的Proxy類中提供了簡(jiǎn)單的方法直接去創(chuàng)建動(dòng)態(tài)代理類,方式如下:
//3.3 采用Proxy 中提供的簡(jiǎn)單方法創(chuàng)建 Collection collection3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() { private ArrayList target = new ArrayList(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(target, args); } }); //System.out.println(collection3); collection3.add("abc"); collection3.add("def"); collection3.add("hij"); System.out.println(collection3.size());
三、動(dòng)態(tài)代理的原理簡(jiǎn)單分析
上面我們創(chuàng)建了動(dòng)態(tài)代理類,下面我們分析下代理的原理:
下面在來(lái)看一個(gè)問(wèn)題:
動(dòng)態(tài)代理的工作原理圖:
對(duì)上面的這個(gè)圖,我們簡(jiǎn)單來(lái)說(shuō)說(shuō):客戶端動(dòng)態(tài)生成代理類,然后調(diào)用代理類的方法,代理類內(nèi)部調(diào)用handler.invoke()方法,在invoke中呢,我們又指向的目標(biāo)類.這樣就實(shí)現(xiàn)了代理了.我客戶端調(diào)用代理的什么方法,invoke就只向目標(biāo)類的同一個(gè)方法.而在指定目標(biāo)類方法的前后呢,我們還可以做其他的操作,比如記錄日志.圖中用圈圈出來(lái)的部分就是代理類自己實(shí)現(xiàn)的功能了.這就是代理類的原理.
我們來(lái)做最后一步,將上面的動(dòng)態(tài)生成的代理類,編寫(xiě)可生成代理和插入通告的通用方法:
test代碼:
package study.javaenhance; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; public class ProxyTest { public static void main(String[] args) throws Exception { Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class); System.out.println(clazzProxy1.getName()); //上面輸出的為一個(gè)類,那么一個(gè)類肯定有其構(gòu)造方法和方法,下面我們來(lái)列出來(lái) System.out.println("----------begin constructors list----------"); //1.列出構(gòu)造方法 Constructor[] constructors = clazzProxy1.getConstructors(); for (Constructor constructor : constructors) { String name = constructor.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = constructor.getParameterTypes(); for (Class clazzParam : clazzParams) { sBuilder.append(clazzParam.getName()).append(','); } if(clazzParams!=null && clazzParams.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } //2.列出這個(gè)類字節(jié)碼中的所有方法 System.out.println("----------begin methods list----------"); Method[] methods = clazzProxy1.getMethods(); for(Method method : methods){ String name = method.getName(); StringBuilder sBuilder = new StringBuilder(name); sBuilder.append('('); Class[] clazzParams = method.getParameterTypes(); for(Class clazzParam : clazzParams){ sBuilder.append(clazzParam.getName()).append(','); } if(clazzParams!=null && clazzParams.length != 0) sBuilder.deleteCharAt(sBuilder.length()-1); sBuilder.append(')'); System.out.println(sBuilder.toString()); } //3.創(chuàng)建實(shí)例對(duì)象 System.out.println("----------begin create instance object----------"); Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); Collection collection = (Collection) constructor.newInstance(myInvocationHandler); System.out.println(collection); collection.clear(); //collection.size(); //報(bào)錯(cuò),異常會(huì)發(fā)生,產(chǎn)生異常的原因在于其返回值為int類型,但是每一次調(diào)用一個(gè)方法都會(huì)調(diào)用到invoke方法,我們此時(shí)的invoke返回的為null,所以是沒(méi)有辦法轉(zhuǎn)換為int類型的。 //3.1 采用匿名內(nèi)部類方式進(jìn)行創(chuàng)建 Collection collection2 = (Collection) constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); System.out.println(collection2); collection2.clear(); //collection2.size(); //3.3 采用Proxy 中提供的簡(jiǎn)單方法創(chuàng)建 Collection collection3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() { private ArrayList target = new ArrayList(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(target, args); } }); //System.out.println(collection3); collection3.add("abc"); collection3.add("def"); collection3.add("hij"); System.out.println(collection3.size()); System.out.println(collection3.getClass().getName());//這個(gè)返回的是 System.out.println("----------begin create instance object 抽化----------"); //3.4抽出動(dòng)態(tài)代理讓目標(biāo)對(duì)象和切面對(duì)象都是傳入進(jìn)去的 final ArrayList target = new ArrayList(); Collection collection4 = (Collection)getProxy(target,new MyAdvice()); collection4.add("test1"); collection4.add("test2"); System.out.println(collection4.size()); } private static Object getProxy(final Object target,final Advice advice) { Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeMethod(method); Object retValue = method.invoke(target, args); advice.afterMethod(method); return retValue; } }); return object; } }
四、實(shí)現(xiàn)類似Spring的可配置的AOP框架
首先,我們要完成的要求如下:
我們來(lái)模擬Spring的工廠模式讀取從配置文件傳遞過(guò)來(lái)的類。如果這個(gè)類是一個(gè)普通類則直接返回。如果是一個(gè)代理類,則創(chuàng)建代理對(duì)象,返回代理類。
具體的理論知識(shí)可以看上面的圖片
首先創(chuàng)建一個(gè)BeanFactory.java類。這是一個(gè)Bean工廠,用于讀取配置文件中的類
package study.javaenhance.aopframework; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import study.javaenhance.Advice; public class BeanFactory { private Properties properties = new Properties(); public BeanFactory(InputStream inStream) { try { properties.load(inStream); } catch (IOException e) { e.printStackTrace(); } finally { if(inStream != null) { try { inStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } public Object getBean(String name) { String className = properties.getProperty(name); Object bean = null; try { Class clazz = Class.forName(className); bean = clazz.newInstance(); if(bean instanceof ProxyFactoryBean) { ProxyFactoryBean proxyBean = (ProxyFactoryBean) bean; Advice advice = (Advice)Class.forName(properties.getProperty(name + ".advice")).newInstance(); Object target = Class.forName(properties.getProperty(name + ".target")).newInstance(); proxyBean.setAdvice(advice); proxyBean.setTarget(target); Object proxy = proxyBean.getProxy(); return proxy; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return bean; } }
如果這個(gè)類中包含ProxyFactoryBean,則調(diào)用ProxyFactoryBean中的getProxy方法。動(dòng)態(tài)生成代理類。
ProxyFactoryBean.java
package study.javaenhance.aopframework; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import study.javaenhance.Advice; public class ProxyFactoryBean { private Object target; private Advice advice; public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } public Advice getAdvice() { return advice; } public void setAdvice(Advice advice) { this.advice = advice; } public Object getProxy() { Object object = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeMethod(method); Object retValue = method.invoke(target, args); advice.afterMethod(method); return retValue; } }); return object; } }
接下來(lái)創(chuàng)建一個(gè)config.Properties文件.config.Properties
xxx=java.util.ArrayList
#xxx=study.javaenhance.aopframework.ProxyFactoryBean
xxx.advice=study.javaenhance.MyAdvice
xxx.target=java.util.ArrayList
最后建立測(cè)試類:
package study.javaenhance.aopframework; import java.io.InputStream; import java.util.Collection; public class AopFrameworkTest { public static void main(String[] args) { InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties"); Object bean = new BeanFactory(ips).getBean("xxx"); System.out.println(bean.getClass().getName()); ((Collection)bean).clear(); } }
測(cè)試OK
總結(jié):整個(gè)java增強(qiáng)的視頻學(xué)習(xí)完成了,一共記住了多少我也不知道.但知道了很多內(nèi)在的知識(shí),如果有時(shí)間的話,或者說(shuō)過(guò)一段時(shí)間可以拿出來(lái)問(wèn)下,提供自己的技能。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Java設(shè)計(jì)模式之動(dòng)態(tài)代理模式實(shí)例分析
- Java動(dòng)態(tài)代理(設(shè)計(jì)模式)代碼詳解
- Java中的引用和動(dòng)態(tài)代理的實(shí)現(xiàn)詳解
- 淺談Java注解和動(dòng)態(tài)代理
- Java動(dòng)態(tài)代理機(jī)制的實(shí)例詳解
- Java CGLib動(dòng)態(tài)代理機(jī)制(全面解析)
- Java JDK 動(dòng)態(tài)代理的使用方法示例
- Java JDK動(dòng)態(tài)代理(AOP)的實(shí)現(xiàn)原理與使用詳析
- java 1.8 動(dòng)態(tài)代理源碼深度分析
- Java設(shè)計(jì)模式之動(dòng)態(tài)代理
相關(guān)文章
Java OpenCV圖像處理之仿射變換,透視變換,旋轉(zhuǎn)詳解
這篇文章主要為大家詳細(xì)介紹了Java OpenCV圖像處理中仿射變換,透視變換,旋轉(zhuǎn)的實(shí)現(xiàn),文中的示例代碼講解詳細(xì),快跟隨小編一起學(xué)習(xí)一下2022-10-10出現(xiàn)java.util.ConcurrentModificationException 問(wèn)題及解決辦法
這篇文章主要介紹了出現(xiàn)java.util.ConcurrentModificationException 問(wèn)題及解決辦法的相關(guān)資料,需要的朋友可以參考下2017-02-02Java Lombok簡(jiǎn)介、使用、工作原理、優(yōu)缺點(diǎn)
這篇文章主要介紹了Java Lombok簡(jiǎn)介、使用、工作原理、優(yōu)缺點(diǎn)的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用Java Lombok,感興趣的朋友可以了解下2021-03-03Java中Scanner類與BufferReader類的不同點(diǎn)(非常詳細(xì))
這篇文章主要介紹了Java中Scanner類與BufferReader類的不同點(diǎn)(非常詳細(xì))的相關(guān)資料,需要的朋友可以參考下2016-08-08springboot+rabbitmq實(shí)現(xiàn)指定消費(fèi)者才能消費(fèi)的方法
當(dāng)項(xiàng)目部署到測(cè)試環(huán)境后,QA測(cè)試過(guò)程中,總是“莫名其妙”的發(fā)現(xiàn)所保存的用戶付款單數(shù)據(jù)有問(wèn)題。這篇文章主要介紹了springboot+rabbitmq實(shí)現(xiàn)指定消費(fèi)者才能消費(fèi),需要的朋友可以參考下2021-11-11簡(jiǎn)單了解redis常見(jiàn)客戶端及Sharding機(jī)制原理
這篇文章主要介紹了簡(jiǎn)單了解redis常見(jiàn)客戶端及Sharding機(jī)制原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09SpringCloud分布式鏈路追蹤組件Sleuth配置詳解
這篇文章主要介紹了SpringCloud鏈路追蹤組件Sleuth配置方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-11-11在已有spring的基礎(chǔ)上集成hibernate的實(shí)例講解
下面小編就為大家?guī)?lái)一篇在已有spring的基礎(chǔ)上集成hibernate的實(shí)例講解。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11