JAVA的動態代理
代理模式
代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務。
按照代理的創建時期,代理類可以分爲兩種。
靜態代理:由程序員創建或特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。
動態代理:在程序運行時,運用反射機制動態創建而成。
先來看看動態代理的內部源碼:
java動態代理
要了解 Java 動態代理的機制,首先需要了解以下相關的類或接口:
· java.lang.reflect.Proxy:這是 Java 動態代理機制的主類,它提供了一組靜態方法來爲一組接口動態地生成代理類及其對象。
清單 1. Proxy 的靜態方法
java.lang.reflect.InvocationHandler:這是調用處理器接口,它自定義了一個 invoke 方法,用於集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委託類的代理訪問。
清單 2. InvocationHandler 的核心方法
// 該方法負責集中處理動態代理類上的所有方法調用。第一個參數既是代理類實例,第二個參數是被調用的方法對象
// 第三個方法是調用參數。調用處理器根據這三個參數進行預處理或分派到委託類實例上發射執行
Object invoke(Object proxy, Method method, Object[] args)
每次生成動態代理類對象時都需要指定一個實現了該接口的調用處理器對象(參見 Proxy 靜態方法 4 的第三個參數)。
· java.lang.ClassLoader:這是類裝載器類,負責將類的字節碼裝載到 Java 虛擬機(JVM)中併爲其定義類對象,然後該類才能被使用。Proxy 靜態方法生成動態代理類同樣需要通過類裝載器來進行裝載才能使用,它與普通類的唯一區別就是其字節碼是由 JVM 在運行時動態生成的而非預存在於任何個 .class 文件中。
每次生成動態代理類對象時都需要指定一個類裝載器對象(參見 Proxy 靜態方法 4 的第一個參數)
首先讓我們來了解一下如何使用 Java 動態代理。具體有如下四步驟:
1. 通過實現 InvocationHandler 接口創建自己的調用處理器;
2. 通過爲 Proxy 類指定 ClassLoader 對象和一組 interface 來創建動態代理類;
3. 通過反射機制獲得動態代理類的構造函數,其唯一參數類型是調用處理器接口類型;
4. 通過構造函數創建動態代理類實例,構造時調用處理器對象作爲參數被傳入。
// InvocationHandlerImpl 實現了 InvocationHandler 接口,並能實現方法調用從代理類到委託類的分派轉發
// 其內部通常包含指向委託類實例的引用,用於真正執行分派轉發過來的方法調用
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過 Proxy 爲包括 Interface 接口在內的一組接口動態創建代理類的類對象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
// 通過反射從生成的類對象獲得構造函數對象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
// 通過構造函數對象創建動態代理類實例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
實際使用過程更加簡單,因爲 Proxy 的靜態方法 newProxyInstance 已經爲我們封裝了步驟 2 到步驟 4 的過程,所以簡化後的過程如
清單 4. 簡化的動態代理對象創建過程
// InvocationHandlerImpl 實現了 InvocationHandler 接口,並能實現方法調用從代理類到委託類的分派轉發
InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過 Proxy 直接創建動態代理類實例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
new Class[] { Interface.class },
handler );
下面我們來看一個簡單實現動態代理的例子:
1.代理類和真實類接口:
public interface Subject
{
public void request();
}
2.真實類:
public class RealSubject implements Subject
{
public void request()
{
System.out.println("From real subject!");
}}
3.具體代理類:
注:該代理類的內部屬性是Object類型,實際使用的時候通過該類的構造方法傳遞進來一個對象。 此外,該類還實現了invoke方法,該方法中的method.invoke其實就是調用被代理對象的將要 執行的方法,方法參數是sub,表示該方法從屬於sub,通過動態代理類,我們可以在執行真實對象的方法前後加入自己的一些額外方法。
4.客戶端調用示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client
{
public static void main(String[] args)
{
RealSubject realSubject = new RealSubject();
InvocationHandler handler = new DynamicSubject(realSubject);
Class<?> classType = handler.getClass();
// 下面的代碼一次性生成代理
Subject subject = (Subject) Proxy.newProxyInstance(classType
.getClassLoader(), realSubject.getClass().getInterfaces(),
handler);
subject.request();
System.out.println(subject.getClass());
}
}
接下來讓我們來了解一下 Java 動態代理機制 Proxy 的構造方法:
清單 6. Proxy 構造方法
// 由於 Proxy 內部從不直接調用構造函數,所以 private 類型意味着禁止任何調用
private Proxy() {}
// 由於 Proxy 內部從不直接調用構造函數,所以 protected 意味着只有子類可以調用
protected Proxy(InvocationHandler h) {this.h = h;}
接着,可以快速瀏覽一下 newProxyInstance 方法,因爲其相當簡單:
清單 7. Proxy 靜態方法 newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
// 檢查 h 不爲空,否則拋異常
if (h == null) {
throw new NullPointerException();
}
// 獲得與制定類裝載器和一組接口相關的代理類類型對象
Class cl = getProxyClass(loader, interfaces);
// 通過反射獲取構造函數對象並生成代理類實例
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
}
-----------------------------------
看一下靜態代理:
1、Count.java
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();
}
}