Java 動態代理原始碼分析
Java 動態代理
1. 相關概念
1.1 代理
在某些情況下,我們不希望或是不能直接訪問物件 A,而是通過訪問一箇中介物件 B,由 B 去訪問 A 達成目的,這種方式我們就稱為代理。
這裡物件 A 所屬類我們稱為委託類,也稱為被代理類,物件 B 所屬類稱為代理類。
代理優點有:
- 隱藏委託類的實現
- 解耦,不改變委託類程式碼情況下做一些額外處理,比如新增初始判斷及其他公共操作
根據程式執行前代理類是否已經存在,可以將代理分為靜態代理和動態代理。
1.2 靜態代理
代理類在程式執行前已經存在的代理方式稱為靜態代理。
通過上面解釋可以知道,由開發人員編寫或是編譯器生成代理類的方式都屬於靜態代理,如下是簡單的靜態代理例項:
class ClassA { public void operateMethod1() {}; public void operateMethod2() {}; public void operateMethod3() {}; } public class ClassB { private ClassA a; public ClassB(ClassA a) { this.a = a; } public void operateMethod1() { a.operateMethod1(); }; public void operateMethod2() { a.operateMethod2(); }; // not export operateMethod3() }
上面ClassA
是委託類,ClassB
是代理類,ClassB
中的函式都是直接呼叫ClassA
相應函式,並且隱藏了Class
的operateMethod3()
函式。
靜態代理中代理類和委託類也常常繼承同一父類或實現同一介面。
1.3 動態代理
代理類在程式執行前不存在、執行時由程式動態生成的代理方式稱為動態代理。
Java 提供了動態代理的實現方式,可以在執行時刻動態生成代理類。這種代理方式的一大好處是可以方便對代理類的函式做統一或特殊處理,如記錄所有函式執行時間、所有函式執行前新增驗證判斷、對某個特殊函式進行特殊操作,而不用像靜態代理方式那樣需要修改每個函式。
靜態代理
比較簡單,本文上面已簡單介紹,下面重點介紹動態代理
2. 動態代理例項
實現動態代理包括三步:
(1). 新建委託類;
(2). 實現InvocationHandler
介面,這是負責連線代理類和委託類的中間類必須實現的介面;
(3). 通過Proxy
類新建代理類物件。
下面通過例項具體介紹,假如現在我們想統計某個類所有函式的執行時間,傳統的方式是在類的每個函式前打點統計,動態代理方式如下:
2.1 新建委託類
public interface Operate {
public void operateMethod1();
public void operateMethod2();
public void operateMethod3();
}
public class OperateImpl implements Operate {
@Override
public void operateMethod1() {
System.out.println("Invoke operateMethod1");
sleep(110);
}
@Override
public void operateMethod2() {
System.out.println("Invoke operateMethod2");
sleep(120);
}
@Override
public void operateMethod3() {
System.out.println("Invoke operateMethod3");
sleep(130);
}
private static void sleep(long millSeconds) {
try {
Thread.sleep(millSeconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Operate
是一個介面,定了了一些函式,我們要統計這些函式的執行時間。OperateImpl
是委託類,實現Operate
介面。每個函式簡單輸出字串,並等待一段時間。
動態代理要求委託類必須實現了某個介面,比如這裡委託類OperateImpl
實現了Operate
,原因會後續在微博公佈。
2.2. 實現 InvocationHandler 介面
public class TimingInvocationHandler implements InvocationHandler {
private Object target;
public TimingInvocationHandler() {}
public TimingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object obj = method.invoke(target, args);
System.out.println(method.getName() + " cost time is:" + (System.currentTimeMillis() - start));
return obj;
}
}
target
屬性表示委託類物件。
InvocationHandler
是負責連線代理類和委託類的中間類必須實現的介面。其中只有一個
public Object invoke(Object proxy, Method method, Object[] args)
函式需要去實現,引數:proxy
表示下面2.3 通過 Proxy.newProxyInstance() 生成的代理類物件
。method
表示代理物件被呼叫的函式。args
表示代理物件被呼叫的函式的引數。
呼叫代理物件的每個函式實際最終都是呼叫了InvocationHandler
的invoke
函式。這裡我們在invoke
實現中添加了開始結束計時,其中還呼叫了委託類物件target
的相應函式,這樣便完成了統計執行時間的需求。invoke
函式中我們也可以通過對method
做一些判斷,從而對某些函式特殊處理。
2.3. 通過 Proxy 類靜態函式生成代理物件
public class Main {
public static void main(String[] args) {
// create proxy instance
TimingInvocationHandler timingInvocationHandler = new TimingInvocationHandler(new OperateImpl());
Operate operate = (Operate)(Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[] {Operate.class},
timingInvocationHandler));
// call method of proxy instance
operate.operateMethod1();
System.out.println();
operate.operateMethod2();
System.out.println();
operate.operateMethod3();
}
}
這裡我們先將委託類物件new OperateImpl()
作為TimingInvocationHandler
建構函式入參建立timingInvocationHandler
物件;
然後通過Proxy.newProxyInstance(…)
函式新建了一個代理物件,實際代理類就是在這時候動態生成的。我們呼叫該代理物件的函式就會呼叫到timingInvocationHandler
的invoke
函式(是不是有點類似靜態代理),而invoke
函式實現中呼叫委託類物件new OperateImpl()
相應的 method(是不是有點類似靜態代理)。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
loader
表示類載入器interfaces
表示委託類的介面,生成代理類時需要實現這些介面h
是InvocationHandler
實現類物件,負責連線代理類和委託類的中間類
我們可以這樣理解,如上的動態代理實現實際是雙層的靜態代理,開發者提供了委託類 B,程式動態生成了代理類 A。開發者還需要提供一個實現了InvocationHandler
的子類 C,子類 C 連線代理類 A 和委託類 B,它是代理類 A 的委託類,委託類 B 的代理類。使用者直接呼叫代理類 A 的物件,A 將呼叫轉發給委託類 C,委託類 C 再將呼叫轉發給它的委託類 B。
3. 動態代理原理
實際上面最後一段已經說清了動態代理的真正原理。我們來仔細分析下
3.1 生成的動態代理類程式碼
下面是上面示例程式執行時自動生成的動態代理類程式碼,如何得到這些生成的程式碼請見ProxyUtils,檢視 class 檔案可使用 jd-gui
import com.codekk.java.test.dynamicproxy.Operate;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy
implements Operate
{
private static Method m4;
private static Method m1;
private static Method m5;
private static Method m0;
private static Method m3;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final void operateMethod1()
throws
{
try
{
h.invoke(this, m4, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final boolean equals(Object paramObject)
throws
{
try
{
return ((Boolean)h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void operateMethod2()
throws
{
try
{
h.invoke(this, m5, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void operateMethod3()
throws
{
try
{
h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return (String)h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m4 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod1", new Class[0]);
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m5 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod2", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod3", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
從中我們可以看出動態生成的代理類是以$Proxy
為類名字首,繼承自Proxy
,並且實現了Proxy.newProxyInstance(…)
第二個引數傳入的所有介面的類。
如果代理類實現的介面中存在非 public 介面,則其包名為該介面的包名,否則為com.sun.proxy
。
其中的operateMethod1()
、operateMethod2()
、operateMethod3()
函式都是直接交給h
去處理,h
在父類Proxy
中定義為
protected InvocationHandler h;
即為Proxy.newProxyInstance(…)
第三個引數。
所以InvocationHandler
的子類 C 連線代理類 A 和委託類 B,它是代理類 A 的委託類,委託類 B 的代理類。
3.2. 生成動態代理類原理
以下針對 Java 1.6 原始碼進行分析,動態代理類是在呼叫Proxy.newProxyInstance(…)
函式時生成的。
(1). newProxyInstance(…)
函式程式碼如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
/*
* Look up or generate the designated proxy class.
*/
Class cl = getProxyClass(loader, interfaces);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
} catch (IllegalAccessException e) {
throw new InternalError(e.toString());
} catch (InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
throw new InternalError(e.toString());
}
}
從中可以看出它先呼叫getProxyClass(loader, interfaces)
得到動態代理類,然後將InvocationHandler
作為代理類建構函式入參新建代理類物件。
(2). getProxyClass(…)
函式程式碼及解釋如下(省略了原英文註釋):
/**
* 得到代理類,不存在則動態生成
* @param loader 代理類所屬 ClassLoader
* @param interfaces 代理類需要實現的介面
* @return
*/
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 代理類類物件
Class proxyClass = null;
/* collect interface names to use as key for proxy class cache */
String[] interfaceNames = new String[interfaces.length];
Set interfaceSet = new HashSet(); // for detecting duplicates
/**
* 入參 interfaces 檢驗,包含三部分
* (1)是否在入參指定的 ClassLoader 內
* (2)是否是 Interface
* (3)interfaces 中是否有重複
*/
for (int i = 0; i < interfaces.length; i++) {
String interfaceName = interfaces[i].getName();
Class interfaceClass = null;
try {
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
if (interfaceSet.contains(interfaceClass)) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
interfaceSet.add(interfaceClass);
interfaceNames[i] = interfaceName;
}
// 以介面名對應的 List 作為快取的 key
Object key = Arrays.asList(interfaceNames);
/*
* loaderToCache 是個雙層的 Map
* 第一層 key 為 ClassLoader,第二層 key 為 上面的 List,value 為代理類的弱引用
*/
Map cache;
synchronized (loaderToCache) {
cache = (Map) loaderToCache.get(loader);
if (cache == null) {
cache = new HashMap();
loaderToCache.put(loader, cache);
}
}
/*
* 以上面的介面名對應的 List 為 key 查詢代理類,如果結果為:
* (1) 弱引用,表示代理類已經在快取中
* (2) pendingGenerationMarker 物件,表示代理類正在生成中,等待生成完成通知。
* (3) null 表示不在快取中且沒有開始生成,新增標記到快取中,繼續生成代理類
*/
synchronized (cache) {
do {
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// proxy class already generated: return it
return proxyClass;
} else if (value == pendingGenerationMarker) {
// proxy class being generated: wait for it
try {
cache.wait();
} catch (InterruptedException e) {
}
continue;
} else {
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
try {
String proxyPkg = null; // package to define proxy class in
/*
* 如果 interfaces 中存在非 public 的介面,則所有非 public 介面必須在同一包下面,後續生成的代理類也會在該包下面
*/
for (int i = 0; i < interfaces.length; i++) {
int flags = interfaces[i].getModifiers();
if (!Modifier.isPublic(flags)) {
String name = interfaces[i].getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) { // if no non-public proxy interfaces,
proxyPkg = ""; // use the unnamed package
}
{
// 得到代理類的類名,jdk 1.6 版本中缺少對這個生成類已經存在的處理。
long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 動態生成代理類的位元組碼
// 最終呼叫 sun.misc.ProxyGenerator.generateClassFile() 得到代理類相關資訊寫入 DataOutputStream 實現
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
// native 層實現,虛擬機器載入代理類並返回其類物件
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
// add to set of all generated proxy classes, for isProxyClass
proxyClasses.put(proxyClass, null);
} finally {
// 代理類生成成功則儲存到快取,否則從快取中刪除,然後通知等待的呼叫
synchronized (cache) {
if (proxyClass != null) {
cache.put(key, new WeakReference(proxyClass));
} else {
cache.remove(key);
}
cache.notifyAll();
}
}
return proxyClass;
}
函式主要包括三部分:
- 入參 interfaces 檢驗,包含是否在入參指定的 ClassLoader 內、是否是 Interface、interfaces 中是否有重複
- 以介面名對應的 List 為 key 查詢代理類,如果結果為:
- 弱引用,表示代理類已經在快取中;
- pendingGenerationMarker 物件,表示代理類正在生成中,等待生成完成返回;
- null 表示不在快取中且沒有開始生成,新增標記到快取中,繼續生成代理類。
- 如果代理類不存在呼叫
ProxyGenerator.generateProxyClass(…)
生成代理類並存入快取,通知在等待的快取。
函式中幾個注意的地方:
- 代理類的快取 key 為介面名對應的 List,介面順序不同表示不同的 key 即不同的代理類。
- 如果 interfaces 中存在非 public 的介面,則所有非 public 介面必須在同一包下面,後續生成的代理類也會在該包下面。
- 代理類如果在 ClassLoader 中已經存在的情況沒有做處理。
- 可以開啟 System Properties 的
sun.misc.ProxyGenerator.saveGeneratedFiles
開關,儲存動態類到目的地址。
Java 1.7 的實現略有不同,通過getProxyClass0(…)
函式實現,實現中呼叫代理類的快取,判斷代理類在快取中是否已經存在,存在直接返回,不存在則呼叫proxyClassCache
的valueFactory
屬性進行動態生成,valueFactory
的apply
函式與上面的getProxyClass(…)
函式邏輯類似。
4. 使用場景
4.1 J2EE Web 開發中 Spring 的 AOP(面向切面程式設計) 特性
作用:目標函式之間解耦。
比如在 Dao 中,每次資料庫操作都需要開啟事務,而且在操作的時候需要關注許可權。一般寫法是在 Dao 的每個函式中新增相應邏輯,造成程式碼冗餘,耦合度高。
使用動態代理前虛擬碼如下:
Dao {
insert() {
判斷是否有儲存的許可權;
開啟事務;
插入;
提交事務;
}
delete() {
判斷是否有刪除的許可權;
開啟事務;
刪除;
提交事務;
}
}
使用動態代理的虛擬碼如下:
// 使用動態代理,組合每個切面的函式,而每個切面只需要關注自己的邏輯就行,達到減少程式碼,鬆耦合的效果
invoke(Object proxy, Method method, Object[] args)
throws Throwable {
判斷是否有許可權;
開啟事務;
Object ob = method.invoke(dao, args);
提交事務;
return ob;
}
4.2 基於 REST 的 Android 端網路請求框架 Retrofit
作用:簡化網路請求操作。
一般情況下每個網路請求我們都需要呼叫一次HttpURLConnection
或者HttpClient
進行請求,或者像 Volley 一樣丟進等待佇列中,Retrofit
極大程度簡化了這些操作,示例程式碼如下:
public interface GitHubService {
@GET("/users/{user}/repos")
List<Repo> listRepos(@Path("user") String user);
}
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.build();
GitHubService service = restAdapter.create(GitHubService.class);
以後我們只需要直接呼叫
List<Repo> repos = service.listRepos("octocat");
即可開始網路請求,Retrofit
的原理就是基於動態代理,它同時用到了 註解 的原理,本文不做深入介紹,具體請等待 Retrofit
原始碼解析 完成。
生成動態代理類的位元組碼並且儲存到硬碟中:
public static void generateClassFileAndSave(Class<?> clazz, String proxyName, String savePath) {
// 根據類資訊和提供的代理類名稱,生成位元組碼
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
if (StrUtil.isStrTrimNull(savePath)) {
savePath = clazz.getResource(".").getPath() + proxyName + ".class";
} else {
File file = new File(savePath);
if (!file.exists()) {
file.mkdirs();
}
savePath += File.separator + proxyName + ".class";
}
try {
FileUtil.writeFile(savePath, classFile);
} catch (IOException e) {
e.printStackTrace();
}
}