代理模式是指,爲其他對象提供一種代理以控制這個對象的訪問.在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶類和目標類對象之間起到中介的作用.
1.1靜態代理
先上代碼有個直觀感受
1.創建一個接口
//主業務方法:本接口中的方法將要被代理增強
public interface ISomeService {
String doFirst();
void doSecond();
}
2.再創建一個類實現這個接口(我們稱他爲目標類)
//目標類:代理類需要增強的類
public class ISomeServiceImpl implements ISomeService {
@Override
public String doFirst() {
System.out.println("執行doFirst");
return "abcde";
}
@Override
public void doSecond() {
System.out.println("執行doSeconed");
}
}
現在我想怎麼做呢?我想讓兩個ISomeServiceImpl的實例化對象分別執行這兩個方法,但是第二個對象在執行doFirst()的時候能夠把打印出來的字母變成大寫(功能增強).
於是我們的代理濃重登場
3.創建一個代理類
//靜態代理類
public class ServiceProxy implements ISomeService { //實現相同的接口
private ISomeService target;
public ServiceProxy() {
target = new ISomeServiceImpl(); //先來一個ISomeService的實例化對象
}
@Override
public String doFirst() {
// 調用目標對象的目標方法,該方法返回全小寫字母
String result = target.doFirst();
// 增強:在這裏將目標方法全小寫字母變成全大寫
result = result.toUpperCase();
return result;
}
@Override
public void doSecond() {
target.doSecond(); //在這裏並沒有做更多的操作,代理的方法調用目標類的方法
}
}
4.建個test來測試測試吧
public class MyTest {
public static void main(String[] args) {
// 創建一個目標對象
ISomeService iservice = new ISomeServiceImpl();
System.out.println(iservice.doFirst()); // print:"執行doFirst"和"abcde"
iservice.doSecond(); // 執行doSeconed,print:執行doSeconed
//同時我再創建一個代理類的話..
ISomeService pservice = new ServiceProxy();
System.out.println(pservice.doFirst()); // print:執行doFirst ABCDE
pservice.doSecond(); // 還是print:執行doSeconed
}
}
看了這個例子,是不是覺得靜態代理不再神祕了?那麼接下來,再把它小改進:在代理類中增加一個有參構造
//靜態代理類
public class ServiceProxy implements ISomeService {
private ISomeService target;//還是先創建一個目標內的對象,代理人?哈哈
public ServiceProxy() {
target = new ISomeServiceImpl();
}
public ServiceProxy(ISomeService target) {//創建一個有參構造,傳入目標類對象
super();
this.target = target;
}
@Override
//下面和之前一樣
public String doFirst() {
// 調用目標對象的目標方法,該方法返回全小寫字母
String result = target.doFirst();
// 增強:將目標方法全小寫字母變成全大寫
result = result.toUpperCase();
return result;
}
@Override
public void doSecond() {
target.doSecond();
}
}
對代理類做了這樣的改變後,在測試類我們就可以這樣做了:
public class MyTest {
public static void main(String[] args) {
ISomeService target = new ISomeServiceImpl();
ISomeService service = new ServiceProxy(target);
System.out.println(service.doFirst());// print:執行doFirst ABCDE
//假設我還想調代理前的方法..
System.out.println(target.doFirst()); //print:執行doFirst abcde
service.doSecond();
}
}
是不是感覺代理類和目標類的關係更加緊密0
了呢?
1.2動態代理
動態代理和靜態代理的區別就是:靜態代理有代理類,動態代理麼有代理類.沒有了律師團,怎麼幫我打官司呢?當然只好請律師了
接口和實現類不變,但是我把ServiceProxy類刪掉
直接上test類
public class MyTest {
public static void main(String[] args) {
final ISomeService target = new ISomeServiceImpl(); // 在內部類中不能引用一個非final的變量,這點需要注意
//重點來了:
ISomeService service = (ISomeService) Proxy.newProxyInstance(target
.getClass().getClassLoader(), // 目標類的類加載器
target.getClass().getInterfaces(),// 目標類所實現的所有接口
new InvocationHandler() {// 口怕,第三個參數居然是匿名內部類..其實就是一個實現了InvocationHandler接口的類
// proxy:代理對象
// method:目標方法
// args:目標方法的參數列表
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
Object result = method.invoke(target, args);// 這裏運用到了反射的知識
String stresult = result.toString().toUpperCase();
return stresult;
}
});
System.out.println(service.doFirst());// print:"執行doFirst"和"ABCDE"
System.out.println(target.doFirst());
}
下面我們把那個最長的方法處理出來看看:
newProxyInstance(ClassLoader loader,
Class[] interfaces,
InvocationHandler h)
其中:
interfaces - 代理類要實現的接口列表
h - 指派方法調用的調用處理程序
返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。
在前面靜態代理例子中:
ISomeService service = new ServiceProxy(target);
我們通過代理類的構造方法得到一個代理類實例,通過這個代理對象執行它自己類中代理的方法從而得到目的.
那我們回過頭來看看動態代理:
newProxyInstance()方法同樣可以返回一個指定接口的代理類實例,只不過是Object類型的,所以要轉型爲代理類接口.
ISomeService service = (ISomeService)Proxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
接着我們再重點關注一下第三個參數:
再簡單說一下:第三個參數實際上是一個InvocationHandler的接口的實例,單接口不允許直接創建實例,所以我們創建一個匿名類不理來實現這個接口
進一步發現,這個InvocationHandler接口實際只有一個方法:
invoke(Object proxy, Method method,Object[] args)
invoke方法在代理實例上處理方法調用並返回結果。在與方法關聯的代理實例上調用方法時,將在調用處理程序上調用此方法。 (這是JDK的中文API文檔說的…)
這就是方法增強的關鍵:
有invoke,有method,是不是想到了反射?
於是我們使用反射的invoke方法:
method.invoke(target, args);
這裏傳入代理對象以及參數,反射的method在newProxyInstance中傳入的interfaces確定.具體就看最後調用哪個了.對反射有疑問的戳這裏(Java反射的使用入門詳解)
簡言之,這一個方法執行了,就相當於目標類的方法執行了.
在本例子中,doFirst()是返回一個String類型,不過這裏要求用Object來接收.
Object result = method.invoke(target, args);
接下來就進行增強:
String stresult = result.toString().toUpperCase();
最後將增強後的結果返回
return stresult;//終於把原來的doFirst()方法增強了
到這裏就解釋得差不多了.現在我調service.doFirst()得到的絕對是大寫了哈哈哈!!!
但是還沒完!我用service.doSecond()時卻報了空指針異常..怎麼回事呢?
原來我們在執行反射的invoke方法時,默認它是有返回值的(不如上面的result).
但doSeconed()卻沒有返回值,所以result=null.
然後null執行result.toString().toUpperCase()就報錯了.
如何解決?
當然是在執行方法增強之前先判斷一下result是否爲空就好,不爲空才執行(就是套一個if)
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
Object result = method.invoke(target, args);// 這裏運用到了反射的知識
if (result != null)
result = result.toString().toUpperCase();
return result;
}
就是這樣,喵~