代理模式簡介
- 代理模式:爲一個對象提供一個替身,以控制對這個對象的訪問。即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能。
- 被代理的對象可以是遠程對象、創建開銷大的對象或需要安全控制的對象
- 代理模式有不同的形式, 主要有兩種: 靜態代理、動態代理 (JDK代理、接口代理、Cglib代理)
- Cglib代理:可以在內存動態的創建對象,而不需要實現接口
靜態代理
- 靜態代理在使用時,需要定義接口或者父類,被代理對象(即目標對象)與代理對象一起實現相同的接口或者是繼承相同父類
應用實例:
- 定義一個接口:ITeacherDao
- 目標對象TeacherDAO實現接口ITeacherDAO
- 使用靜態代理方式,就需要在代理對象TeacherDAOProxy中也實現ITeacherDAO
- 調用的時候通過調用代理對象的方法來調用目標對象
- 特別提醒:代理對象與目標對象要實現相同的接口,然後通過調用相同的方法來調用目標對象的方法。
// 接口
public interface ITeacherDao {
void teach(); // 授課的方法
}
----------------------------------------------------------------------------------------
// 實現類
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println(" 老師授課中 。。。。。");
}
}
----------------------------------------------------------------------------------------
//代理對象,靜態代理
public class TeacherDaoProxy implements ITeacherDao{
private ITeacherDao target; // 目標對象,通過接口來聚合
//構造器
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("開始代理 完成某些操作。。。。。 ");//方法
target.teach();
System.out.println("提交。。。。。");//方法
}
}
----------------------------------------------------------------------------------------
// 測試類
public class Client {
public static void main(String[] args) {
//創建目標對象(被代理對象)
TeacherDao teacherDao = new TeacherDao();
//創建代理對象, 同時將被代理對象傳遞給代理對象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
//通過代理對象,調用到被代理對象的方法
//即:執行的是代理對象的方法,代理對象再去調用目標對象的方法
teacherDaoProxy.teach();
}
}
- 優點:在不修改目標對象的功能前提下, 能通過代理對象對目標功能擴展
- 缺點:因爲代理對象需要與目標對象實現一樣的接口,所以會有很多代理類,一旦接口增加方法,目標對象與代理對象都要維護
動態代理
- 代理對象,不需要實現接口,但是目標對象要實現接口,否則不能用動態代理
- 代理對象的生成,是利用JDK的API,動態的在內存中構建代理對象
- 動態代理也叫做:JDK代理、接口代理
對上述例子進行改造
// 接口
public interface ITeacherDao {
void teach(); // 授課方法
void sayHello(String name);
}
-------------------------------------------------------------------------------
// 實現類
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println(" 老師授課中.... ");
}
@Override
public void sayHello(String name) {
System.out.println("hello " + name);
}
}
-------------------------------------------------------------------------------
// 代理工廠
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
//維護一個目標對象 , Object
private Object target;
//構造器 對target進行初始化
public ProxyFactory(Object target) {
this.target = target;
}
//給目標對象 生成一個代理對象
public Object getProxyInstance() {
//說明
/*
* public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
//1. ClassLoader loader : 指定當前目標對象使用的類加載器, 獲取加載器的方法固定
//2. Class<?>[] interfaces: 目標對象實現的接口類型,使用泛型方法確認類型
//3. InvocationHandler h : 事情處理,執行目標對象的方法時,會觸發invoke事情處理器方法, 會把當前執行的目標對象方法作爲參數傳入
*/
// 這裏可以讓這個類實現InvocationHandler接口,然後這裏直接傳入this就可以了,會更加美觀
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理開始~~");
//反射機制調用目標對象的方法
Object returnVal = method.invoke(target, args);
System.out.println("JDK代理提交");
return returnVal;
}
});
}
}
-------------------------------------------------------------------------------
// 測試類
public class Client {
public static void main(String[] args) {
//創建目標對象
ITeacherDao target = new TeacherDao();
//給目標對象,創建代理對象, 可以轉成 ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
// proxyInstance=class com.sun.proxy.$Proxy0 內存中動態生成了代理對象
System.out.println("proxyInstance=" + proxyInstance.getClass());
//通過代理對象,調用目標對象的方法
proxyInstance.sayHello(" tom ");
}
}
Cglib代理
- 靜態代理和JDK代理模式都要求目標對象是實現一個接口,但是有時候目標對象只是一個單獨的對象,並沒有實現任何的接口,這個時候可使用目標對象子類來實現代理,這就是Cglib代理
- Cglib代理也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能擴展,有些書也將Cglib代理歸屬到動態代理。
- Cglib是一個強大的高性能的代碼生成包,它可以在運行期擴展 Java 類與實現 Java 接口,它廣泛的被許多AOP的框架使用,例如Spring AOP,實現方法攔截
- 在AOP編程中如何選擇代理模式:
- 目標對象需要實現接口,用JDK代理
- 目標對象不需要實現接口,用Cglib代理
- Cglib包的底層是通過使用 字節碼處理框架 ASM 來轉換字節碼並生成新的類
- 需要導入一些包,點擊下載
將上述的案例進行改造
// 目標對象
public class TeacherDao {
public String teach() {
System.out.println(" 老師授課中 , 我是cglib代理,不需要實現接口 ");
return "hello";
}
}
-------------------------------------------------------------------------------------
// 代理工廠
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class ProxyFactory implements MethodInterceptor {
//維護一個目標對象
private Object target;
//構造器,傳入一個被代理的對象
public ProxyFactory(Object target) {
this.target = target;
}
//返回一個代理對象: 是 target 對象的代理對象
public Object getProxyInstance() {
//1. 創建一個工具類
Enhancer enhancer = new Enhancer();
//2. 設置父類
enhancer.setSuperclass(target.getClass());
//3. 設置回調函數
enhancer.setCallback(this);
//4. 創建子類對象,即代理對象
return enhancer.create();
}
// 重寫 intercept 方法,會調用目標對象的方法
// Object表示要進行增強的對象
// Method表示攔截的方法
// Object[]數組表示參數列表,基本數據類型需要傳入其包裝類型
// 如 int-->Integer、long-Long、double-->Double
// MethodProxy表示對方法的代理,invokeSuper方法表示對被代理對象方法的調用
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
System.out.println("Cglib代理模式 ~~ 開始");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib代理模式 ~~ 提交");
return returnVal;
}
}
-------------------------------------------------------------------------------------
// 測試類
public class Client {
public static void main(String[] args) {
//創建目標對象
TeacherDao target = new TeacherDao();
//獲取到代理對象,並且將目標對象傳遞給代理對象
TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();
//執行代理對象的方法,觸發intercept 方法,從而實現 對目標對象的調用
String res = proxyInstance.teach();
System.out.println("res=" + res);
}
}
注意!!!!!!!
- 在內存中動態構建子類,注意代理的類不能爲final,否則報錯
java.lang.IllegalArgumentException:
- 目標對象的方法如果爲
final/static
,那麼就不會被攔截,即不會執行目標對象額外的業務方法
結束語
幾種常見的代理模式介紹
- 防火牆代理
內網通過代理穿透防火牆,實現對公網的訪問。 - 緩存代理
比如:當請求圖片文件等資源時,先到緩存代理取,如果取到資源則ok,如果取不到資源,再到公網或者數據庫取,然後緩存。 - 遠程代理
遠程對象的本地代表,通過它可以把遠程對象當本地對象來調用。遠程代理通過網絡和真正的遠程對象溝通信息。 - 同步代理:主要使用在多線程編程中,完成多線程間同步工作