JAVA的動態代理
代理模式
代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的接口,代理類主要負責為委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類別與委託類別之間通常會存在關聯關係,一個代理類別的物件與一個委託類別的物件關聯,代理類別的物件本身並不真正實現服務,而是透過呼叫委託類別的物件的相關方法,來提供特定的服務。
依照代理程式的創建時期,代理類別可以分為兩種。
靜態代理:由程式設計師創建或特定工具自動產生原始程式碼,再對其編譯。在程式運行前,代理類別的.class檔案就已經存在了。
動態代理:在程式運作時,運用反射機制動態創建而成。
首先看一下靜態代理:
1、Count.java
複製代碼代碼如下:
package net.battier.dao;
/**
* 定義一個帳戶介面
*
* @author Administrator
*
*/
public interface Count {
// 查看帳戶方法
public void queryCount();
// 修改帳戶方法
public void updateCount();
}
2、CountImpl.java
複製代碼代碼如下:
package net.battier.dao.impl;
import net.battier.dao.Count;
/**
* 委託類別(包含業務邏輯)
*
* @author Administrator
*
*/
public class CountImpl implements Count {
@Override
public void queryCount() {
System.out.println("查看帳戶方法...");
}
@Override
public void updateCount() {
System.out.println("修改帳戶方法...");
}
}
CountProxy.java
package net.battier.dao.impl;
import net.battier.dao.Count;
/**
* 這是一個代理類別(增強CountImpl實作類別)
*
* @author Administrator
*
*/
public class CountProxy implements Count {
private CountImpl countImpl;
/**
* 覆蓋預設建構器
*
* @param countImpl
*/
public CountProxy(CountImpl countImpl) {
this.countImpl = countImpl;
}
@Override
public void queryCount() {
System.out.println("交易處理之前");
// 呼叫委託類別的方法;
countImpl.queryCount();
System.out.println("交易處理之後");
}
@Override
public void updateCount() {
System.out.println("交易處理之前");
// 呼叫委託類別的方法;
countImpl.updateCount();
System.out.println("交易處理之後");
}
}
3、TestCount.java
複製代碼代碼如下:
package net.battier.test;
import net.battier.dao.impl.CountImpl;
import net.battier.dao.impl.CountProxy;
/**
*測試Count類
*
* @author Administrator
*
*/
public class TestCount {
public static void main(String[] args) {
CountImpl countImpl = new CountImpl();
CountProxy countProxy = new CountProxy(countImpl);
countProxy.updateCount();
countProxy.queryCount();
}
}
觀察程式碼可以發現每一個代理類別只能為一個介面服務,這樣一來程式開發中必然會產生過多的代理,而且,所有的代理操作除了呼叫的方法不一樣之外,其他的操作都一樣,則此時肯定是重複程式碼。解決這問題最好的做法是可以透過一個代理類別完成全部的代理功能,那麼此時就必須使用動態代理來完成。
再來看一下動態代理:
JDK動態代理程式中包含一個類別和一個介面:
InvocationHandler介面:
public interface InvocationHandler {
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
}
參數說明:
Object proxy:指被代理的物件。
Method method:要呼叫的方法
Object[] args:方法呼叫時所需要的參數
可以將InvocationHandler介面的子類別想像成一個代理的最終操作類,替換掉ProxySubject。
Proxy類別:
Proxy類別是專門完成代理的操作類,可以透過此類為一個或多個介面動態地產生實作類,此類提供瞭如下的操作方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
參數說明:
ClassLoader loader:類別載入器
Class<?>[] interfaces:得到全部的接口
InvocationHandler h:得到InvocationHandler介面的子類別實例
Ps:類別載入器
在Proxy類別中的newProxyInstance()方法需要一個ClassLoader類別的實例,ClassLoader實際上對應的是類別載入器,在Java中主要有一下三種類載入器;
Booststrap ClassLoader:此載入器採用C++編寫,一般開發中是看不到的;
Extendsion ClassLoader:用來進行擴充類別的加載,一般對應的是jre/lib/ext目錄中的類別;
AppClassLoader:(預設)載入classpath指定的類,是最常使用的是一種載入器。
動態代理
與靜態代理類對照的是動態代理類,動態代理類的字節碼在程式運行時由Java反射機制動態生成,無需程式設計師手動編寫它的源代碼。動態代理類別不僅簡化了程式設計工作,而且提高了軟體系統的可擴展性,因為Java 反射機制可以產生任意類型的動態代理類別。 java.lang.reflect 套件中的Proxy類別和InvocationHandler 介面提供了產生動態代理類別的能力。
動態代理範例:
1、BookFacade.java
複製代碼代碼如下:
package net.battier.dao;
public interface BookFacade {
public void addBook();
}
2、BookFacadeImpl.java
複製代碼代碼如下:
package net.battier.dao.impl;
import net.battier.dao.BookFacade;
public class BookFacadeImpl implements BookFacade {
@Override
public void addBook() {
System.out.println("增加圖書方法。。。");
}
}
BookFacadeProxy.java
package net.battier.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK動態代理程式類
*
* @author student
*
*/
public class BookFacadeProxy implements InvocationHandler {
private Object target;
/**
* 綁定委託物件並傳回一個代理類
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
//取得代理對象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this); //要綁定介面(這是一個缺陷,cglib彌補了這一缺陷)
}
@Override
/**
* 呼叫方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=null;
System.out.println("事物開始");
//執行方法
result=method.invoke(target, args);
System.out.println("事物結束");
return result;
}
}
3、TestProxy.java
複製代碼代碼如下:
package net.battier.test;
import net.battier.dao.BookFacade;
import net.battier.dao.impl.BookFacadeImpl;
import net.battier.proxy.BookFacadeProxy;
public class TestProxy {
public static void main(String[] args) {
BookFacadeProxy proxy = new BookFacadeProxy();
BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());
bookProxy.addBook();
}
}
但是,JDK的動態代理依靠接口實現,如果有些類並沒有實現接口,則不能使用JDK代理,這就要使用cglib動態代理了。
Cglib動態代理
JDK的動態代理機制只能代理實現了介面的類,而不能實現介面的類就不能實現JDK的動態代理,cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆寫其中方法實作增強,但因為採用的是繼承,所以不能對final修飾的類別進行代理。
範例
1、BookFacadeCglib.java
複製代碼代碼如下:
package net.battier.dao;
public interface BookFacade {
public void addBook();
}
2、BookCadeImpl1.java
複製代碼代碼如下:
package net.battier.dao.impl;
/**
* 這個是沒有實作介面的實作類
*
* @author student
*
*/
public class BookFacadeImpl1 {
public void addBook() {
System.out.println("增加圖書的普通方法...");
}
}
3、BookFacadeProxy.java
複製代碼代碼如下:
package net.battier.proxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 使用cglib動態代理
*
* @author student
*
*/
public class BookFacadeCglib implements MethodInterceptor {
private Object target;
/**
* 建立代理對象
*
* @param target
* @return
*/
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回呼方法
enhancer.setCallback(this);
// 建立代理對象
return enhancer.create();
}
@Override
// 回呼方法
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("事物開始");
proxy.invokeSuper(obj, args);
System.out.println("事物結束");
return null;
}
}
4、TestCglib.java
複製代碼代碼如下:
package net.battier.test;
import net.battier.dao.impl.BookFacadeImpl1;
import net.battier.proxy.BookFacadeCglib;
public class TestCglib {
public static void main(String[] args) {
BookFacadeCglib cglib=new BookFacadeCglib();
BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1());
bookCglib.addBook();
}
}