介紹
眾所周知,AOP(面向切面編程)是Spring框架的特色功能之一。通過設置橫切關(guān)注點(cross cutting concerns),AOP提供了極高的擴展性。那AOP在Spring中是怎樣運作的呢?當你只能使用core java,卻需要AOP技術(shù)時,這個問題的解答變得極為關(guān)鍵。不僅如此,在高級技術(shù)崗位的面試中,此類問題也常作為考題出現(xiàn)。這不,我的朋友最近參加了一個面試,就被問到了這樣一個棘手的問題——如何在不使用Spring及相關(guān)庫,只用core Java的條件下實現(xiàn)AOP。因此,我將在本文中提供一份大綱,幫助大家了解如何只用core Java實現(xiàn)一個AOP(當然啦,這種AOP在功能上有一定的局限性)。注意,本文不是一篇有關(guān)Spring AOP與Java AOP的對比研究,而是有關(guān)在core Java中借助固有的設計模式實現(xiàn)AOP的教程。
想必讀者已經(jīng)知道AOP是什么,也知道在Spring框架中如何使用它,因此本文只著眼于如何在不用Spring的前提下實現(xiàn)AOP。首先,我們得知道,Spring是借助了JDK proxy和CGlib兩種技術(shù)實現(xiàn)AOP的。JDK dynamic proxy提供了一種靈活的方式來hook一個方法并執(zhí)行指定的操作,但執(zhí)行操作時得有一個限制條件:必須先提供一個相關(guān)的接口以及該接口的實現(xiàn)類。實踐出真知,讓我們透過一個案例來理解這句吧!現(xiàn)在有一個計算器程序,用于完成一些數(shù)學運算。讓我們來考慮下除法功能,此時的問題是:如果core framework 已經(jīng)具備了一份實現(xiàn)除法的代碼,我們能否在代碼執(zhí)行時劫持(highjack)它并執(zhí)行額外的校驗呢?答案是肯定的,我將用下面提供的代碼片段來證明這點。首先來看基礎接口的代碼:
1
2
3
|
public interface Calculator { public int calculate( int a , int b); } |
該接口實現(xiàn)類的代碼如下:
1
2
3
4
5
6
|
public class CalculatorImpl implements Calculator { @Override public int calculate( int a, int b) { return a/b; } } |
假設我們既不能修該上面的代碼,也不能對核心庫進行任何改動,怎樣才能完美地實現(xiàn)校驗功能呢?不如試下JDK dynamic proxy的功能吧。
1
2
3
4
5
6
7
8
9
10
11
12
|
public class SomeHandler implements InvocationHandler { // Code omitted for simplicity….. @Override public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { // Your complex business validation and logic Object result = method.invoke(targetObject ,params); return result; } } |
讓我們通過測試類來看看由JDK dynamic proxy實現(xiàn)的校驗功能的效果如何。
1
2
3
4
5
6
7
|
public static void main(String[] args) { CalculatorImpl calcImpl = new CalculatorImpl(); Calculator proxied = (Calculator)ProxyFactory.getProxy (Calculator. class , calcImpl, new SomeHandler(calcImpl)); int result = proxied.calculate( 20 , 10 ); System.out.println( "FInal Result :::" + result); } |
從結(jié)果可以看出,簡單地實現(xiàn)功能強大的InvocationHandler接口,我們便能得到一個hooking implementation。按照JDK文檔的描述,InvocationHandler接口是借助一個代理實例(proxy instance)來處理一個方法調(diào)用的。
現(xiàn)在我們已經(jīng)知道,InvocationHandler的invoke()方法能夠幫助我們解決問題。那么再來解決一個新問題——怎樣才能在方法執(zhí)行的前后執(zhí)行操作呢?說的更具體一些,我們能通過添加多個aop(before、after、around)來hook一個方法嗎(譯注:原文為add multiple aops,但我認為Handler是充當Aspect的角色)?答案同樣是肯定的。按照以下的步驟建立一個精簡的代碼模板便能滿足這樣的需求:
-
創(chuàng)建一個抽象類,用于將aop應用于目標對象上。
-
創(chuàng)建名為BeforeHandler 和 AfterHandler的兩個aop。前者在方法執(zhí)行之前工作,而后者則在方法執(zhí)行結(jié)束后工作。
-
創(chuàng)建一個代理類,使所有的aop handler和目標對象只需作為參數(shù)傳入,就能創(chuàng)建一個hook。
-
加入你自己的業(yè)務邏輯或者橫切關(guān)注點。
-
最后,通過傳入相關(guān)的參數(shù)創(chuàng)建代理對象(proxy object)。
兩種實現(xiàn)AOP的方式:
1,JDK提供的動態(tài)代理實現(xiàn)
接口
1
2
3
4
5
6
7
|
public interface UserBean { void getUser(); void addUser(); void updateUser(); void deleteUser(); } |
原始實現(xiàn)類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class UserBeanImpl implements UserBean { private String user = null ; public UserBeanImpl() { } public UserBeanImpl(String user) { this .user = user; } public String getUserName() { return user; } public void getUser() { System.out.println( "this is getUser() method!" ); } public void setUser(String user) { this .user = user; System.out.println( "this is setUser() method!" ); } public void addUser() { System.out.println( "this is addUser() method!" ); } public void updateUser() { System.out.println( "this is updateUser() method!" ); } public void deleteUser() { System.out.println( "this is deleteUser() method!" ); } } |
代理類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import com.cignacmc.finance.bean.UserBeanImpl; public class UserBeanProxy implements InvocationHandler { private Object targetObject; public UserBeanProxy(Object targetObject) { this .targetObject = targetObject; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { UserBeanImpl userBean = (UserBeanImpl) targetObject; String userName = userBean.getUserName(); Object result = null ; //權(quán)限判斷 if (userName != null && ! "" .equals(userName)) { result = method.invoke(targetObject, args); } return result; } } |
測試類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import java.lang.reflect.Proxy; import com.cignacmc.finance.bean.UserBean; import com.cignacmc.finance.bean.UserBeanImpl; import com.cignacmc.finance.proxy.UserBeanProxy; public class ProxyExe { public static void main(String[] args) { System.out.println( "Proved............." ); UserBeanImpl targetObject = new UserBeanImpl( "Bob Liang" ); UserBeanProxy proxy = new UserBeanProxy(targetObject); //生成代理對象 UserBean object = (UserBean)Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), proxy); object.addUser(); System.out.println( "NO Proved............." ); targetObject = new UserBeanImpl(); proxy = new UserBeanProxy(targetObject); //生成代理對象 object = (UserBean)Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), proxy); object.addUser(); } } |
輸出:
1
2
3
|
Proved............. this is addUser() method! NO Proved............. |
從上面這個例子可以成功攔截了調(diào)用的方法addUser()并對其做了相應的處理
2, 通過cglib創(chuàng)建代理類
好處是不要求我們的目標對象實現(xiàn)接口
原始類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
public class ClientBean { private String name = null ; public ClientBean() { } public ClientBean(String name) { this .name = name; } public void addClient() { System.out.println( "this is addClient() method!" ); } public void deleteClient() { System.out.println( "this is deleteClient() method!" ); } public void getClient() { System.out.println( "this is getClient() method!" ); } public void updateClient() { System.out.println( "this is updateClient() method!" ); } public String getClientName() { return name; } public void setClientName(String name) { this .name = name; } } |
代理類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import java.lang.reflect.Method; import com.cignacmc.finance.bean.ClientBean; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CGLibProxy implements MethodInterceptor { private Object targetObject; public Object createProxyObject(Object targetObject) { this .targetObject = targetObject; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass( this .targetObject.getClass()); enhancer.setCallback( this ); return enhancer.create(); } public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { ClientBean clientBean = (ClientBean)targetObject; String userName = clientBean.getClientName(); Object result = null ; if (userName != null && ! "" .equals(userName)) { result = method.invoke(targetObject, args); } return result; } } |
測試類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import java.lang.reflect.Proxy; import com.cignacmc.finance.bean.ClientBean; import com.cignacmc.finance.bean.UserBean; import com.cignacmc.finance.bean.UserBeanImpl; import com.cignacmc.finance.proxy.CGLibProxy; import com.cignacmc.finance.proxy.UserBeanProxy; public class ProxyExe { public static void main(String[] args) { System.out.println( ".............CGLIB Proxy...................." ); System.out.println( "Proved...................." ); CGLibProxy cproxy = new CGLibProxy(); ClientBean clientBean = (ClientBean)cproxy.createProxyObject( new ClientBean( "Bob Liang" )); clientBean.addClient(); System.out.println( "NO Proved...................." ); cproxy = new CGLibProxy(); clientBean = (ClientBean)cproxy.createProxyObject( new ClientBean()); clientBean.addClient(); } } |
輸出:
1
2
3
4
|
.............CGLIB Proxy.................... Proved.................... this is addClient() method! NO Proved.................... |