前言:本篇文章爲閱讀《Head First設計模式》一書中的代理模式一章後整理而來,本篇博文主要介紹該章節提到的動態代理(保護代理),後續會補上該章節中講到的遠程代理和虛擬代理。
一、使用Java API的代理,創建一個保護代理
Java在java.lang.reflect包中有自己的代理支持,通過這個包可以在運行時動態的創建代理類,實現一個或多個接口,並將方法的調用轉發到所指定的類。因爲實際的代理類是在運行時創建的,因此稱這種Java技術爲:動態代理。
接下來要利用Java的動態代理創建一個代理實現(保護代理)。在此之前,先來看一下類圖,瞭解一下動態代理是怎麼一回事。
Java爲我們創建了Proxy類,但需要告訴Proxy類我們要做什麼。但是我們不能將代碼放入Proxy類中,因爲Proxy不是我們直接創建的。所以這時需要將代碼放入InvocationHandler中。InvocationHandler的工作是響應代理的任何調用,可以把InvocationHandler當成代理收到方法調用後,請求做實際工作的對象。
二、實現動態代理
假設我們需要實現一個約會服務系統。該服務系統中涉及到一個接口PersonBean,通過該接口允許設置或獲取一個人的信息。
PersonBean接口代碼如下:
package com.pattern.proxy.dynamic;
/**
* PersonBean接口,通過該接口允許設置或取得一個人的信息
*
* @date:2017年3月13日 下午9:54:02
*/
public interface PersonBean {
// 獲取姓名
String getName();
// 獲取性別
String getGender();
// 獲取興趣愛好
String getInterests();
// HotOrNot評分
int getHotOrNotRating();
void setName(String name);
void setGender(String gender);
void setInterests(String interests);
void setHotOrNotRating(int rating);
}
PersonBeanImpl實現PersonBean接口,代碼如下:
package com.pattern.proxy.dynamic;
/**
* 實現PersonBean接口
*
* @date:2017年3月13日 下午10:04:12
*/
public class PersonBeanImpl implements PersonBean {
private String name;
private String gender;
private String interests;
private int rating;
private int ratingCount = 0;
@Override
public String getName() {
return name;
}
@Override
public String getGender() {
return gender;
}
@Override
public String getInterests() {
return interests;
}
@Override
public int getHotOrNotRating() {
if(ratingCount == 0)
return 0;
return rating / ratingCount;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setGender(String gender) {
this.gender = gender;
}
@Override
public void setInterests(String interests) {
this.interests = interests;
}
@Override
public void setHotOrNotRating(int rating) {
this.rating += rating;
ratingCount++;
}
}
現在有如下需求,該系統允許用戶設置自己的信息,但是不應該允許用戶篡改別人的數據。但是HotOrNot評分則相反,用戶不能更改自己的評分,但是可以給他人評分。在目前PersonBean實現中所有的方法都是公開的,任何人都可以調用。
所以現在我們需要解決這些問題,要修正這些問題,必須創建兩個代理:一個代理訪問自己的PersonBean對象,另一個訪問其他用戶的PersonBean對象。創建這種代理,我們必須使用Java API的動態代理。Java會爲我們創建兩個代理,我們只需要提供handler來處理代理轉發來的方法。我們需要寫兩個InvocationHandler,其中一個給擁有者使用,另一個給非擁有者使用。當代理的方法被調用時,代理就會把這個調用轉發給InvocationHandler,但這個並不是通過調用InvocationHandler的相應方法做到的。那是如何做到的?讓我們看一下InvocationHandler接口:
InvocatonHandler只有一個invoke()的方法,不管代理被調用的是何種方法,處理器被調用的一定是invoke()方法。工作過程如下:
1.假設proxy的setHotOrNotRating()方法被調用。
proxy.setHotOrNotRating(9);
2.proxy會接着調用InvocationHandler的invoke()方法。
invoke(Object proxy, Method method, Object[] args)
3.handler決定如何處理請求。
return method.invoke(person, args);
實現OwnerInvocationHandler,代碼如下:
package com.pattern.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 調用處理器實現了InvocationHandler接口
* 當用戶需要查看或設置自己的信息時,調用該處理器進行訪問控制
*
* @date:2017年3月13日 下午10:17:58
*/
public class OwnerInvocationHandler implements InvocationHandler {
private PersonBean personBean;
// OwnerInvocationHandler持有PersonBean類對象的引用
public OwnerInvocationHandler(PersonBean personBean) {
this.personBean = personBean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
try {
if(method.getName().startsWith("get")) { // 用戶允許查看自己的相關信息
return method.invoke(personBean, args);
} else if(method.getName().equals("setHotOrNotRating")) { // 用戶不能自己給自己打分
throw new IllegalAccessException();
} else if(method.getName().startsWith("set")) { // 用戶可以對其他信息進行設置
return method.invoke(personBean, args);
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 如果調用的是其他的方法,一律處理,返回null
return null;
}
}
實現NonOwnerInvocationHandler,代碼如下:
package com.pattern.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 用戶訪問其他用戶時通過該處理類進行訪問處理
*
* @date:2017年3月13日 下午10:37:45
*/
public class NonOwnerInvocationHandler implements InvocationHandler {
private PersonBean personBean;
public NonOwnerInvocationHandler(PersonBean personBean) {
this.personBean = personBean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().startsWith("get")) { // 允許訪問獲取其他用戶的信息
return method.invoke(personBean, args);
} else if(method.getName().equals("setHotOrNotRating")) { // 可以給其他用戶進行打分
return method.invoke(personBean, args);
} else if(method.getName().startsWith("set")) { // 不能更改其他用戶的信息
throw new IllegalAccessException();
}
// 對其他的方法調用不處理,放回null
return null;
}
}
接下來,我們需要創建動態Proxy類,並實例化Proxy對象。我們創建擁有者代理,該代理可以將它的方法調用轉發給OwnerInvocationHandler,代碼如下:
/**
* 獲取用戶處理自己信息的代理對象
*
* 此方法需要一個PersonBean對象作爲參數,然後返回
* 他的代理,因爲代理和被代理對象(主題subject)
* 實現了相同的接口,該方法最終返回一個PersonBean
*
* @param personBean
* @return
*/
public PersonBean getOwnerProxy(PersonBean personBean) {
return (PersonBean) Proxy.newProxyInstance( // 通過Proxy類的靜態方法newProxyInstance創建代理
personBean.getClass().getClassLoader(), // 將PersonBean的類載入器當作參數
personBean.getClass().getInterfaces(), // 代理需要實現的接口
new OwnerInvocationHandler(personBean)); // 處理器
}
非擁有者的代理類創建代碼如下:
/**
* 獲取用戶處理他人信息的代理對象
*
* @param personBean
* @return
*/
public PersonBean getNonOwnerProxy(PersonBean personBean) {
return (PersonBean) Proxy.newProxyInstance(
personBean.getClass().getClassLoader(),
personBean.getClass().getInterfaces(),
new NonOwnerInvocationHandler(personBean));
}
最後我們來測試下,測試代碼如下:
package com.pattern.proxy.dynamic;
import java.lang.reflect.Proxy;
public class ProxyTestDriver {
private PersonBean personBean;
public ProxyTestDriver() {
/*
* 初始化數據庫
* initializeDatabase();
* */
personBean = new PersonBeanImpl();
personBean.setName("Joe Javabean");
personBean.setInterests("Singing");
personBean.setGender("male");
personBean.setHotOrNotRating(7);
}
public static void main(String[] args) {
ProxyTestDriver testDriver = new ProxyTestDriver();
testDriver.driver();
}
public void driver() {
/*
* 從數據庫中讀取一個人信息
* PersonBean personBean = getPersonFromDataBase("Joe Javabean");
*/
PersonBean joe = personBean;
// 創建處理自己信息的代理對象
PersonBean ownerProxy = getOwnerProxy(joe);
System.out.println("Name is " + ownerProxy.getName());
System.out.println("before set interests:" + ownerProxy.getInterests());
// 用戶修改了自己的愛好
ownerProxy.setInterests("reading");
System.out.println("after set interests:" + ownerProxy.getInterests());
try { // 用戶嘗試修改自己的評分
ownerProxy.setHotOrNotRating(9);
} catch (Exception e) {
System.out.println("can't set rating from owner proxy");
}
System.out.println("rating is " + ownerProxy.getHotOrNotRating());
System.out.println("========================================");
// 創建處理他人信息的代理類
PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
System.out.println("Name is " + nonOwnerProxy.getName());
System.out.println("before set interests:" + nonOwnerProxy.getInterests());
try {
// 嘗試修改他人興趣
nonOwnerProxy.setInterests("painting");
} catch (Exception e) {
System.out.println("can't set interests from non owner proxy");
}
System.out.println("after set interests:" + nonOwnerProxy.getInterests());
nonOwnerProxy.setHotOrNotRating(5);
System.out.println("rating is " + nonOwnerProxy.getHotOrNotRating());
}
/**
* 獲取用戶處理自己信息的代理對象
*
* 此方法需要一個PersonBean對象作爲參數,然後返回
* 他的代理,因爲代理和被代理對象(主題subject)
* 實現了相同的接口,該方法最終返回一個PersonBean
*
* @param personBean
* @return
*/
public PersonBean getOwnerProxy(PersonBean personBean) {
return (PersonBean) Proxy.newProxyInstance( // 通過Proxy類的靜態方法newProxyInstance創建代理
personBean.getClass().getClassLoader(), // 將PersonBean的類載入器當作參數
personBean.getClass().getInterfaces(), // 代理需要實現的接口
new OwnerInvocationHandler(personBean)); // 處理器
}
/**
* 獲取用戶處理他人信息的代理對象
*
* @param personBean
* @return
*/
public PersonBean getNonOwnerProxy(PersonBean personBean) {
return (PersonBean) Proxy.newProxyInstance(
personBean.getClass().getClassLoader(),
personBean.getClass().getInterfaces(),
new NonOwnerInvocationHandler(personBean));
}
}
執行結果: