代理模式
使用代理模式創(chuàng)建代理對象,讓代理對象來控制對某個對象的訪問, 被代理對象可以是遠程對象,創(chuàng)建開銷大的對象或者需要安全控制的對象等.
Proxy
稱為代理對象.
RealSubject
是被代理的對象,也稱為委托對象.
Subject
是他們抽象出來的接口.
RealSubject
和Proxy
都繼承自Subject
, Proxy
內(nèi)部持有一個 RealSubject
的變量,調(diào)用代理的方法,代理中將直接調(diào)用RealSubject
對應的方法.
靜態(tài)代理
靜態(tài)代理,在編譯期間就需要指定好代理類,即在程序運行前就已經(jīng)存在代理類的字節(jié)碼文件烟逊,代理類和委托類的關系在運行前就確定了.
interface Subject {
void request();
}
class Proxy implements Subject {
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
// 添加log
System.out.println("log...");
realSubject.request();
}
}
class RealSubject implements Subject {
@Override
public void request() {
// todo ...
System.out.println("request from http");
}
}
public static void main(String[] args) {
Subject proxy = new Proxy(new RealSubject());
proxy.request();
}
這樣做的優(yōu)點:
1. 可以隱藏委托類的實現(xiàn),可以進行權限控制和安全控制.
2. 實現(xiàn)客戶端和委托類解耦,只要對外接口不變,客戶端就不需要修改調(diào)用方式.
3. 通過擴展代理類伦吠,進行一些功能的附加與增強.
動態(tài)代理
動態(tài)代理類的字節(jié)碼在程序運行時由Java反射機制動態(tài)生成俺孙,無需程序員手工編寫它的源代碼.代理類和委托類的關系是在程序運行時確定。
相比于靜態(tài)代理, 動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理冯乘,而不用修改每個代理類的函數(shù)焚刚。
Java中的動態(tài)代理是使用Proxy.newProxyInstance()
方法生成的.
先觀察下 Proxy.newProxyInstance()
的參數(shù).
Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h
)
ClassLoader loader:
表示加載生成代理類的類加載器,通常情況下是直接使用
當前線程的類加載器 Thread.currentThread().getContextClassLoader()
,
或者 使用 代理接口的類加載器. 如 Subject.class.getClassLoader()
.
少部分情況下,需要使用特殊的類加載器,如Android插件化中,使用動態(tài)代理,可能需要傳入插件對應的類加載器.
Class<?>[] interfaces:
這里,傳入你感興趣的 委托類所實現(xiàn)的接口.
如下面例子中的 Subject.class
, 則你需要傳入 new Class[]{Subject.class}
作為參數(shù).
如果 RealSubject
實現(xiàn)了多個接口Subject1,Subject2,Subject3...
,而你對其中Subject1,Subject2
感興趣,
你可以傳入 new Class[]{Subject1.class,Subject2.class}
, JVM運行時生成的字節(jié)碼類,將會實現(xiàn)傳入的這些接口.
InvocationHandler h
方法調(diào)度處理器接口. 內(nèi)部有一個回調(diào)方法 Object invoke(Object proxy, Method method, Object[] args)
.
實現(xiàn)該方法,可以攔截 上一個參數(shù)傳入的接口的方法, 你可以對這些方法進行增強, 甚至改變方法的行為.
實現(xiàn) InvocationHandler
通常需要傳入一個 委托對象, 然后在invoke()
方法中,對委托對象進行修改或者增強操作.
來看一個簡單的動態(tài)代理的例子.
// proxy.Subject.java
public interface Subject {
void doSomething();
}
// proxy.RealSubject.java
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("RealSubject do ...");
}
}
// ReflectTest.java
public class ReflectTest {
public static void main(String[] args) {
// 委托對象
RealSubject realSubject = new RealSubject();
// 方法調(diào)度處理器
InvocationHandler handler = new ProxyHandler(realSubject);
// 生成代理對象
Subject proxy = (Subject) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{Subject.class},
handler
);
// 這里使用輔助類,保存JVM生成動態(tài)代理類到本地,此時先忽略
ProxyUtils.generateClassFile(realSubject.getClass(), "SubjectProxy");
// 代理運行方法
proxy.doSomething();
}
// 代理方法調(diào)度器
static class ProxyHandler implements InvocationHandler {
// 委托對象
RealSubject realSubject;
ProxyHandler(RealSubject realSubject) {
this.realSubject = realSubject;
}
/**
* 在此方法中,進行委托對象方法的增強或者修改
* 該示例中只是簡單的對其添加log.
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("do before ...");
// 處理委托對象的方法
Object result = method.invoke(realSubject, args);
System.out.println("do after ...");
return result;
}
}
}
//do before ...
//RealSubject do ...
//do after ...
從這里,我們可以窺探出 動態(tài)代理與靜態(tài)代理的一些差別,我們來總結一下.
動態(tài)代理使用
InvocationHandler
的invoke()
方法來統(tǒng)一處理委托類的方法, 也就是說, 即使委托對象實現(xiàn)的接口中有幾百個方法,我們也只要在這一個方法中處理即可.換句話說,如果委托類增加了一些方法,而我們不需要對方法進行修改,那動態(tài)代理這部分的代碼,可以不需要改動,靜態(tài)代理則達不到這種效果.
動態(tài)代理在運行時,動態(tài)生成字節(jié)碼數(shù)據(jù),我們在編寫代碼的時候是看不到真正的代理類代碼.
動態(tài)代理一個重要的應用就是
AOP(面向切面編程)
, 主要處理 日志記錄濒蒋,性能統(tǒng)計,安全控制楼誓,事務處理玉锌,異常處理等等
如果我們需要對一些 不對外開發(fā)的或者不容易直接操作的類或者api進行操作,我們也可以使用 動態(tài)代理來處理. 如Android插件化中,對系統(tǒng)資源的HOOK, 如對
ActivityManager
的Hook就用到動態(tài)代理技術.
動態(tài)代理原理
Java的動態(tài)代理是通過 Proxy .newProxyInstance()
方法來生成的. 我們來追蹤下源碼, 源碼中 我把無關和不打緊的代碼去掉,以便分析.
public class Proxy {
// 生成的動態(tài)代理的構造參數(shù)類
private static final Class<?>[] constructorParams = {InvocationHandler.class};
// 動態(tài)代理類的緩存池
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new Proxy.ProxyClassFactory());
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException {
final Class<?>[] intfs = interfaces.clone();
// 1. 生成動態(tài)代理類
Class<?> cl = getProxyClass0(loader, intfs);
try {
// 2. 反射創(chuàng)建動態(tài)代理類實例
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
} catch (Exception e) {
throw new InternalError(e.toString(), e);
}
}
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
// 如果通過傳入的 類加載器和接口類已經(jīng)緩存過, 則直接從緩存中獲取 之前已經(jīng)生成的代理類
// 否則, 將會通過ProxyClassFactory來創(chuàng)建一份動態(tài)代理類
return proxyClassCache.get(loader, interfaces);
}
/**
* 生成代理類的工廠
*/
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
// 生成的動態(tài)代理類的類名前綴
private static final String proxyClassNamePrefix = "$Proxy";
// 為生成的動態(tài)代理類加上數(shù)字標簽
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
// 1. 驗證傳入的接口類,是否是用傳入的 類加載器 加載的,不是則報錯
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(intf + " is not visible from class loader");
}
// 2. 驗證傳入的類是接口類型, 不是則報錯
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");
}
// 3. 驗證是否傳入重復的接口類 , 是則報錯
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 4. 對所有非公開的接口進行判斷,
// 4.1 判斷所有的非公開接口,是不是在同一個包下, 不在 則報錯, 在則生成的代理類與其 同包名
// 4.2 如果沒有非公開的接口,直接使用默認的包名 : com.sun.proxy
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.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, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
// 5. 生成動態(tài)代理類的名稱
// 形如 : com.sun.proxy.$Proxy0 com.sun.proxy.$Proxy1
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 6. 通過代理生成器 直接生成 代理類的 字節(jié)碼數(shù)據(jù) 即 .class類型的數(shù)據(jù)
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 7. 將字節(jié)碼數(shù)據(jù)轉(zhuǎn)為 Class 類
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
}
從源碼中我們可以看出, 生成代理對象大步驟分為兩個.
- 生成動態(tài)代理類對象
- 反射生成動態(tài)代理實例對象
我們主要看第一個步驟. 它又分為好幾個小的流程, 在上面源碼分析中已經(jīng)很清楚的寫出來,這里就不在贅述.
它最終會調(diào)用 ProxyGenerator.generateProxyClass()
方法來生成字節(jié)碼文件.
然而這個過程 我們并不能拿到 這個生成的對象,也就是說 我們從代碼上是看不到這份文件的, 這對我們分析有很大的麻煩.
而既然 代碼是通過 ProxyGenerator.generateProxyClass()
這個方法來生成, 我們可以嘗試通過這個方法來將生成的代碼保存到本地, 以便于分析.
public class ProxyUtils {
/**
* 根據(jù)類信息和提供的代理類名稱,生成字節(jié)碼并且保存到本地
*
* @param clazz 委托類
* @param proxyName 生成代理類的名稱
*/
public static void generateClassFile(Class<?> clazz, String proxyName) {
// 根據(jù)類信息和提供的代理類名稱疟羹,生成字節(jié)碼
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String path = clazz.getResource(".").getPath();
System.out.println(path);
FileOutputStream out = null;
try {
// 生成.class文件保存到本地
out = new FileOutputStream(path + proxyName + ".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
這個類可以幫助我們保存生成的代理類對象, 在上個例子中 有調(diào)用到過 ProxyUtils.generateClassFile(realSubject.getClass(), "SubjectProxy");
,
我們直接打開生成的類來看下.
public final class SubjectProxy extends Proxy implements Subject {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public SubjectProxy(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 doSomething() throws {
try {
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);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("proxy.Subject").getMethod("doSomething");
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
類,并且實現(xiàn)了Subject
接口, 構造函數(shù)傳入 InvocationHandler
對象.
然后,它將實現(xiàn)來的所有方法, 都通過反射的方式,調(diào)用 InvocationHandler.invoke()
方法來實現(xiàn).
而InvocationHandler
中將傳入委托類 來完成反射調(diào)用.
也就是說, 代理類, 最終調(diào)用的是 通過
InvocationHandler.invoke()
方法進行增強或者修改的, 委托類(RealSubject)所對應的方法.
至此,我們分析完了, 動態(tài)代理實現(xiàn)的原理.
動態(tài)代理的不足
動態(tài)代理是一定要基于接口的, 如果委托對象沒有實現(xiàn)相應的接口, 是無法對其創(chuàng)建動態(tài)代理的.
JDK為我們提供的代理實現(xiàn)方案確實沒法解決這個問題, 那怎么辦呢? 可以使用 CGLib動態(tài)代理
, 這里就不對其進行展開, 感興趣的可以自行搜索了解.