代理模式及手實現動態代理(aop原理)
一、代理模式
熟悉代理模式的可以直接點擊目錄第二章,jdk動態代理實現原理,本文的精髓所在,通過這個例子,教大家如何去學習源碼。
1. 定義
代理模式(Proxy Pattern) 是一個使用頻率非常高的設計模式,其定義如下:
Provide a surrogate or placeholder for another object to control access to it.(爲其他對象提供一種代理以控制對這個對象的訪問。)
2. 示例
(1)靜態代理
-
遊戲者接口
/** * 遊戲者接口 */ public interface IGamePlayer { /** * 登錄遊戲 * * @param user 用戶名 * @param password 用戶密碼 */ void login(String user, String password); /** * 玩遊戲 */ void play(); }
-
遊戲者
public class GamePlayer implements IGamePlayer { private String name = ""; public GamePlayer(String name) { this.name = name; } /** * 登錄 */ @Override public void login(String user, String password) { System.out.println("登錄名爲" + user + "的用戶" + this.name + "登錄成功!"); } /** * 玩遊戲 */ @Override public void play() { System.out.println(this.name + "的賬號正在進行遊戲"); } }
-
代練者
/** * 代練類,負責幫助玩家代練,按時計費 */ public class GamePlayerProxy implements IGamePlayer { /** * 代練對象 */ private IGamePlayer gamePlayer = null; /** * 代練一小時價格 */ private int PER_HUOR_COST = 5; /** * 通過構造方法傳入需要代練服務的對象 */ public GamePlayerProxy(IGamePlayer gamePlayer) { this.gamePlayer = gamePlayer; } @Override public void login(String user, String password) { System.out.println("代練開始登錄賬號"); this.gamePlayer.login(user, password); } @Override public void play() { long strTime = System.currentTimeMillis(); Date date = new Date(strTime); Calendar calendar = Calendar.getInstance(); calendar.setTime(date); System.out.println("代練開始時間是" + date); this.gamePlayer.play(); long endTime = System.currentTimeMillis(); int costTime = (int) (endTime - strTime); //使用毫秒模擬小時,給開始時間增加消耗的毫秒數個小時 calendar.add(Calendar.HOUR, costTime); System.out.println("代練結束時間是" + calendar.getTime()); System.out.println("共計代練" + costTime + "小時,收費" + costTime * PER_HUOR_COST + "元。"); } }
-
場景類
public class Client { public static void main(String[] args) { //定義一個癡迷的玩家 IGamePlayer player = new GamePlayer("張三"); //然後再定義一個代練者 IGamePlayer proxy = new GamePlayerProxy(player); //登陸賬號 proxy.login("zhangSan", "password"); //開始代練 proxy.play(); } }
-
測試結果
(2)動態代理
動態代理步驟:
1.創建一個實現接口InvocationHandler的類,它必須實現invoke方法
2.創建被代理的類以及接口
3.通過Proxy的靜態方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)創建一個代理
4.通過代理調用方法
-
Person接口
public interface Person { void hello(); }
-
ZhangSan實現類
public class ZhangSan implements Person { @Override public void hello() { System.out.println("我是張三,大家好"); } }
-
MyInvocationHandler
public class MyInvocationHandler implements InvocationHandler { private ZhangSan zhangSan; public MyInvocationHandler(ZhangSan zhangSan) { this.zhangSan = zhangSan; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object o = method.invoke(zhangSan, args); after(); return o; } public void before() { System.out.println("動態代理前置處理"); } public void after() { System.out.println("動態代理後置處理"); } }
- Test類
public class Test { public static void main(String[] args) { try { Person person = (Person) Proxy.newProxyInstance(Test.class.getClassLoader(), ZhangSan.class.getInterfaces(), new MyInvocationHandler(new ZhangSan())); person.hello(); } catch (Exception e) { e.printStackTrace(); } } }
- 測試結果
3. 通用類圖
代理模式的通用類如圖所示,如果不能理解也可以先看下面的示例,再返回來重新查看通用模型
代理模式也叫做委託模式,它是一項基本設計技巧。許多其他的模式,如狀態模式、策
略模式、訪問者模式本質上是在更特殊的場合採用了委託模式,而且在日常的應用中,代理 模式可以提供非常好的訪問控制。在一些著名開源軟件中也經常見到它的身影,如Spring框架中的aop就是基於代理模式(更確切的說是動態代理)實現的。我們先看一下類圖中三個組件的解釋:
- Subject抽象主題角色
抽象主題類可以是抽象類也可以是接口,是一個最普通的業務類型定義,無特殊要求。
/**
* 業務接口
*/
public interface ISubject {
/**
* 定義一個方法
*/
void request();
}
- RealSubject具體主題角色
也叫做被委託角色、被代理角色。它纔是冤大頭,是業務邏輯的具體執行者,這個往往是我們需要關注的重點。
/**
* 實際業務對象
*/
public class RealISubject implements ISubject {
/**
* 業務實現方法
*/
@Override
public void request() {
//業務邏輯處理
}
}
- Proxy代理主題角色
也叫做委託類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制
委託給真實主題角色實現,並且在真實主題角色處理完畢前後做預處理和善後處理工作。
public class Proxy implements ISubject {
/**
* 被代理對象的引用
*/
private ISubject subject = null;
/**
* 默認被代理對象
*/
public Proxy() {
this.subject = new Proxy();
}
/**
* 傳入被代理對象
*/
public Proxy(ISubject subject) {
this.subject = subject;
}
/**
* 代理對象控制被代理對象行爲,添加預處理和後置處理,如可以添加日誌打印,權限控制,事務管理
*/
@Override
public void request() {
before();
this.subject.request();
after();
}
public void before() {
//預處理
}
public void after() {
//後置處理
}
}
4. 代理模式的優點
- 職責清晰
主題角色(被代理對象)就是實現實際的業務邏輯,不用關心其他非本職責的邏輯處理,如日誌,事務的開啓、關閉、回滾等,就可以通過後期代理來實現。在spring中常說的AOP(面向切面編程)的思想就是將代碼進行橫向切分,通過預編譯方式和運行期間動態代理實現程序功能的統一維護。它可以將模塊劃分的更加細緻,減少各個模塊和公用模塊之間的耦合,讓我們將關注點轉移到業務本身。 - 高擴展性
具體主題角色(被代理對象)是隨時都會發生變化的,只要它實現了接口,甭管它如何變化,都逃不脫如來佛的手掌(接口),那我們的代理類完全就可以在不做任何修改的情況下使用。在這種情況下,我們的代理類只需直接調用真實對象的業務方法即可,我們只需要關心流程控制,和一些其他的邏輯。如我們在使用業務代碼前,進行權限驗證,如進入業務代碼前,記錄此次調用,將調用記錄(如時間,調用方法,調用地點)寫入數據庫來方便後臺監控用戶的行爲。同樣的,我們在主題角色(被代理對象)中只需專注於業務邏輯變更即可。
二、jdk動態代理實現原理
1. jdk動態代理源碼分析(通過該示例學會閱讀源碼的方法)
以之前的動態代理 代碼爲例,我們閱讀jdk動態代理源碼分析其實現原理。在閱讀源碼時我們最重要的是找對切入點,在這段代碼中,沒有複雜的邏輯,很明顯,我們以newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)
這個方法爲切入點。我們在代碼中着重觀察我們的代理類生成的路徑,f3進入這個方法。這裏如果不懂或者暫時不感興趣可直接查看下一部分和總結部分查看結論。推薦大家最好可以跟着自己走一遍。大家也可以查看我的另一篇文章ArrayList核心源碼分析-擴容機制(jdk1.8)來練習源碼閱讀能力。
-
newProxyInstance:Proxy.java(java.lang.reflect)
在這個方法中,找到關鍵代碼,這一行代碼返回代理類的class文件,下面再通過反射創造實例返回。(我這裏因爲篇幅原因省去了這些類的具體代碼,如果看起來有點暈,強烈建議自己動手試試,或者直接查看結論)/* * Look up or generate the designated proxy class. * 查找或生成指定的代理類。 */ Class<?> cl = getProxyClass0(loader, intfs);
再次點擊f3進入getProxyClass0方法
-
getProxyClass0:Proxy (java.lang.reflect)
這個方法非常簡單,我們觀察它的返回值// If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory //如果存在由實現了給定接口的給定加載器定義的代理類,則將僅返回緩存的副本; //否則,它將通過ProxyClassFactory創建代理類。在java中是採用了享元模式存儲了已經生成的代理類的class文件 return proxyClassCache.get(loader, interfaces);
再次f3進入get方法
-
get:WeakCache (java.lang.reflect)
我們可以觀察到最終總是返回valuewhile (true) { if (supplier != null) { // supplier might be a Factory or a CacheValue<V> instance V value = supplier.get(); if (value != null) { return value; } } ...
f3進入get方法
public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
我們走到這裏無法確定到底調用的是那個實現類,此時可以在上一層
V value = supplier.get();
處打上斷點。然後debug運行Test類,可以看到停到斷點處。
我們可以觀察到調用的是WeakCache$Factory
類的方法。找到該類的這個方法
-
get:WeakCache$Factory (java.lang.reflect)
即WeakCache的內部類Factory
觀察該方法,我們發現最終return value,再細看有一行create value,發現這一行關鍵代碼,==在這行打上斷點。==我們每次進行類的跳轉如果不是很清楚就可以在每次跳轉前打上斷點。value = Objects.requireNonNull(valueFactory.apply(key, parameter));
這段代碼
requireNonNull()
做一個非空檢查,核心是valueFactory.apply(key, parameter)
,再次f3進入該方法
可以看到這是一個接口,我們無法判斷是那個實現類,我們繼續使用上次的方法,查看到底調用的是哪個實現類,在debug中f6,走到我們上一步中打下的斷點。
我們可以看到代理類的class文件從這個方法中生成,我們觀察debug窗口變量監控
可以看到發現我們調用的是Proxy$ProxyClassFactory
的apply()方法。找到該類的該方法
-
apply:Proxy$ProxyClassFactory (java.lang.reflect)
我們觀察到該方法中有一行代碼,生成指定的proxy class,這就是我們要找的東西啦。
我們可以看到該方法生成class文件的byte流,我們再進入方法,可以觀察到其實是在使用文件寫入的方式,動態寫入java文件,然後編譯java文件生成class文件,最後將其轉換爲byte流返回。其實動態代理和靜態代理的區別就是:靜態是在運行程序時已經生成了class文件並且加載進了jvm。我們看到下一行defineClass0()其實就是將該class文件動態的加載到jvm中,顯然動態代理就是運行時加載代理類的class。我們會在第三部分會自己手寫實現這個gennerate方法,所以不在重複。可以從第三部分中獲得更多的細節。
2.jdk動態代理生成的代理類的源碼
改變之前的Test測試類代碼如下
public class Test {
public static void main(String[] args) {
try {
Person person = (Person) Proxy.newProxyInstance(Test.class.getClassLoader(),
ZhangSan.class.getInterfaces(), new MyInvocationHandler(new ZhangSan()));
//之前源碼分析中的關鍵方法
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
//在哪裏存儲生成的class文件,因爲idea中有反編譯插件,所以我們可以看到該java文件源碼
FileOutputStream f = new FileOutputStream("src/main/java/com/jdk_proxy/$Proxy0.class");
f.write(bytes);
person.hello();
System.out.println(person.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
生成的class文件如下,注意看註釋行
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.jdk_proxy.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
//可以看到該類實現了newProxyInstance:Proxy.java(java.lang.reflect)這個方法傳入的接口參數,繼承了Proxy類
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
//我們可以看到它會傳遞InvocationHandler至父類Proxy
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//這裏就是我們的代理類重寫接口中的方法,也是我們最終在調用代理類中的方法時所真正調用的方法
public final void hello() throws {
try {
//注意這裏的三個參數,我們可以看到他將this和在staic塊中反射生成的接口中的方法,和調用代理傳入的參數
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//在加載時通過反射從實現的接口中獲得接口中的Method對象。
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.jdk_proxy.Person").getMethod("hello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
Proxy類
public class Proxy implements java.io.Serializable {
...
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
...
InvocationHandler類
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
3.總結
- 動態代理實現過程(結合上面的class文件和我們示例程序)
- 通過實現 InvocationHandler 接口,自定義調用處理器;
- 通過調用
newProxyInstance:Proxy.java(java.lang.reflect)
指定ClassLoader對象和一組 interface 來創建動態代理類; - 通過
newProxyInstance
傳入的接口來動態的生成代理類java文件(下一部分有自己實現代碼,可以詳細瞭解原理),然後進行編譯,最後加載到jvm中。代理類中的所有方法都調用了newProxyInstance:Proxy.java(java.lang.reflect)
中傳入的InvocationHandler
的invoke方法。也就是說我們的代理類重寫了接口中的所有方法,然後再這些方法中只做了一件事,調用invoke:InvocationHandler
。 - 通過反射創建代理類對象返回。
- 源碼分析總結
我們將斷點打ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
這個方法上,debug運行
生成Java文件,編譯成class文件,然後加載到jvm中,整個調用鏈如下。在學習時如果上面源碼分析部分有點暈可以利用這種方法,快速定位代碼。
三、手寫實現jdk動態代理
-
Generator
public class Generator { static String rt = "\r\n"; public static Class<?> generator(Class<?>[] interfaces, MyClassLoader loader, MyInvocationHandler h) throws Exception { try { // 1.創建代理類java源碼文件,寫入到硬盤中.. Method[] methods = interfaces[0].getMethods(); String name = interfaces[0].getName(); String packageName = name.substring(0, name.lastIndexOf(".")); StringBuilder stringBuilder = new StringBuilder(); //包名 stringBuilder.append("package "); stringBuilder.append(packageName); stringBuilder.append(";"); stringBuilder.append(rt); //導入的類 stringBuilder.append("import java.lang.reflect.Method;"); stringBuilder.append(rt); stringBuilder.append("import com.proxy_design.*;"); stringBuilder.append(rt); //類的聲明 stringBuilder.append("public class Proxy0 extends MyProxy implements "); stringBuilder.append(interfaces[0].getName()); stringBuilder.append("{"); stringBuilder.append(rt); //構造方法 stringBuilder.append("\tpublic Proxy0(MyInvocationHandler h){"); stringBuilder.append(rt); stringBuilder.append("\t\tsuper(h);"); stringBuilder.append(rt); stringBuilder.append("\t}"); stringBuilder.append(rt); //添加重寫後的方法,在所有方法中調用super.h.invoke方法即可 stringBuilder.append(getMethodString(methods, interfaces[0])); stringBuilder.append(rt); stringBuilder.append("}"); stringBuilder.append(rt); String proxyClass = stringBuilder.toString(); // 2. 將代理類源碼文件寫入硬盤中,根據自己的目錄輸入 String filename = "src/main/java/" + packageName.replace(".", "/") + "/Proxy0.java"; File f = new File(filename); FileWriter fw = new FileWriter(f); fw.write(proxyClass); fw.flush(); fw.close(); // 3.使用JavaJavaCompiler 編譯該Proxy0源代碼 獲取class文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects(filename); JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); return loader.findClass("Proxy0"); } catch (Exception e) { e.printStackTrace(); } throw new ClassNotFoundException(); } public static String getMethodString(Method[] methods, Class intf) { StringBuilder proxyMe = new StringBuilder(); for (int i = 0; i < methods.length; i++) { proxyMe.append("\tprivate static Method m").append(i).append(";").append(rt); } for (int i = 0; i < methods.length; i++) { proxyMe.append("\tpublic void "); proxyMe.append(methods[i].getName()); proxyMe.append("(){"); proxyMe.append(rt); proxyMe.append("\t\ttry {"); proxyMe.append(rt); proxyMe.append("\t\t\tsuper.h.invoke(this,m"); proxyMe.append(i); proxyMe.append(",null);"); proxyMe.append(rt); proxyMe.append("\t\t} catch (Throwable throwable) {"); proxyMe.append(rt); proxyMe.append("\t\t\tthrowable.printStackTrace();"); proxyMe.append(rt); proxyMe.append("\t\t}"); proxyMe.append(rt); proxyMe.append("\t}"); proxyMe.append(rt); } //從接口中反射獲得所有方法 proxyMe.append("\tstatic {"); proxyMe.append(rt); proxyMe.append("\t\ttry{"); proxyMe.append(rt); for (int i = 0; i < methods.length; i++) { proxyMe.append("\t\t\tm"); proxyMe.append(i); proxyMe.append("="); proxyMe.append(intf.getName()); proxyMe.append(".class.getMethod(\""); proxyMe.append(methods[i].getName()); proxyMe.append("\",new Class[]{});"); proxyMe.append(rt); } proxyMe.append("\t\t} catch (NoSuchMethodException var2) {"); proxyMe.append(rt); proxyMe.append("\t\t\tthrow new NoSuchMethodError(var2.getMessage());"); proxyMe.append(rt); proxyMe.append("\t\t}"); proxyMe.append(rt); proxyMe.append("\t}"); return proxyMe.toString(); } }
-
MyClassLoader
public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { //class文件將生成的位置 File file = new File("src/main/java/com/proxy_design/Proxy0.class"); if (file.exists()) { try { //將文件轉換爲byte流 FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(1000); byte[] b = new byte[1000]; int n; while ((n = fis.read(b)) != -1) { bos.write(b, 0, n); } fis.close(); bos.close(); byte[] buffer = bos.toByteArray(); //加載類返回類,此時靜態塊中會被調用 return defineClass("com.proxy_design." + name, buffer, 0, buffer.length); } catch (IOException e) { e.printStackTrace(); } } return super.findClass(name); }
}
```
-
MyInvocationHandler
public interface MyInvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
-
MyInvocationHandlerImp
public class MyInvocationHandlerImp implements MyInvocationHandler { private ZhangSan zhangSan; public MyInvocationHandlerImp(ZhangSan zhangSan) { this.zhangSan = zhangSan; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("動態代理前置處理"); //調用被代理對象的真實方法 Object o = method.invoke(zhangSan, args); System.out.println("動態代理後置處理"); return o; } }
-
MyProxy
public class MyProxy { /** * 在使用動態代理生成的proxy類中,他會繼承本類,然後在構造方法中傳入h */ protected MyInvocationHandler h; public MyProxy(MyInvocationHandler h) { this.h = h; } public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) throws Exception { Class<?> proxy = Generator.generator(interfaces, loader, h); Constructor<?> proxyConstructor = proxy.getConstructor(MyInvocationHandler.class); return proxyConstructor.newInstance(h); } }
-
Test
public class Test { public static void main(String[] args) { try { Person proxyMapper = (Person) MyProxy.newProxyInstance(new MyClassLoader(), ZhangSan.class.getInterfaces(), new MyInvocationHandlerImp(new ZhangSan()) ); proxyMapper.hello(); } catch (Exception e) { e.printStackTrace(); } } }
-
Person、ZhangSan和上面一樣。(動態代理示例)
-
生成的Proxy類源碼
public class Proxy0 extends MyProxy implements com.proxy_design.Person{ public Proxy0(MyInvocationHandler h){ super(h); } private static Method m0; //實際生效方法 public void hello(){ try { super.h.invoke(this,m0,null); } catch (Throwable throwable) { throwable.printStackTrace(); } } static { try{ m0=com.proxy_design.Person.class.getMethod("hello",new Class[]{}); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } } }