JAVA動態代理實現簡單的AOP框架

- 背景

作業:

  1. 使用Java動態代理實現一個簡單的AOP框架

  2. 定義兩個註解 @Transaction ,@Log

  3. 定義一個接口Aspect,它有兩個接口方法:before(); after();

  4. 實現連個Apect實現類:TransactionAspect,LogAspect;
    TransactionAspect:before():輸出事務開始,after():輸出事務結束
    LogAspect: before():輸出調用前, after():輸出調用後

  5. 實現測試類,這個類裏有四個方法:

    – 第一個方法:標有@Transaction
    – 第二個方法:標有@Log
    – 第三個方法:標有@Transaction 和@Log
    – 第四 個方法:沒有任何注個解

  6. 寫main方法,使用aop框架執行測試類方法,要求:
    有@Transaction 方法執行的時候, TransactionAspect的兩個方法被執行;
    有@Log方法執 行的時候, LogAspect的兩個方法被執行。


- 代理的概念和作用

  1. 爲其他對象提供一種代理以控制對這個對象的訪問。 在某些情況下,一個客戶不想或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。
    現實中的例子:
    歌星或者明星都有一個自己的經紀人,這個經紀人就是他們的代理人,當我們需要找明星表演時,不能直接找到該明星,只能是找明星的代理人

總結: 在這裏代理對象存在的價值:主要用於攔截對真實業務對象的訪問。

  1. 爲已存在的多個具有相同接口的目標類的各個方法增加一些系統功能,例如,異常處理、日誌、計算方法的運行時間、事務管理、等等。
    例如:
    現實生活中,你有一個蛋糕機(接口),這個蛋糕機可以製造出草莓蛋糕、奶油蛋糕等多種蛋糕(實現了同一接口的類)。突然有一天,一位客人想在草莓蛋糕中加入其他的水果。但是你的蛋糕機只能生產出草莓蛋糕,現實中也不能因爲一個需求就把蛋糕機(接口)修改了,這個時候就可以創建一個實現了蛋糕機的代理類,在代理類中添加相應的功能。

總結:
代理類與委託類有同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務。簡單的說就是,我們在訪問實際對象時,是通過代理對象來訪問的,代理模式就是在訪問實際對象時引入一定程度的間接性,因爲這種間接性,可以附加多種用途。


- 靜態代理和動態代理

根據加載被代理類的時機不同,將代理分爲靜態代理和動態代理。如果我們在代碼編譯時就確定了被代理的類是哪一個,那麼就可以直接使用靜態代理;如果不能確定,那麼可以使用類的動態加載機制,在代碼運行期間加載被代理的類這就是動態代理,比如RPC框架和Spring AOP機制。


- 靜態代理的實現

靜態代理: 由程序員創建或特定工具自動生成源代碼,也就是在編譯時就已經將接口,被代理類,代理類等確定下來。在程序運行之前,代理類的.class文件就已經生成。

這裏實現一個用戶管理類UserManagerImp,實現接口UserManager的兩個方法,添加用戶addUser()和刪除用戶deleteUser()。也可通過第三方代理類UserManagerImpProxy(實現了同樣的UserManager接口)來代理執行添加用戶和刪除用戶的操作。

首先創建一個UserManager接口。這個接口就是被代理類(UserManagerImp)和代理類(UserManagerImpProxy)的公共接口,它們都有添加用戶和刪除用戶的行爲。

package com.glodon.hongq.day01;

public interface UserManager {
    void addUser();
    void delUser();
    String findUser();
}

UserManagerImp實現UserManager接口:

package com.glodon.hongq.day01;

public class UserManagerImp implements UserManager{
    @Override
    public void addUser() {
        System.out.println("UserManagerImp.addUser");
    }

    @Override
    public void delUser() {
        System.out.println("UserManagerImp.delUser");
    }

    @Override
    public String findUser() {
        return "UserManagerImp.UserName";
    }
}

UserManagerImpProxy類也實現了UserManager接口,同時擁有一個UserManger引用,通過這個能夠代理實現UserManager接口的任何類

package com.glodon.hongq.day01;

public class UserManagerImpProxy implements UserManager {
    private UserManager userManager;

    public UserManagerImpProxy(UserManager userManager){
        this.userManager = userManager;
    }

    @Override
    public void addUser() {
        System.out.println("我是代理商UserManagerImpProxy");
        System.out.println("start->addUser");
        userManager.addUser();
        System.out.println("success->addUser");
    }

    @Override
    public void delUser() {
        userManager.delUser();
    }

    @Override
    public String findUser() {
        return userManager.findUser();
    }
}

測試類

package com.glodon.hongq.day01;

public class ClientProxy {
    public static void main(String[] args){
        UserManager u = new UserManagerImpProxy(new UserManagerImp());

        // UserManagerImpProxy對象代理了UserManagerImp執行了addUser()方法
        u.addUser();
    }
}

運行結果:

在這裏插入圖片描述
說明:
代理模式最主要的就是有一個公共接口(UserManager),一個具體的類(UserManagerImp),一個代理類(UserManagerImpProxy),代理類持有具體類的實例,代爲執行具體類實例方法。上面說到,代理模式就是在訪問實際對象時引入一定程度的間接性,因爲這種間接性,可以附加多種用途。這裏的間接性就是指不直接調用實際對象的方法,那麼我們在代理過程中就可以加上一些其他用途。就這個例子來說,在執行addUser()方法的前後都添加了相應的說明,如下圖所示:

在這裏插入圖片描述

可以看到,只需要在代理類中運行addUser()之前,執行其他操作就可以了。這種操作,也是使用代理模式的一個很大的優點。最直白的就是在Spring中的面向切面編程(AOP),我們能在一個切點之前執行一些操作,在一個切點之後執行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。

以下結構圖是一個典型的靜態代理模式:
在這裏插入圖片描述
其中:Subject角色負責定義RealSubject和Proxy角色應該實現的接口;RealSubject角色用來真正完成業務服務功能;Proxy角色負責將自身的Request請求,調用realsubject 對應的request功能來實現業務功能,自己不真正做業務。

當在代碼階段規定這種代理關係,Proxy類通過編譯器編譯成class文件,當系統運行時,此class已經存在了。這種靜態的代理模式固然在訪問無法訪問的資源,增強現有的接口業務功能方面有很大的優點,但是大量使用這種靜態代理,會使我們系統內的類的規模增大,並且不易維護;並且由於Proxy和RealSubject的功能 本質上是相同的,Proxy只是起到了中介的作用,這種代理在系統中的存在,導致系統結構比較臃腫和鬆散。

爲了解決這個問題,就有了動態地創建Proxy的想法:在運行狀態中,需要代理的地方,根據Subject 和RealSubject,動態地創建一個Proxy,用完之後,就會銷燬,這樣就可以避免了Proxy 角色的class在系統中冗雜的問題了。


- 動態代理

動態代理:代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理。

動態代理模式的結構跟上面的靜態代理模式稍微有所不同,多引入了一個InvocationHandler角色
在這裏插入圖片描述
有上圖可以看出,代理類處理的邏輯很簡單:在調用某個方法前及方法後做一些額外的業務。換一種思路就是:在觸發(invoke)真實角色的方法之前或者之後做一些額外的業務。那麼,爲了構造出具有通用性和簡單性的代理類,可以將所有的觸發真實角色動作交給一個觸發的管理器,讓這個管理器統一地管理觸發。這種管理器就是Invocation Handler。

動態代理工作的基本模式就是將自己的方法功能的實現交給 InvocationHandler角色,外界對Proxy角色中的每一個方法的調用,Proxy角色都會交給InvocationHandler來處理,而InvocationHandler則調用具體對象角色的方法。如下圖所示:
在這裏插入圖片描述
JDK的動態代理創建機制----通過接口
1. 獲取 RealSubject上的所有接口列表;
2. 確定要生成的代理類的類名,默認爲:com.sum.$ProxyXXXX ;
3. 根據需要實現的接口信息,在代碼中動態創建該Proxy類的字節碼;
4 . 將對應的字節碼轉換爲對應的class 對象;
4. 創建InvocationHandler 實例handler,用來處理Proxy所有方法調用;
5. Proxy 的class對象 以創建的handler對象爲參數,實例化一個proxy對象

在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過這個類和這個接口可以生成JDK動態代理類和動態代理對象。

newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序

在調用代理對象中的每一個方法時,在代碼內部,都是直接調用了InvocationHandler 的invoke方法,而invoke方法根據代理類傳遞給自己的method參數來區分是什麼方法。

invoke(Object proxy,Method method,Object[] args)
在代理實例上處理方法調用並返回結果

動態代理的實例

根據本文“背景”一欄的作業,運用Java動態代理實現簡單的AOP框架。

  1. 定義兩個註解 @Transaction ,@Log
package com.glodon.hongq.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transaction {
	String value() default "Transaction";
}
package com.glodon.hongq.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
	String value() default "Log";
}

  1. 定義一個接口Aspect,它有兩個接口方法:before(); after();
package com.glodon.hongq.aop;

public interface Aspect {
	void before();
	void after();
}
  1. 實現連個Apect實現類:TransactionAspect,LogAspect;

TransactionAspect:before():輸出事務開始,after():輸出事務結束

package com.glodon.hongq.aop;

public class TransactionAspect implements Aspect {

	@Override
	public void before() {
		// TODO Auto-generated method stub
		System.out.println("事物開始");
	}

	@Override
	public void after() {
		// TODO Auto-generated method stub
		System.out.println("事物結束");
	}

}

LogAspect: before():輸出調用前, after():輸出調用後

package com.glodon.hongq.aop;

public class LogAspect implements Aspect {

	@Override
	public void before() {
		// TODO Auto-generated method stub
		System.out.println("調用前");
	}

	@Override
	public void after() {
		// TODO Auto-generated method stub
		System.out.println("調用後");
	}

}
  1. 實現測試類,這個類裏有四個方法:

    – 第一個方法:標有@Transaction
    – 第二個方法:標有@Log
    – 第三個方法:標有@Transaction 和@Log
    – 第四個方法:沒有任何注個解

package com.glodon.hongq.aop;

public interface Test {
	
	@Transaction()
	public void method1() ;
	
	@Log
	public void method2() ;
	
	@Transaction()
	@Log
	public void method3() ;
	
	public void method4() ;

}
  1. 寫main方法,使用aop框架執行測試類方法,要求:
    有@Transaction 方法執行的時候, TransactionAspect的兩個方法被執行;
    有@Log方法執 行的時候, LogAspect的兩個方法被執行。
package com.glodon.hongq.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;


public class AopTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Test t1 = new Test() {
			@Transaction()
			public void method1() {
				System.out.println("method1");
			}
			
			@Log
			public void method2() {
				System.out.println("method2");
			}
			
			@Transaction()
			@Log
			public void method3() {
				System.out.println("method3");
			}
			
			public void method4() {
				System.out.println("method4");
			}
		};
		
		InvocationHandler testHandler = new AopInvocationHandler<Test>(t1);
		
		Test testProxy = (Test)Proxy.newProxyInstance(Test.class.getClassLoader(), new Class<?>[] {Test.class}, testHandler);
		
		testProxy.method1();
		testProxy.method2();
		testProxy.method3();
		testProxy.method4();
	}

}

運行結果:
在這裏插入圖片描述

- 總結
上面的動態代理的例子,其實就是AOP的一個簡單實現了,在目標對象的方法的註解進行識別判斷,並通過註解的不同執行相應的操作。Spring的AOP實現其實也是用了Proxy和InvocationHandler這兩個東西的。

- 參考資料

Java動態代理機制詳解(JDK 和CGLIB,Javassist,ASM)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章