論JDK動態代理的重要性及自定義自己的動態代理
1、JDK的動態代理
動態代理中的幾個角色:
1、被代理類
在jdk中被代理類必須實現介面
2、代理類
在jdk中代理類是執行時動態生成的,所以你在工程中看不到代理類
3、通知類也叫advice
在jdk中通知類必須實現invocationHandler介面
在這裡我們舉個例子幫助理解
比如,張三是一個IT狗,到了適婚年齡但是沒有物件,平時工作忙又沒有時間找物件,這時候張三的父母就著急了張羅著給張三介紹物件,這時候張三的父母就拿著張三的照片、身份證等等可以代表張三的資料去媒婆那。這個就是最典型的代理,張三的父母代張三找物件,而不是張三本人,其實張三本人就只需要結婚就行了。找物件和照顧孩子的事情就有張三的父母代理了。
從這個例子中,我們提取出幾個點:
1、張三,肯定是被代理物件,他要做的一件事情就是結婚。這裡我們抽象下,張三是一個人,所以他需要實現people介面
2、張三的父母,張三的父母是具體的執行者,比如,代理張三找物件,張三結婚後幫忙張三照顧孩子,所以我們這裡抽象出一個parent類,parent中有一個before方法,一個after方法。parent需要實現invocationHandler介面,它是一個通知類,負責具體執行
3、代理類,這是動態代理的難點,因為它是看不到摸不著的,它是存在在記憶體中的,且聽博主慢慢道來。
OK~~~看程式碼吧。
people介面:
public interface People {
public void zhaoduixiang() throws Throwable;
}
張三類:
public class ZhangSan implements People { /* * 張三這個人,平時加班很忙, * 他有生理問題,要解放雙手 * 只關心有沒有一個幫他解放雙手這個人 * 至於怎麼去找,他不關心 */ @Override public void zhaoduixiang() { System.out.println("=============================找到物件,解放雙手(你們懂的),然後結婚====================="); } }
parent類:
/**
* @Description TODO
* @ClassName Parent
* @Date 2018年9月26日 下午8:12:35
* @Author zg-jack
*
* 這個就是張三的父母類
* 這個類的目的就是為了增強張三的zhaoduixiang()方法
*
*
* 張三的父母需要持有張三這個人的引用
*
*
*/
public class Parent implements InvocationHandler {
private People people;
public Parent(People people) {
this.people = people;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//前置增強
before();
//2、呼叫張三的這個人的zhaoduixiang方法
method.invoke(people, args);
//3、呼叫after()
after();
return null;
}
/**
* @Description TODO
* @param 引數
* @return void 返回型別
* @throws
*
* 在張三找到物件之前,幫助張三找物件
*/
public void before() {
System.out.println("&&&&&&&&&&&&&&&張三的父母,幫助張三找物件&&&&&&&&&&&&&&&&");
}
/**
* @Description TODO
* @param 引數
* @return void 返回型別
* @throws
*
* 找到物件要以後,張三父母幫他操持結婚
*/
public void after() {
System.out.println("&&&&&&&&&&&&&&&張三的父母,幫助張三操持結婚的事情&&&&&&&&&&&&&&&&");
}
}
測試類:
public class MyTest {
public static void main(String[] args) throws Throwable {
//這個返回的物件就是張三這個人的代理物件
People proxyPeople = (People)Proxy.newProxyInstance(MyTest.class.getClassLoader(),
new Class<?>[] {People.class},
new Parent(new ZhangSan()));
createProxyClassFile();
proxyPeople.zhaoduixiang();
}
/**
* @Description TODO
* @param 引數
* @return void 返回型別
* @throws
*
* 用流的方式把記憶體中的代理物件的位元組碼輸出到.class檔案中
*/
public static void createProxyClassFile() {
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",
new Class[] {People.class});
try {
FileOutputStream out = new FileOutputStream("$Proxy0.class");
out.write(data);
out.close();
}
catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
測試結果:
&&&&&&&&&&&&&&&張三的父母,幫助張三找物件&&&&&&&&&&&&&&&&
=============================找到物件,解放雙手(你們懂的),然後結婚=====================
&&&&&&&&&&&&&&&張三的父母,幫助張三操持結婚的事情&&&&&&&&&&&&&&&&
從這個測試結果,我們僅僅用了一行呼叫程式碼
proxyPeople.zhaoduixiang();就出現了這個結果,而這個結果明顯又是parent類中的invoke方法的列印結果,那麼這裡問題來了,誰呼叫了parent中的invoke方法???OK,帶著這個問題,我們看一下圖,從圖中瞭解動態代理的呼叫過程,回答前面的代理類問題,如圖:
從圖中我們可以看出,proxypeople.zhaoduixiang()呼叫的實際上是記憶體中的代理物件,這個物件是程式在執行時的時候動態載入到記憶體裡的,所以程式設計師看不到,那麼有什麼辦法可以看到這個記憶體中的代理物件的程式碼呢,有辦法~~~這個博主待會兒講。從圖中我們可以看出,帶代理物件中,有一個屬性,invocationHandler h屬性,而h屬性的值就是parent物件,而在代理物件中的方法zhaoduixiang中就只有一行代理,h.invoke(.......),其實就是掉到了parent物件中的invoke方法。。。這裡就回答了剛剛那個問題,誰調了parent物件的invoke方法? 這下我們知道了,是記憶體中的代理物件調到了invoke方法。。。
那麼還是回到前面的問題,記憶體中的代理物件,我們能拿到嗎??OK,貼程式碼
/**
* @Description TODO
* @param 引數
* @return void 返回型別
* @throws
*
* 用流的方式把記憶體中的代理物件的位元組碼輸出到.class檔案中
*/
public static void createProxyClassFile() {
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",
new Class[] {People.class});
try {
FileOutputStream out = new FileOutputStream("$Proxy0.class");
out.write(data);
out.close();
}
catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
我們可以拿到位元組碼,然後用流的方式輸出到.class檔案中,然後用反編譯工具反編譯一下就可以了。。
為了讓大家徹底領悟動態代理技術,我打算自己定義一個動態代理,不用jdk的proxy類和invocationHandler介面,這樣才能徹底領悟
貼程式碼吧:
自定義的JackInvocationHandler介面,類似於JDK中的InvocationHandler介面
public interface JackInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
最關鍵的是獲取代理物件,因為這個代理物件在記憶體裡面,在JDK中我們用Proxy這個類獲取代理物件
People proxyPeople = (People)Proxy.newProxyInstance(MyTest.class.getClassLoader(),
new Class<?>[] {People.class},
new Parent(new ZhangSan()));
那麼我們自己定義的動態代理裡面,這個代理物件是如何獲取呢??
MyParent類,類似於parent類
public class MyParent implements JackInvocationHandler {
private People people;
public MyParent(People people) {
this.people = people;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//前置增強
before();
//2、呼叫張三的這個人的zhaoduixiang方法
method.invoke(people, args);
//3、呼叫after()
after();
return null;
}
/**
* @Description TODO
* @param 引數
* @return void 返回型別
* @throws
*
* 在張三找到物件之前,幫助張三找物件
*/
public void before() {
System.out.println("&&&&&&&&&&&&&&&張三的父母,幫助張三找物件&&&&&&&&&&&&&&&&");
}
/**
* @Description TODO
* @param 引數
* @return void 返回型別
* @throws
*
* 找到物件要以後,張三父母幫他操持結婚
*/
public void after() {
System.out.println("&&&&&&&&&&&&&&&張三的父母,幫助張三操持結婚的事情&&&&&&&&&&&&&&&&");
}
}
MyProxy類,這個是最關鍵的核心程式碼,其功能類似於JDK中的Proxy類
public class MyProxy {
private static String rt = "\r\n";
/**
* @Description TODO
* @param @param loader
* @param @param interfaces
* @param @param h
* @param @return 引數
* @return Object 返回型別
* @throws
*
* 這個方法最終是需要返回一個代理物件
* 1、執行時動態生成
* 2、它是一個類
* 3、它要實現people介面
* 4、是以位元組碼的方式載入到記憶體中
*
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, JackInvocationHandler h) {
String fileName = "F:/workspace/proxy/src/main/java/com/zhuguang/jack/myproxy/$Proxy0.java";
//1、我們要生成一個類,以字串拼湊的方式,拼湊出一個類
String javaClassStr = getJavaStr(interfaces);
//2、通過流的方式生成java檔案
createJavaFile(javaClassStr, fileName);
//3、動態編譯java檔案,生成.class檔案
compilerJava(fileName);
//4、要把磁盤裡面的.class檔案內容載入到jvm的記憶體
Object instance = LoadClass(h);
return instance;
}
/**
* @Description TODO
* @param @param h
* @param @return 引數
* @return Object 返回型別
* @throws
*
* 自定義一個類載入器
*/
private static Object LoadClass(JackInvocationHandler h) {
JackClassLoader jackClassLoader = new JackClassLoader(
"F:/workspace/proxy/src/main/java/com/zhuguang/jack/myproxy");
try {
//返回的是一個被代理物件的Class物件
Class<?> findClass = jackClassLoader.findClass("$Proxy0");
Constructor<?> constructor = findClass.getConstructor(JackInvocationHandler.class);
Object newInstance = constructor.newInstance(h);
return newInstance;
}
catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private static void compilerJava(String fileName) {
JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null,
null,
null);
Iterable<? extends JavaFileObject> javaFileObjects = standardFileManager.getJavaFileObjects(fileName);
CompilationTask task = systemJavaCompiler.getTask(null,
standardFileManager,
null,
null,
null,
javaFileObjects);
task.call();
try {
standardFileManager.close();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void createJavaFile(String javaClassStr, String fileName) {
try {
File f = new File(fileName);
FileWriter fw;
fw = new FileWriter(f);
fw.write(javaClassStr);
fw.flush();
fw.close();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static String getJavaStr(Class<?>[] interfaces) {
Method[] methods = interfaces[0].getMethods();
String proxyClassStr = "package com.zhuguang.jack.myproxy;" + rt
+ "import java.lang.reflect.Method;" + rt
+ "public class $Proxy0 implements " + interfaces[0].getName()
+ "{" + rt + "JackInvocationHandler h;" + rt
+ "public $Proxy0(JackInvocationHandler h) {" + rt
+ "this.h=h;" + rt + "}"
+ getMethodString(methods, interfaces[0]) + rt + "}";
return proxyClassStr;
}
private static String getMethodString(Method[] methods, Class intf) {
String proxyMe = "";
for (Method method : methods) {
proxyMe += "public void " + method.getName()
+ "() throws Throwable {" + rt + "Method md = "
+ intf.getName() + ".class.getMethod(\"" + method.getName()
+ "\",new Class[]{});" + rt
+ "this.h.invoke(this,md,null);" + rt + "}" + rt;
}
return proxyMe;
}
}
在這個類中有幾個步驟:
1、生成代理類,我們用字串拼湊的方式把類中的內容拼湊成String型別
2、通過流的方式把String寫到java檔案中
3、動態編譯java檔案,把它編譯成.class檔案
4、把磁碟中的.class檔案載入到記憶體中並返回代理類的物件
通過這幾個步驟,我們就可以拿到記憶體中的代理類,如此一個自己定義的動態代理就搞定了,是不是沒有那麼難?
希望博主的整理能夠幫助到大家,如果大家覺得OK的話,麻煩點個贊,或者寫個評論什麼的,好歹我也要士氣來 繼續堅持我的原創部落格之路啊,哈哈~~~~~