靜態代理、動態代理、Cglib代理全面梳理
1.1 代理(Proxy)是一種設計模式, 提供了對目標物件另外的訪問方式;即通過代理訪問目標物件。 這樣好處: 可以在目標物件實現的基礎上,增強額外的功能操作。(擴充套件目標物件的功能)。
舉例:明星(鄧紫棋)<-----經紀人<-------使用者
目標 (代理)
代理模式的關鍵點: 代理物件與目標物件。
1.2 靜態代理
靜態代理,
1) 代理物件,要實現與目標物件一樣的介面;
2) 舉例:
儲存使用者(模擬)
Dao , 直接儲存
DaoProxy, 給儲存方法新增事務處理
//介面類
public interface UserDao {
//儲存方法
void save();
}
/**
* 目標物件
*/
public class UserDaoImpl implements UserDao{
@Override
public void save() {
System.out.println("----------已儲存資料----------");
}
}
/**
* 代理物件(靜態代理)
* 代理物件要實現與目標物件一樣的方法
*/
public class UserDaoImplProxy implements UserDao{
//接受儲存目標物件
public UserDao target;
public UserDaoImplProxy(UserDao target){
this.target = target;
}
@Override
public void save() { //開閉原則
System.out.println("開啟事務");
target.save();
System.out.println("結束事務");
}
}
//測試類
public class App {
public static void main(String[] args) {
//目標物件
UserDao target = new UserDaoImpl();
//代理物件
UserDao proxy = new UserDaoImplProxy(target);
proxy.save(); //執行代理方法
}
}
輸出結果:
開啟事務
----------已儲存資料----------
結束事務
總結靜態代理:
1)可以做到在不修改目標物件的功能前提下,對目標物件功能擴充套件。
2)缺點:
--》 因為代理物件,需要與目標物件實現一樣的介面。所以會有很多代理類,類太多。
--》 一旦介面增加方法,目標物件與代理物件都要維護。
解決:
代理工廠? 可以使用動態代理。
1.3 動態代理
動態代理,
1)代理物件,不需要實現介面;
2)代理物件的生成,是利用JDKAPI, 動態的在記憶體中構建代理物件(需要我們指定建立 代理物件/目標物件 實現的介面的型別;);
3) 動態代理, JDK代理, 介面代理;
JDK中生成代理物件的API:
|-- Proxy
static Object newProxyInstance(
ClassLoader loader, 指定當前目標物件使用類載入器
Class<?>[] interfaces, 目標物件實現的介面的型別
InvocationHandler h 事件處理器
)
//介面類 public interface UserDao { void save(); } /** * 目標物件 */ public class UserDaoImpl implements UserDao{ @Override public void save() { System.out.println("----------已儲存資料----------"); } } //動態代理工廠 /** * 給所有的Dao建立代理物件【動態代理】 * 代理物件 不需要實現介面 指定目標物件使用的介面型別即可 */ public class ProxyFactory { //維護一個目標物件 private Object target; public ProxyFactory(Object target){ this.target = target; } //給目標物件生成代理物件 public Object getProxyInstance(){ 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("開啟事務"); //執行目標物件方法 Object returnValue = method.invoke(target, args); System.out.println("提交事務"); return returnValue; } }); } } //測試類 public class App { public static void main(String[] args) { //目標物件 UserDao target = new UserDaoImpl(); //原始型別【com.tao.dynamicProxy.UserDaoImpl】 System.out.println(target); //靜態代理物件 //UserDao proxy = new UserDaoImplProxy(target); //proxy.save(); //執行代理方法 //動態代理工廠 給目標物件 建立代理物件 UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance(); //代理型別【class com.sun.proxy.$Proxy0】 記憶體中動態生成的代理物件 System.out.println(proxy.getClass()); proxy.save(); } } 輸出結果: [email protected] class com.sun.proxy.$Proxy0 開啟事務 ----------已儲存資料---------- 提交事務
動態代理總結:
代理物件不需要實現介面,但是目標物件一定要實現介面;否則不能用動態代理!
(class $Proxy0 implements IuserDao)
思考:
有一個目標物件,想要功能擴充套件,但目標物件沒有實現介面,怎樣功能擴充套件?
Class UserDao{}
// 子類的方式
Class subclass extends UserDao{}
以子類的方式實現(cglib代理)
1.4 Cglib代理
Cglib代理,也叫做子類代理。在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件。
- JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面。如果想代理沒有實現介面的類,就可以使用CGLIB實現。
- CGLIB是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件Java類與實現Java介面。它廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop,為他們提供方法的interception(攔截)。
CGLIB包的底層是通過使用一個小而快的位元組碼處理框架ASM,來轉換位元組碼並生成新的類。不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉。
Cglib子類代理:
1) 需要引入cglib – jar檔案, 但是spring的核心包中已經包括了cglib功能,所以直接引入spring-core-3.2.5.jar即可。
2)引入功能包後,就可以在記憶體中動態構建子類
3)代理的類不能為final, 否則報錯。
4) 目標物件的方法如果為final/static, 那麼就不會被攔截,即不會執行目標物件額外的業務方法。
在Spring的AOP程式設計中,
如果加入容器的目標物件有實現介面,用JDK代理;
如果目標物件沒有實現介面,用Cglib代理;
/** * 目標物件 */ public class UserDao { public void save(){ System.out.println("---- 已經儲存資料 ----"); } } /** * Cglib子類代理工廠 * 對userDao 在記憶體中動態構建一個子類物件 * */ public class CglibProxyFactory implements MethodInterceptor{ //維護一個目標物件 private Object target; public CglibProxyFactory(Object target){ this.target = target; } //給目標物件建立代理物件 public Object getProxyInstance(){ //1.工具類 Enhancer en = new Enhancer(); //2.設定父類 en.setSuperclass(target.getClass()); //3.設定回撥函式 en.setCallback(this); //4.建立子類(代理物件) return en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("開始事務..."); //執行目標物件的方法 Object returnValue = method.invoke(target, args); System.out.println("提交事務..."); return returnValue; } } //測試類 public class App { public static void main(String[] args) { //目標物件 UserDao target = new UserDao(); //代理物件 UserDao proxy = (UserDao) new CglibProxyFactory(target).getProxyInstance(); //執行代理物件的方法 proxy.save(); } } //輸出結果: 開始事務... ---- 已經儲存資料 ---- 提交事務...