概念
定義
爲其他對象提供一種代理以控制對這個對象的訪問。本質:“控制對象訪問”
結構
該模式有三種角色。代理對象,目標接口,具體的目標對象。
代理對象:
1.代理對象需要和具體的目標對象一樣實現目標接口
2.代理對象要持有具體目標對象的引用,還要提供給用戶傳入具體目標對象的入口
3.代理對象的方法,可以控制對具體目標的訪問
目標接口:
定義代理和具體對象的接口,這樣代理和具體對象都是其子類,纔可以在需要使用目標對象的時候使用代理對象
目標對象:
真正的對象,用戶真正想要調用的方法在這裏,也是真正實現目標接口要求的業務功能的
關鍵思想
代理模式較爲複雜,且有多種不同的變形。但其核心部分是,代理和真正的目標對象都實現同一個接口,而這個接口,主要是用來約束,且提供可以代換的功能。邏輯稍微複雜點的是代理類
小實例
目標接口
/**
* 抽象的目標接口,定義具體的目標對象和代理公用的接口
*/
public interface Subject {
/**
* 示意方法:一個抽象的請求方法
*/
public void request();
}
真正目標對象
/**
* 具體的目標對象,是真正被代理的對象
*/
public class RealSubject implements Subject{
public void request() {
//執行具體的功能處理
}
}
代理對象
/**
* 代理對象
*/
public class Proxy implements Subject{
/**
* 持有被代理的具體的目標對象
*/
private RealSubject realSubject=null;
/**
* 構造方法,傳入被代理的具體的目標對象
* @param realSubject 被代理的具體的目標對象
*/
public Proxy(RealSubject realSubject){
this.realSubject = realSubject;
}
public void request() {
//在轉調具體的目標對象前,可以執行一些功能處理
//轉調具體的目標對象的方法
realSubject.request();
//在轉調具體的目標對象後,可以執行一些功能處理
}
}
實例一
這是一種一對多映射關係。數據庫經常需要使用到級聯查詢,以查出另外的對象。如:有一個部門表和員工表。一個部門下有多個員工,員工表存儲着部門表外鍵id。現在需要查出某個部分下所有員工的信息
不使用設計模式
javaBean類
/**
* 描述用戶數據的對象
*/
public class UserModel {
/**
* 用戶編號
*/
private String userId;
/**
* 用戶姓名
*/
private String name;
/**
* 部門編號
*/
private String depId;
/**
* 性別
*/
private String sex;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDepId() {
return depId;
}
public void setDepId(String depId) {
this.depId = depId;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString(){
return "userId="+userId+",name="+name+",depId="+depId+",sex="+sex+"\n";
}
}
dao層類
/**
* 實現示例要求的功能
*/
public class UserManager {
/**
* 根據部門編號來獲取該部門下的所有人員
* @param depId 部門編號
* @return 該部門下的所有人員
*/
public Collection<UserModel> getUserByDepId(String depId)throws Exception{
Collection<UserModel> col = new ArrayList<UserModel>();
Connection conn = null;
try{
conn = this.getConnection();
String sql = "select * from tbl_user u,tbl_dep d "
+"where u.depId=d.depId and d.depId like ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, depId+"%");
ResultSet rs = pstmt.executeQuery();
while(rs.next()){
UserModel um = new UserModel();
um.setUserId(rs.getString("userId"));
um.setName(rs.getString("name"));
um.setDepId(rs.getString("depId"));
um.setSex(rs.getString("sex"));
col.add(um);
}
rs.close();
pstmt.close();
}finally{
conn.close();
}
return col;
}
/**
* 獲取與數據庫的連接
* @return 數據庫連接
*/
private Connection getConnection() throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
return DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:orcl", "test", "test");
}
}
用戶類
public class Client {
public static void main(String[] args) throws Exception{
UserManager userManager = new UserManager();
Collection<UserModel> col = userManager.getUserByDepId("0101");
System.out.println(col);
}
}
上面的例子是及時加載的,不管查出多少條,連帶用戶信息都會被查出來。如果一次性數據較多,內存開銷會很大,而且會產生浪費。
所以Hibernate使用了代理模式,實現了懶加載,這是我認爲很經典的一個關於代理模式的實例
下面使用的實例,是學習視頻中的例子。包含懶加載和代理的思想。
關鍵步驟:
1.代理類和javaBean類都實現目標接口
2.目標接口包含了javaBean的所有getset方法
3.代理類實現了目標接口,所以實現了所有getset方法。當有一個接口,傳入真正的目標對象(JavaBean)時,用戶調用代理類的getset方法時,可以進行控制獲取屬性。如果調用代理對象不需要懶加載的屬性,那麼直接調用目標對象(JavaBean)的相應get/set方法即可。如果是需要懶加載的屬性,那麼就重置代理類內的目標對象(JavaBean),去數據庫獲取該屬性的數據,然後在返回給用戶,這個時候纔有去數據庫加載數據的動作,所以纔是懶加載
使用設計模式
目標接口
/**
* 定義用戶數據對象的接口
*/
public interface UserModelApi {
public String getUserId();
public void setUserId(String userId);
public String getName();
public void setName(String name);
public String getDepId();
public void setDepId(String depId);
public String getSex();
public void setSex(String sex);
}
實際目標對象
/**
* 描述用戶數據的對象
*/
public class UserModel implements UserModelApi{
/**
* 用戶編號
*/
private String userId;
/**
* 用戶姓名
*/
private String name;
/**
* 部門編號
*/
private String depId;
/**
* 性別
*/
private String sex;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDepId() {
return depId;
}
public void setDepId(String depId) {
this.depId = depId;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString(){
return "userId="+userId+",name="+name+",depId="+depId+",sex="+sex+"\n";
}
}
加載類,不涉及需要外連接其他對象的數據,即不需要懶加載,使用代理的數據
/**
* 實現示例要求的功能
*/
public class UserManager {
/**
* 根據部門編號來獲取該部門下的所有人員
* @param depId 部門編號
* @return 該部門下的所有人員
*/
public Collection<UserModelApi> getUserByDepId(String depId)throws Exception{
Collection<UserModelApi> col = new ArrayList<UserModelApi>();
Connection conn = null;
try{
conn = this.getConnection();
//只需要查詢userId和name兩個值就可以了
String sql = "select u.userId,u.name "
+"from tbl_user u,tbl_dep d "
+"where u.depId=d.depId and d.depId like ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, depId+"%");
ResultSet rs = pstmt.executeQuery();
while(rs.next()){
//這裏是創建的代理對象,而不是直接創建UserModel的對象
Proxy proxy = new Proxy(new UserModel());
//只是設置userId和name兩個值就可以了
proxy.setUserId(rs.getString("userId"));
proxy.setName(rs.getString("name"));
col.add(proxy);
}
rs.close();
pstmt.close();
}finally{
conn.close();
}
return col;
}
/**
* 獲取與數據庫的連接
* @return 數據庫連接
*/
private Connection getConnection() throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
return DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:orcl", "test", "test");
}
}
代理對象
/**
* 代理對象,代理用戶數據對象
*/
public class Proxy implements UserModelApi{
/**
* 持有被代理的具體的目標對象
*/
private UserModel realSubject=null;
/**
* 構造方法,傳入被代理的具體的目標對象
* @param realSubject 被代理的具體的目標對象
*/
public Proxy(UserModel realSubject){
this.realSubject = realSubject;
}
/**
* 標示是否已經重新裝載過數據了
*/
private boolean loaded = false;
public String getUserId() {
return realSubject.getUserId();
}
public void setUserId(String userId) {
realSubject.setUserId(userId);
}
public String getName() {
return realSubject.getName();
}
public void setName(String name) {
realSubject.setName(name);
}
public void setDepId(String depId) {
realSubject.setDepId(depId);
}
public void setSex(String sex) {
realSubject.setSex(sex);
}
public String getDepId() {
//需要判斷是否已經裝載過了
if(!this.loaded){
//從數據庫中重新裝載
reload();
//設置重新裝載的標誌爲true
this.loaded = true;
}
return realSubject.getDepId();
}
public String getSex() {
if(!this.loaded){
reload();
this.loaded = true;
}
return realSubject.getSex();
}
/**
* 重新查詢數據庫以獲取完整的用戶數據
*/
private void reload(){
System.out.println("重新查詢數據庫獲取完整的用戶數據,userId=="+realSubject.getUserId());
Connection conn = null;
try{
conn = this.getConnection();
String sql = "select * from tbl_user where userId=? ";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, realSubject.getUserId());
ResultSet rs = pstmt.executeQuery();
if(rs.next()){
//只需要重新獲取除了userId和name外的數據
realSubject.setDepId(rs.getString("depId"));
realSubject.setSex(rs.getString("sex"));
}
rs.close();
pstmt.close();
}catch(Exception err){
err.printStackTrace();
}finally{
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public String toString(){
return "userId="+getUserId()+",name="+getName()
+",depId="+getDepId()+",sex="+getSex()+"\n";
}
private Connection getConnection() throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
return DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:orcl", "test", "test");
}
}
用戶類
public class Client {
public static void main(String[] args) throws Exception{
UserManager userManager = new UserManager();
Collection<UserModelApi> col = userManager.getUserByDepId("0101");
//如果只是顯示用戶名稱,那麼不需要重新查詢數據庫
for(UserModelApi umApi : col){
System.out.println("用戶編號:="+umApi.getUserId()+",用戶姓名:="+umApi.getName());
}
//如果訪問非用戶編號和用戶姓名外的屬性,那就會重新查詢數據庫
for(UserModelApi umApi : col){
System.out.println("用戶編號:="+umApi.getUserId()+",用戶姓名:="+umApi.getName()+",所屬部門:="+umApi.getDepId());
}
}
}
懶加載是以時間換空間的形式,及時加載,是以空間換時間的形式
Lazy Load===〉實現機制就是 代理
代理模式相關信息
代理模式是通過創建一個代理對象,用這個代理對象去代表真正的對象,客戶端得到這個代理對象後,對客戶端沒有任何影響,跟得到真正的對象一樣。
客戶端操作代理對象,真正的功能還是由真實的對象完成,不過通過代理進行操作,所以是客戶端操作代理,代理操作真正對象。
因爲代理對象在客戶端和真正對象的中間,是一箇中裝,所以他可以有很多種變形,分類
代理的分類
1.虛代理,即上面的懶加載。通常會用於需要創建開銷很大的對象,且該對象只在需要使用的時候才被真正創建
2.遠程代理。即在不同的地址空間中代表同一對象,不同的地址空間,就是不一樣的機器,不一定在本機,即遠程調用。java典型的RMI技術就是這個
3.copy-on-write代理。只有在對象真正改變了,纔會拷貝目標對象,是虛代理的一個分支
4.保護代理:控制對原始對象的訪問,控制他們的訪問權限
5.緩存代理:爲那些昂貴的操作結果提供臨時存儲空間,以便多個客戶端可以共享結果
6.防火牆代理:保護對象不被惡意用戶訪問和操作
7.同步代理:使多個用戶能同時訪問目標對象且沒有衝突
8.智能指引:當目標接口被引用是,進行額外的動作,比如計算一個對象被引用的額次數
常用的代理有:虛代理,保護代理,遠程代理和智能指引。本篇會有虛代理和保護代理,另外會弄個遠程代理,關於java的RMI技術。
遠程代理:控制訪問遠程對象
虛代理:控制訪問創建開銷大的資源
保護代理:基於權限控制對資源的訪問
保護代理
保護代理是一種權限控制對原始對象訪問的代理,多用與對象應該有不同的訪問權限的時候。保護代理會檢查調用目標對象,從而實現對目標對象的保護
實例二
實例說明:有一個訂單系統,需求是:訂單一旦被創建,只有訂單創建人可以修改訂單數據,其他人不能修改
所以把訂單當做一個訂單對象實例。那麼代理對象只需要控制外部對它的訪問即可。
目標接口
/**
* 訂單對象的接口定義
*/
public interface OrderApi {
/**
* 獲取訂單訂購的產品名稱
* @return 訂單訂購的產品名稱
*/
public String getProductName();
/**
* 設置訂單訂購的產品名稱
* @param productName 訂單訂購的產品名稱
* @param user 操作人員
*/
public void setProductName(String productName,String user);
/**
* 獲取訂單訂購的數量
* @return 訂單訂購的數量
*/
public int getOrderNum();
/**
* 設置訂單訂購的數量
* @param orderNum 訂單訂購的數量
* @param user 操作人員
*/
public void setOrderNum(int orderNum,String user);
/**
* 獲取創建訂單的人員
* @return 創建訂單的人員
*/
public String getOrderUser();
/**
* 設置創建訂單的人員
* @param orderUser 創建訂單的人員
* @param user 操作人員
*/
public void setOrderUser(String orderUser,String user);
}
代理類
/**
* 訂單的代理對象
*/
public class OrderProxy implements OrderApi{
/**
* 持有被代理的具體的目標對象
*/
private Order order=null;
/**
* 構造方法,傳入被代理的具體的目標對象
* @param realSubject 被代理的具體的目標對象
*/
public OrderProxy(Order realSubject){
this.order = realSubject;
}
public void setProductName(String productName,String user) {
//控制訪問權限,只有創建訂單的人員才能夠修改
if(user!=null && user.equals(this.getOrderUser())){
order.setProductName(productName, user);
}else{
System.out.println("對不起"+user+",您無權修改訂單中的產品名稱。");
}
}
public void setOrderNum(int orderNum,String user) {
//控制訪問權限,只有創建訂單的人員才能夠修改
if(user!=null && user.equals(this.getOrderUser())){
order.setOrderNum(orderNum, user);
}else{
System.out.println("對不起"+user+",您無權修改訂單中的訂購數量。");
}
}
public void setOrderUser(String orderUser,String user) {
//控制訪問權限,只有創建訂單的人員才能夠修改
if(user!=null && user.equals(this.getOrderUser())){
order.setOrderUser(orderUser, user);
}else{
System.out.println("對不起"+user+",您無權修改訂單中的訂購人。");
}
}
public int getOrderNum() {
return this.order.getOrderNum();
}
public String getOrderUser() {
return this.order.getOrderUser();
}
public String getProductName() {
return this.order.getProductName();
}
public String toString(){
return "productName="+this.getProductName()+",orderNum="+this.getOrderNum()+",orderUser="+this.getOrderUser();
}
}
測試類
public class Client {
public static void main(String[] args) {
//張三先登錄系統創建了一個訂單
OrderApi order = new OrderProxy(new Order("設計模式",100,"張三"));
//李四想要來修改,那就會報錯
order.setOrderNum(123, "李四");
//輸出order
System.out.println("李四修改後訂單記錄沒有變化:"+order);
//張三修改就不會有問題
order.setOrderNum(123, "張三");
//再次輸出order
System.out.println("張三修改後,訂單記錄:"+order);
}
}
java中的代理
java中有兩種大概念的代理,一爲靜態代理,二爲動態代理
靜態代理:即前面所有自己實現的代理模式,都是靜態代理。但是如果Subject接口一旦發生變化,代理類和目標實例都需要變化,靈活度不夠。
動態代理:使用Java內建對代理模式支持的功能來實現的代理叫動態代理。與靜態相比,區別在於:
1. 靜態代理Subject接口定義的方法,代理類實現Subject接口,就要都實現這些方法。
2. 動態代理始終只有invoke方法,當Subject接口發生變化時,動態代理的接口就不需要變化
3. 動態代理要實現InvocationHandler接口,需要實現invoke方法。同時需要有一個方法,獲取目標接口。看似獲取目標接口,但是實際上在這個方法中有進行綁定,所以獲取的是代理對象
小實例
目標接口
/**
* 訂單對象的接口定義
*/
public interface OrderApi {
/**
* 獲取訂單訂購的產品名稱
* @return 訂單訂購的產品名稱
*/
public String getProductName();
/**
* 設置訂單訂購的產品名稱
* @param productName 訂單訂購的產品名稱
* @param user 操作人員
*/
public void setProductName(String productName,String user);
/**
* 獲取訂單訂購的數量
* @return 訂單訂購的數量
*/
public int getOrderNum();
/**
* 設置訂單訂購的數量
* @param orderNum 訂單訂購的數量
* @param user 操作人員
*/
public void setOrderNum(int orderNum,String user);
/**
* 獲取創建訂單的人員
* @return 創建訂單的人員
*/
public String getOrderUser();
/**
* 設置創建訂單的人員
* @param orderUser 創建訂單的人員
* @param user 操作人員
*/
public void setOrderUser(String orderUser,String user);
}
目標實例
/**
* 訂單對象
*/
public class Order implements OrderApi{
/**
* 訂單訂購的產品名稱
*/
private String productName;
/**
* 訂單訂購的數量
*/
private int orderNum;
/**
* 創建訂單的人員
*/
private String orderUser;
/**
* 構造方法,傳入構建需要的數據
* @param productName 訂單訂購的產品名稱
* @param orderNum 訂單訂購的數量
* @param orderUser 創建訂單的人員
*/
public Order(String productName,int orderNum,String orderUser){
this.productName = productName;
this.orderNum = orderNum;
this.orderUser = orderUser;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName,String user) {
this.productName = productName;
}
public int getOrderNum() {
return orderNum;
}
public void setOrderNum(int orderNum,String user) {
this.orderNum = orderNum;
}
public String getOrderUser() {
return orderUser;
}
public void setOrderUser(String orderUser,String user) {
this.orderUser = orderUser;
}
public String toString(){
return "productName="+this.getProductName()+",orderNum="+this.getOrderNum()+",orderUser="+this.getOrderUser();
}
}
代理類
/**
* 使用Java中的動態代理
*/
public class DynamicProxy implements InvocationHandler{
/**
* 被代理的對象
*/
private OrderApi order = null;
/**
* 獲取綁定好代理和具體目標對象後的目標對象的接口
* @param order 具體的訂單對象,相當於具體目標對象
* @return 綁定好代理和具體目標對象後的目標對象的接口
*/
public OrderApi getProxyInterface(Order order){
//設置被代理的對象,好方便invoke裏面的操作
this.order = order;
//把真正的訂單對象和動態代理關聯起來
OrderApi orderApi = (OrderApi) Proxy.newProxyInstance(
order.getClass().getClassLoader(),
//獲得這個對象所實現的接口。
order.getClass().getInterfaces(),
this);
return orderApi;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//如果是調用setter方法就需要檢查權限
if(method.getName().startsWith("set")){
//如果不是創建人,那就不能修改
if(order.getOrderUser()!=null && order.getOrderUser().equals(args[1])){
//可以操作
return method.invoke(order, args);
}else{
System.out.println("對不起,"+args[1]+",您無權修改本訂單中的數據");
}
}else{
//不是調用的setter方法就繼續運行
return method.invoke(order, args);
}
return null;
}
}
代碼解析
OrderApi orderApi = (OrderApi) Proxy.newProxyInstance(
order.getClass().getClassLoader(),
//獲得這個對象所實現的接口。
order.getClass().getInterfaces(),
this);
newProxyInstance方法創建代理,裏面需要傳入目標對象(不是目標接口)的類加載器,
目標接口(不是目標接口)的所實現的對象的接口
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
每次代理類proxy的所有方法被訪問,該方法都會被會提前調用。
測試類
public class Client {
public static void main(String[] args) {
//張三先登錄系統創建了一個訂單
Order order = new Order("設計模式",100,"張三");
//創建一個動態代理
DynamicProxy dynamicProxy = new DynamicProxy();
//然後把訂單和動態代理關聯起來
OrderApi orderApi = dynamicProxy.getProxyInterface(order);
//以下就需要使用被代理過的接口來操作了
//李四想要來修改,那就會報錯
orderApi.setOrderNum(123, "李四");
//輸出order
System.out.println("李四修改後訂單記錄沒有變化:"+orderApi);
//張三修改就不會有問題
orderApi.setOrderNum(123, "張三");
//再次輸出order
System.out.println("張三修改後,訂單記錄:"+orderApi);
}
}