【轉】Java設計模式之代理模式
代理模式是Java常見的設計模式之一。所謂代理模式是指客戶端並不直接呼叫實際的物件,而是通過呼叫代理,來間接的呼叫實際的物件。
代理模式的定義:
為其他物件提供一種代理,以控制對這個物件的訪問;
為其他物件提供一種代理以控制對這個物件的訪問。代理物件起到中介作用,可去掉功能服務或增加額外的服務。
代理模式的分類:
遠端代理模式:為不同地理的物件提供區域網代表物件(例子:通過遠端代理可以監控各個店鋪,使之可以直觀的瞭解店裡的情況)
虛擬代理:根據需要將資源消耗很大的物件進行延遲,真正需要的時候進行建立
保護代理:控制使用者的訪問許可權
智慧引用代理:提供對目標物件提供額外的服務(火車票代售處)
為什麼要採用這種間接的形式來呼叫物件呢?一般是因為客戶端不想直接訪問實際的物件,或者訪問實際的物件存在困難,因此通過一個代理物件來完成間接的訪問。
在現實生活中,這種情形非常的常見,比如請一個律師代理來打官司。
下面例子的程式碼可以訪問原始碼。歡迎star,歡迎fork
代理模式的UML圖
從UML圖中,可以看出代理類與真正實現的類都是繼承了抽象的主題類,這樣的好處在於代理類可以與實際的類有相同的方法,可以保證客戶端使用的透明性。
代理模式的實現
代理模式可以有兩種實現的方式,一種是靜態代理類,另一種是各大框架都喜歡的動態代理。下面我們主要講解一下這兩種代理模式
靜態代理
我們先看針對上面UML實現的例子,再看靜態代理的特點。
Subject介面的實現
public interface Subject {
void visit();
}
實現了Subject介面的兩個類:
public class RealSubject implements Subject { private String name = "byhieg"; @Override public void visit() { System.out.println(name); } } public class ProxySubject implements Subject{ private Subject subject; public ProxySubject(Subject subject) { this.subject = subject; } @Override public void visit() { subject.visit(); } }
具體的呼叫如下:
public class Client {
public static void main(String[] args) {
ProxySubject subject = new ProxySubject(new RealSubject());
subject.visit();
}
}
通過上面的代理程式碼,我們可以看出代理模式的特點,代理類接受一個Subject介面的物件,任何實現該介面的物件,都可以通過代理類進行代理,增加了通用性。但是也有缺點,每一個代理類都必須實現一遍委託類(也就是realsubject)的介面,如果介面增加方法,則代理類也必須跟著修改。其次,代理類每一個介面物件對應一個委託物件,如果委託物件非常多,則靜態代理類就非常臃腫,難以勝任。
動態代理
動態代理有別於靜態代理,是根據代理的物件,動態建立代理類。這樣,就可以避免靜態代理中代理類介面過多的問題。動態代理是實現方式,是通過反射來實現的,藉助Java自帶的java.lang.reflect.Proxy,通過固定的規則生成。
其步驟如下:
編寫一個委託類的介面,即靜態代理的(Subject介面)
實現一個真正的委託類,即靜態代理的(RealSubject類)
建立一個動態代理類,實現InvocationHandler介面,並重寫該invoke方法
在測試類中,生成動態代理的物件。
第一二步驟,和靜態代理一樣,不過說了。第三步,程式碼如下:
public class DynamicProxy implements InvocationHandler {
private Object object;
public DynamicProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object, args);
return result;
}
}
第四步,建立動態代理的物件
Subject realSubject = new RealSubject();
DynamicProxy proxy = new DynamicProxy(realSubject);
ClassLoader classLoader = realSubject.getClass().getClassLoader();
Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new Class[]{Subject.class}, proxy);
subject.visit();
建立動態代理的物件,需要藉助Proxy.newProxyInstance。該方法的三個引數分別是:
ClassLoader loader表示當前使用到的appClassloader。
Class<?>[] interfaces表示目標物件實現的一組介面。
InvocationHandler h表示當前的InvocationHandler實現例項物件。
關於動態代理的使用,我們就介紹到這裡。關於動態代理的實現、藉助非JDK庫實現動態代理、以及他們的優缺點放到以後再介紹。
2018-11-29
靜態代理的一些缺陷:
①代理類和被代理類實現了相同的介面,導致程式碼的重複,如果介面增加一個方法,那麼除了被代理類需要實現這個方法外,代理類也要實現這個方法,增加了程式碼維護的難度。
②代理物件只服務於一種型別的物件,如果要服務多型別的物件。勢必要為每一種物件都進行代理,靜態代理在程式規模稍大時就無法勝任了。
動態代理定義:
在程式執行期間根據需要動態建立代理類及其例項來完成具體的功能。(動態代理主要分為JDK動態代理和cglib動態代理兩大類)。
動態代理Proxy.newProxyInstance()方法原始碼分析:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//首先,傳入的處理器不能為空
Objects.requireNonNull(h);
//拷貝一份代理類的介面
final Class<?>[] intfs = interfaces.clone();
//安全檢查,不太明白 - -
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
//查詢(在快取中已經有)或生成指定的代理類的class物件。
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
* 用已經設定好的處理器呼叫其構造方法
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//這裡生成代理物件
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
JDK動態代理和CGLIB代理:
jdk代理的類必須實現某些介面,cglib的代理通過子類的方式實現代理