1. 程式人生 > >【Java】代理模式

【Java】代理模式

        代理模式的定義:為其他物件提供一種代理以控制對這個物件的訪問。在某些情況下,一個物件不適合或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用。


 

【分類】

         代理模式分為靜態代理、動態代理。

         靜態代理是由程式設計師建立或工具生成代理類的原始碼,再編譯代理類。所謂靜態也就是在程式執行前就已經存在代理類的位元組碼檔案,代理類和委託類的關係在執行前就確定了。

         動態代理是在實現階段不用關心代理類,而在執行階段才指定哪一個物件。

【靜態代理例項】


介面類:

public interface UserManager {
	/**
	 * 新增 使用者
	 * 
	 * @param userId
	 * @param userName
	 */
	public void addUser(String userId, String userName);

}

委託類(實現類):

public class UserManagerImpl implements UserManager {

	/**
	 * 新增 使用者
	 * 
	 * @param userId
	 * @param userName
	 */
	public void addUser(String userId, String userName) {
		try {
			System.out.println("UserManagerImpl.addUser() userId-->>" + userId);
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException();
		}
	}

}

代理類:

/**
 * 代理類
 * 
 * @author happy
 * 
 */
public class UserManagerImplProxy implements UserManager {

	private UserManager userManager;

	public UserManagerImplProxy(UserManager userManager) {
		this.userManager = userManager;
	}

	/**
	 * 新增
	 */
	public void addUser(String userId, String userName) {
		try {
			System.out.println("start-->>addUser() userId-->>" + userId);
			userManager.addUser(userId, userName);
			System.out.println("success-->>addUser()");
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("error-->>addUser()");
		}
	}

}

客戶端類:

public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		UserManager userManager = new UserManagerImplProxy(
				new UserManagerImpl());
		userManager.addUser("0001", "張三");
	}

}

【問題】

       靜態代理存在問題:

第一,需要建立大量的代理類

第二,這只是一個新增方法,如果再加入其它方法,豈不是會有很多重複的程式碼。

【動態代理實現】

         動態代理,就是在執行過程中動態的建立一個類似於上文靜態代理中的代理類的過程。JDK動態代理只能對實現了介面的類進行代理。

        動態代理類是一個實現在建立類時在執行時指定的介面列表的類,代理介面是動態代理類實現的一個介面。代理例項(相當於靜態代理中的委託類)是動態代理類的一個例項。每個代理例項都有一個關聯的呼叫處理程式物件(橙汁為Handler類),Handler類可以實現介面InvocationHandler。通過其中一個代理介面的代理例項上的方法呼叫將被指派到代理例項的呼叫處理程式的Invoke方法,並傳遞代理例項、識別呼叫方法的java.lang.reflect.Method物件以及包含引數的Object型別的陣列。


介面類和委託類程式碼如上例靜態代理。略。

動態代理類:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class LogHandler implements InvocationHandler {
	
	private Object targetObject;
	
	public Object newProxyInstance(Object targetObject) {
		this.targetObject = targetObject;
		return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
							   targetObject.getClass().getInterfaces(), this);
	}
	
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("start-->>" + method.getName());
		for (int i=0; i<args.length; i++) {
			System.out.println(args[i]);
		}
		Object ret = null;
		try {
			//呼叫目標方法
			ret = method.invoke(targetObject, args);
			System.out.println("success-->>" + method.getName()); 
		}catch(Exception e) {
			e.printStackTrace();
			System.out.println("error-->>" + method.getName());
			throw e;
		}
		return ret;
	}

}

客戶端類:

public class Client {

	/**
	 * 動態代理
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		LogHandler logHandler = new LogHandler();
		UserManager userManager = (UserManager) logHandler
				.newProxyInstance(new UserManagerImpl());
		String name = userManager.findUser("0001");
		System.out.println("Client.main() --- " + name);
	}

}

動態代理主要用到的兩個類,具體方法介紹如下:

1.InvocationHandler

InvocationHandler是代理例項的呼叫處理程式實現的介面。

2.newProxyInstance

(1)語法結構

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException

(2)詳解

①引數:loader定義了代理類的類載入器,interfaces是指本代理類要實現的介面列表,h指派方法呼叫的呼叫處理程式,也就是實現動態建立代理類的方法,在本文中的兩個例項中都指的是方法本身,故用this來代指。

②返回值:返回一個帶有代理類的指定呼叫處理程式的代理例項,也就是委託類。

3.invoke

(1)語法結構:

Object invoke(Object Proxy,Method method,Object[] args)throws Throwable

(2)詳解:

①引數:Proxy是在其上呼叫方法的代理例項,method相當於代理介面的抽象。args儲存的是物件陣列,用陣列中傳遞的引數來指定具體的委託類方法。

②返回值:該方法的返回值型別與代理介面方法的返回型別要相容。

        簡單說,就是在invoke方法內,展示我們想要對委託類進行的處理方法,然後根據一些引數什麼的,呼叫相應的委託類方法,然後返回相應的結果。

【動態代理的事務應用】

原新增方法如下:

	public void addFlowCard(FlowCard flowCard) throws ApplicationException {
		Connection conn = null;
		try {
			// 取得Connection
			conn = ConnectionManager.getConnection();

			// 開始事務
			ConnectionManager.beginTransaction(conn);

			// 生成流向單單號
			String flowCardVouNo = flowCardDao.generateVouNo();
			// 新增流向單主資訊
			flowCardDao.addFlowCardMaster(flowCardVouNo, flowCard);
			// 新增流向單明細資訊
			flowCardDao.addFlowCardDetail(flowCardVouNo,
					flowCard.getFlowCardDetailList());

			// 提交事務
			ConnectionManager.commitTransaction(conn);
		} catch (DaoException e) {
			// 回滾事務
			ConnectionManager.rollbackTransaction(conn);
			throw new ApplicationException("新增流向單失敗!");
		} finally {
			// 關閉Connection並從ThreadLocal中清除
			ConnectionManager.closeConnection();
		}
	}
同類的方法還有很多,增刪改查等,並且都需要就在呼叫方法前開始事務,在方法正確返回時提交事務等,這時,我們可以使用動態代理對程式碼進行抽象和隔離。提取一個事務類,如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;

/**
 * 採用動態代理封裝事務
 * @author Administrator
 *
 */
public class TransactionHandler implements InvocationHandler {

	private Object targetObject;
	
	public Object newProxyInstance(Object targetObject) {
		this.targetObject = targetObject;
		return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
							   targetObject.getClass().getInterfaces(), this);
	}

	
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Connection conn = null;
		Object ret = null;
		try {
			//從ThreadLocal中取得Connection
			conn = ConnectionManager.getConnection();
			if (method.getName().startsWith("add") ||
				method.getName().startsWith("del") ||
				method.getName().startsWith("modify")) {
				//手動控制事務提交
				ConnectionManager.beginTransaction(conn);
			}	
			//呼叫目標物件的業務邏輯方法
			ret = method.invoke(targetObject, args);
			if (!conn.getAutoCommit()) {
				//提交事務
				ConnectionManager.commitTransaction(conn);
			}
		}catch(ApplicationException e) {
			//回滾事務
			ConnectionManager.rollbackTransaction(conn);
			throw e;
		}catch(Exception e) {
			e.printStackTrace();
			if (e instanceof InvocationTargetException) {
				InvocationTargetException ete = (InvocationTargetException)e;
				throw ete.getTargetException();
			}
			//回滾事務
			ConnectionManager.rollbackTransaction(conn);
			throw new ApplicationException("操作失敗!");
		}finally {
			ConnectionManager.closeConnection();
		}
		return ret;
	}

}
同時原新增方法經過修改,也可以只專注於自己的業務啦,如下:

public void addFlowCard(FlowCard flowCard) throws ApplicationException {
		try {
			// 生成流向單單號
			String flowCardVouNo = flowCardDao.generateVouNo();
			// 新增流向單主資訊
			flowCardDao.addFlowCardMaster(flowCardVouNo, flowCard);
			// 新增流向單明細資訊
			flowCardDao.addFlowCardDetail(flowCardVouNo,
					flowCard.getFlowCardDetailList());
		} catch (DaoException e) {
			throw new ApplicationException("新增流向單失敗!");
		}
	}

【小結】

        1.代理物件可以在客戶端和目標物件之間起到中介的作用,這樣起到了中介的作用和保護了目標物件的作用。

        2.代理類還可以附加一些內務處理,例如對委託類進行預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。具體的例如日誌、延遲載入、監視狀態等。