介绍
众所周知,AOP(面向切面编程)是Spring框架的特色功能之一。通过设置横切关注点(cross cutting concerns),AOP提供了极高的扩展性。那AOP在Spring中是怎样运作的呢?当你只能使用core java,却需要AOP技术时,这个问题的解答变得极为关键。不仅如此,在高级技术岗位的面试中,此类问题也常作为考题出现。这不,我的朋友最近参加了一个面试,就被问到了这样一个棘手的问题——如何在不使用Spring及相关库,只用core Java的条件下实现AOP。因此,我将在本文中提供一份大纲,帮助大家了解如何只用core Java实现一个AOP(当然啦,这种AOP在功能上有一定的局限性)。注意,本文不是一篇有关Spring AOP与Java AOP的对比研究,而是有关在core Java中借助固有的设计模式实现AOP的教程。
想必读者已经知道AOP是什么,也知道在Spring框架中如何使用它,因此本文只着眼于如何在不用Spring的前提下实现AOP。首先,我们得知道,Spring是借助了JDK proxy和CGlib两种技术实现AOP的。JDK dynamic proxy提供了一种灵活的方式来hook一个方法并执行指定的操作,但执行操作时得有一个限制条件:必须先提供一个相关的接口以及该接口的实现类。实践出真知,让我们透过一个案例来理解这句吧!现在有一个计算器程序,用于完成一些数学运算。让我们来考虑下除法功能,此时的问题是:如果core framework 已经具备了一份实现除法的代码,我们能否在代码执行时劫持(highjack)它并执行额外的校验呢?答案是肯定的,我将用下面提供的代码片段来证明这点。首先来看基础接口的代码:
1
2
3
|
public interface Calculator { public int calculate( int a , int b); } |
该接口实现类的代码如下:
1
2
3
4
5
6
|
public class CalculatorImpl implements Calculator { @Override public int calculate( int a, int b) { return a/b; } } |
假设我们既不能修该上面的代码,也不能对核心库进行任何改动,怎样才能完美地实现校验功能呢?不如试下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实现的校验功能的效果如何。
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); } |
从结果可以看出,简单地实现功能强大的InvocationHandler接口,我们便能得到一个hooking implementation。按照JDK文档的描述,InvocationHandler接口是借助一个代理实例(proxy instance)来处理一个方法调用的。
现在我们已经知道,InvocationHandler的invoke()方法能够帮助我们解决问题。那么再来解决一个新问题——怎样才能在方法执行的前后执行操作呢?说的更具体一些,我们能通过添加多个aop(before、after、around)来hook一个方法吗(译注:原文为add multiple aops,但我认为Handler是充当Aspect的角色)?答案同样是肯定的。按照以下的步骤建立一个精简的代码模板便能满足这样的需求:
-
创建一个抽象类,用于将aop应用于目标对象上。
-
创建名为BeforeHandler 和 AfterHandler的两个aop。前者在方法执行之前工作,而后者则在方法执行结束后工作。
-
创建一个代理类,使所有的aop handler和目标对象只需作为参数传入,就能创建一个hook。
-
加入你自己的业务逻辑或者横切关注点。
-
最后,通过传入相关的参数创建代理对象(proxy object)。
两种实现AOP的方式:
1,JDK提供的动态代理实现
接口
1
2
3
4
5
6
7
|
public interface UserBean { void getUser(); void addUser(); void updateUser(); void deleteUser(); } |
原始实现类
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 ; //权限判断 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............. |
从上面这个例子可以成功拦截了调用的方法addUser()并对其做了相应的处理
2, 通过cglib创建代理类
好处是不要求我们的目标对象实现接口
原始类
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.................... |