前言
本文是我在學(xué)習(xí)代理模式時的一篇筆記,除了對代理模式、靜態(tài)和動態(tài)代理的概念和實(shí)現(xiàn)進(jìn)行了描述外,還有關(guān)于動態(tài)代理中的InvocationHandler的一些實(shí)驗(yàn)性的實(shí)例,測試了InvocationHandler中的proxy參數(shù)的來源、多個方法的接口得到的代理類的具體情況以及代理類的生成。
代理模式
概述
代理模式是一種常用的設(shè)計模式,它為其他對象提供一個代理以控制對某個對象的訪問。代理對象負(fù)責(zé)為實(shí)際的處理對象進(jìn)行一些數(shù)據(jù)預(yù)處理等工作。根據(jù)代理類的生成時間不同,代理模式也可以分為靜態(tài)代理和動態(tài)代理
詳解
代理模式一般涉及到的角色有四種:
1.主題接口:定義代理類和真實(shí)主題的方法的公共對外方法,也是代理類代理真實(shí)主題的方法。
2.真實(shí)主題:真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類。
3.代理類:用來代理和封裝真實(shí)主題的類。
4.客戶端:使用代理類提供的接口進(jìn)行工作。
代理的優(yōu)點(diǎn)
- 隱藏委托類的實(shí)現(xiàn),調(diào)用者只需要與代理類進(jìn)行交互。
- 解耦,對業(yè)務(wù)邏輯的改變只需要改變代理類和委托類,不需要對客戶端類進(jìn)行修改
代理模式的使用場景
代理模式的典型使用場景如:struts2中的action調(diào)用,hibernate的懶加載,spring的aop等。大體分為以下幾種:
1.在原方法執(zhí)行之前和之后做一些相關(guān)操作,可以使用代理實(shí)現(xiàn)。例如記錄日志和數(shù)據(jù)預(yù)處理。
2.封裝真實(shí)的主題類,隱藏真實(shí)的業(yè)務(wù)邏輯接口,只暴露給使用者公用的接口。
3.還可以應(yīng)用于延遲加載。
靜態(tài)代理
概念
靜態(tài)代理是在代碼編譯后程序運(yùn)行前就存在代碼的字節(jié)碼文件,代理類和委托類的關(guān)系已經(jīng)確定好了。
一個例子
一個房主想要出售自己的房子,但是房主并沒有時間親自去出售,所以他找了一個代理幫他去尋找買主。
首先定義一個Sales接口來表示這個對象可以銷售房子:
public interface Sales{
public void sale();
}
房主類
Class Owner implements Sales{
@Override
public void sale(){
System.out.println("房主銷售房子");
}
}
被委托的中介類:
Class Agent impelments Sales{
private Owner owner;
@Override
public void sale(){
System.out.println("中介聯(lián)系買家,協(xié)商賣房事宜并聯(lián)系房主");
owner.sale();
System.out.println("賣房完成,中介收取中介費(fèi)");
}
}
可以看出,當(dāng)調(diào)用中介類Agent
中的````sale```方法時,中介經(jīng)過自己的處理后調(diào)用自己所對應(yīng)的房主類的同名方法進(jìn)行真正的業(yè)務(wù)邏輯,在賣房完成后再進(jìn)行后續(xù)處理(收取中介費(fèi))。
在這種情況下的買家類寫法如下:
Class Customer {
public void buy{
Sales sale = new Agent();
sale.sale();
}
}
客戶類只需要調(diào)用中介類的售賣方法就可以完成購買的過程,對于客戶來說,只需要知道中介類的相應(yīng)方法即可,不需要關(guān)心到底是誰執(zhí)行了業(yè)務(wù)邏輯。
靜態(tài)代理的局限
由于客戶端是調(diào)用的主題接口進(jìn)行操作的,所以同一個代理類只能代理一個功能。如果存在多個需要代理的接口,并且代理的步驟相同只是方法調(diào)用不同,那么靜態(tài)代理會導(dǎo)致代碼十分臃腫。
如果接口增加一個新的方法,那么委托類和代理類都需要實(shí)現(xiàn)這個方法,維護(hù)相對比較復(fù)雜。
動態(tài)代理
使用動態(tài)代理需要一個InvocationHandler接口和一個Proxy類。
InvocationHandler接口是創(chuàng)建委托類和代理類關(guān)系的連接的中間類必須實(shí)現(xiàn)的接口,其唯一的方法Invoke方法用于集中處理代理類對象上的方法調(diào)用,在該方法上通常實(shí)現(xiàn)對委托類的代理訪問。
Proxy類是Java動態(tài)代理機(jī)制的核心工具類,它提供了一組靜態(tài)方法來動態(tài)生成代理類以及代理類對象。
相關(guān)類詳解
InvocationHandler
InvocationHandler接口只需要實(shí)現(xiàn)一個方法,即public Object invoke(Object proxy,Method method,Object[] params);
這個方法用來組裝代理類。
Proxy
Proxy類提供了一系列靜態(tài)方法,用來提供代理類的組裝功能
// 方法 1: 該方法用于獲取指定代理對象所關(guān)聯(lián)的調(diào)用處理器
static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:該方法用于獲取關(guān)聯(lián)于指定類裝載器和一組接口的動態(tài)代理類的類對象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
// 方法 3:該方法用于判斷指定類對象是否是一個動態(tài)代理類
static boolean isProxyClass(Class cl)
// 方法 4:該方法用于為指定類裝載器、一組接口及調(diào)用處理器生成動態(tài)代理類實(shí)例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
實(shí)現(xiàn)步驟
1.實(shí)現(xiàn)InvocationHandler接口定義自己的調(diào)用處理器。
2.通過Proxy類的newProxyInstance方法指定代理類的ClassLoader對象和代理要實(shí)現(xiàn)的Interface以及調(diào)用處理器InvocationHandler對象,從而創(chuàng)建動態(tài)代理的對象。
一個實(shí)例
使用動態(tài)代理的方式實(shí)現(xiàn)上邊的售房過程:
售房接口
public interface Sales{
public void sale();
}
售房委托類
public class Owner implements Sales{
@Override
public void sale(){
System.out.println("房主賣房");
}
}
invocation handler類
public class ReadyInvocationHandler implements InvocationHandler{
private Object ins;
public ReadyInvocationHandler(Object ins){
this.ins = ins;
}
@Override
public Object invoke(Object proxy,Method method,Object[] params){
Object result = null;
System.out.println("中介與客戶討論買方事宜,聯(lián)系房主");
method.invoke(ins,params);
System.out.println("房子賣出,中介收取中介費(fèi)");
return result;
}
}
客戶類
public class Customer{
public static void main(String[] args){
Sales sale = Proxy.newProxyInstance(Owner.class.getClassLoader,Owner.class.getInterfaces(),new ReadyInvocationHandler(new Owner));
sale.sale();
}
}
客戶類的運(yùn)行結(jié)果如下
中介與客戶討論買方事宜,聯(lián)系房主
房主賣房
房子賣出,中介收取中介費(fèi)
結(jié)論
InvocationHandler是定義在代理類組裝過程中生成代理類的相應(yīng)接口方法的接口,在接口的invoke方法中,method.invoke(object,params)
的調(diào)用即代表靜態(tài)代理中代理類對委托類的相應(yīng)接口方法的調(diào)用,在invoke的調(diào)用之前的操作即是代理類中對數(shù)據(jù)的預(yù)操作,之后的操作是代理類中后續(xù)的操作。而Proxy類在生成代理類時,就通過invoke來組裝相應(yīng)的代理類,生成字節(jié)碼并加載實(shí)例化。
當(dāng)一個接口有多個方法時的情況
主題接口
public interface Multi {
public void methodA();
public void methodB();
}
委托類
public class InstanceMulti implements Multi {
@Override
public void methodA() {
System.out.println("a");
}
@Override
public void methodB() {
System.out.println("b");
}
}
InvocationHandler實(shí)現(xiàn)
public class MultiInvocationHandler implements InvocationHandler {
private Object ins;
public MultiInvocationHandler(Object ins) {
this.ins = ins;
}
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
System.out.println("before");
Object result = arg1.invoke(ins, arg2);
System.out.println("after");
return result;
}
}
客戶類
public class CustomerMulti {
public static void main(String[] args) {
Multi m1 = (Multi) Proxy.newProxyInstance(InstanceMulti.class.getClassLoader(), InstanceMulti.class.getInterfaces(), new MultiInvocationHandler(new InstanceMulti()));
Saleable s = (Saleable) Proxy.newProxyInstance(Owner.class.getClassLoader(), Owner.class.getInterfaces(), new ReadyInvocationHandler(new Owner()));
System.out.println(m1.getClass().getName());
System.out.println(s.getClass().getName());
m1.methodA();
m1.methodB();
s.sale();
}
}
這里加入了之前寫的單方法接口的一個代理來進(jìn)行對比。
運(yùn)行結(jié)果
com.sun.proxy.$Proxy0
com.sun.proxy.$Proxy1
before
a
after
before
b
after
com.sun.proxy.$Proxy1
和客戶談合同,準(zhǔn)備聯(lián)系房主
房主賣房
完事之后收取中介費(fèi)
結(jié)果表明了,對于多個方法的接口,一個InvocationHandler會規(guī)定所有的代理類方法的組裝方式。
另外關(guān)于InvocationHanlder中的proxy,在結(jié)果中地就行是我在InvocationHandler中輸出的proxy的類名稱,在第二行輸出的是得到的代理類名稱??梢悦黠@看出,InvocationHandler中的proxy是最后實(shí)際的代理類,而最終得到的代理類是JVM在運(yùn)行過程中臨時生成的,名稱格式是$Proxy+編號。