JDK的Proxy技術實現AOP,InvocationHandler和Proxy詳解——Spring AOP(三)
上一章已經講到了想要完成AOP的設計原理以及要實現AOP功能,得需要使用代理模式:
本章就介紹一個實現動態代理的兩種方式之一——JDK中的Proxy技術
AOP實現原理(使用JDK中的Proxy技術實現AOP功能,InvocationHandler和Proxy(Class)詳解)
AOP技術在企業開發中或多或少都會用到,但用的最多的大概就是做許可權系統時,判斷使用者是否是有許可權,有許可權就執行該方法,沒有許可權就不能執行該方法,本章就以此為案例
現有如下需求
使用者在執行某個業務方法時我們需要對這個使用者進行判斷是否具有許可權。
例如實現使用者的增刪改查操作,在不使用AOP的情況下,在執行方法之前要判斷使用者user是否為null(表示沒有許可權或者沒有登入),而一旦我們的需求發生改變,就需要再次修改程式碼進行判斷,這種開發導致修改程式碼頻繁程式碼不易維護。AOP就實現了程式碼的可插拔性(AOP第一章介紹過)
為了完成上面的需求,我們可以使用橫行擴充套件的方式給目標業務層新增代理物件。
代理模式的代理角色最起碼要考慮三個階段:
- 在呼叫真正物件的方法之前,應該需要做什麼?
- 在呼叫真正物件的方法過程中,如果丟擲了異常,需要做什麼?
- 在呼叫真正物件的方法後,返回了結果了,需要做什麼?
JDK的Proxy技術
在使用Proxy代理物件的時候,對需要代理的物件有一些要求,目標物件必須是實現了介面,是面向介面程式設計的。(其實就是去實現目標物件實現的所有介面)
新建一個專案spring_aop(java專案或web專案均可),在com.oak.service中建立UserService介面,然後建立UserServiceImpl實現它
UserService如下:
public interface UserService {
void add(String name);
void delete(int id);
}
UserServiceImpl:
public class UserServiceImpl implements UserService{
//假設有該user是個User物件
private String user=null;
public String getUser(){
return user;
}
@Override
public void add(String name) {
System.out.println("我要增加");
}
@Override
public void delete(int id) {
System.out.println("我要刪除");
}
}
如果按照我們之前的處理方式,就是下面這種方式:
public class UserServiceImpl implements UserService{
//假設有該user是個User物件
private String user=null;
public String getUser(){
return user;
}
public UserServiceImpl(){
}
public UserServiceImpl(String user){
this.user=user;
}
@Override
public void add(String name) {
if(user!=null){
System.out.println("我要增加");
}else{
System.out.println("沒有許可權");
}
}
@Override
public void delete(int id) {
if(user!=null){
System.out.println("我要刪除");
}else{
System.out.println("沒有許可權");
}
}
}
很明顯這種方式複雜並且維護性較差
現在我們就使用JDK中的Proxy技術來生成UserServiceBean物件的代理物件,但要記住在Java裡面有這樣一個限定:要想建立某一個物件的代理物件,那麼該物件必須實現一個介面。
在com.oak.aop包下新建一個類JDKProxyFactory實現InvocationHandler介面:
public class JDKProxyFactory implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
}
然後我們來完成這個代理物件類的編寫,需要用來以下兩個東西:
Proxy類
我們先來看看Proxy這個類,它的作用就是用來動態建立一個代理物件,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個方法:
Proxy類的newProxyInstance靜態方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
這個方法的作用就是得到一個動態的代理物件,其接收三個引數,我們來看看這三個引數所代表的含義:
loader: 一個ClassLoader物件,定義了由哪個ClassLoader物件來對生成的代理物件進行載入
interfaces: 一個Interface物件的陣列,表示的是我將要給我需要代理的物件提供一組什麼介面,如果我提供了一組介面給它,那麼這個代理物件就宣稱實現了該介面(多型),這樣我就能呼叫這組介面中的方法了
h: 一個InvocationHandler物件,表示的是當我這個動態代理物件在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上
如果不是很明白,繼續我們的案例來講解
把剛才建立好的JDKProxyFactory進行完善,先建立一個成員變數代表要代理的目標物件(那個真實物件),再建立一個例項工廠方法使用 Proxy類的newProxyInstance方法返回一個一個動態代理物件。
程式碼如下:
public class JDKProxyFactory implements InvocationHandler{
//代理的目標物件
private Object targetObject;
//例項工廠返回返回一個目標物件的代理物件
public Object createProxyInstance(Object targetObject){
this.targetObject=targetObject;
/**
* 第一個引數設定程式碼使用的類裝載器,一般採用跟目標相同的類裝載器
* 第二個引數設定代理類實現的介面,所以要求代理的目標物件必須實現一個類
* 第三個引數設定回撥物件,當代理物件的方法被呼叫時,會委派給該引數指定物件的invoke方法
*/
return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader()
,this.targetObject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
}
InvocationHandler介面
每一個動態代理類都必須要實現InvocationHandler這個介面,並且每個代理類的例項都關聯到了一個handler,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行呼叫。
InvocationHandler這個介面的唯一一個方法 invoke 方法
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
我們看到這個方法一共接受三個引數:
proxy: 指代我們所代理的那個真實物件
method: 指代的是我們所要呼叫真實物件的某個方法的Method物件
args: 指代的是呼叫真實物件某個方法時接受的引數
如果不是很明白,沒關係,我們繼續案例講解,
在JDKProxyFactory中完善invoke方法,可以在其中判斷許可權,根據許可權來判斷真實物件的方法執不執行,也可以真實方法呼叫之前指定一段程式碼,可以在發生異常時指定程式碼,也可以在之後指定一段程式碼:
public class JDKProxyFactory implements InvocationHandler{
//代理的目標物件
private Object targetObject;
//例項工廠返回返回一個目標物件的代理物件
public Object createProxyInstance(Object targetObject){
this.targetObject=targetObject;
/**
* 第一個引數設定程式碼使用的類裝載器,一般採用跟目標相同的類裝載器
* 第二個引數設定代理類實現的介面,所以要求代理的目標物件必須實現一個類
* 第三個引數設定回撥物件,當代理物件的方法被呼叫時,會委派給該引數指定物件的invoke方法
*/
return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader()
,this.targetObject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
UserServiceImpl service=(UserServiceImpl) this.targetObject;
Object result=null;
//在代理真實物件前我們可以新增一些自己的操作
System.out.println("before service方法執行...");
if(service.getUser()!=null){//判斷許可權
// 把方法呼叫委派給目標物件
result=method.invoke(targetObject, args);
}
//在代理真實物件後我們也可以新增一些自己的操作
System.out.println("after service方法執行...");
return result;
}
}
新建測試類:
測試1,使用無參構造建立UserServiceImpl物件,然後呼叫UserService的add方法
@Test
public void test01(){
UserService userService=(UserService) new JDKProxyFactory()
.createProxyInstance(new UserServiceImpl());
userService.add("哈哈");
}
user為null,沒有許可權,實際上add方法沒有執行,控制檯輸出:
before service方法執行...
after service方法執行...
測試2,使用有參構造建立UserServiceImpl物件:
@Test
public void test02(){
UserService userService=(UserService) new JDKProxyFactory()
.createProxyInstance(new UserServiceImpl("admin"));
userService.add("哈哈");
}
user為admin,有許可權,add方法被呼叫執行,控制檯輸出:
before service方法執行...
我要增加
after service方法執行...
在實際的開發中我們的目標物件可能沒有實現介面,那麼我們就不能使用JDK的動態代理,我們可以使用第三方的實現方式(CGLIB),下一章: