1. 程式人生 > >靜態代理、動態代理、Cglib代理全面梳理

靜態代理、動態代理、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代理,也叫做子類代理。在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件。

 

  1. JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面。如果想代理沒有實現介面的類,就可以使用CGLIB實現。
  2.   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();
	}
}


//輸出結果:

            開始事務...
            ---- 已經儲存資料 ----
            提交事務...