说到AOP,很容易就想到 spring AOP。因为它是AOP里最优秀的实现框架。但本文不打算讨论 spring AOP,只想把如何通过动态代理方式实现AOP思想说通。当然,整明白了这个道理,理解 spring AOP 也就简单了!
首先我觉得需特别强调一下什么是面向接口编程!
用本人的意思理解,面向接口编程,有两个方面的角色,一个是接口的实现者,一个是接口的使用者,而接口本质上是一种服务规范,它规定了一个规范是通过什么方式向服务对象提供服务的,有什么参数,有什么约束,会有什么异常无法处理等。对接口的实现者来说,他们只需要按接口规定的要求提供服务即可,不必关心谁会来使用接口;对接口使用者来看,他只关注接口能提供什么功能和服务,不必关心接口是怎么实现的。
比如 JDK 里,有一个接口 java.util.List。它规定了一个列表可以提供的一系列服务:添加元素、移除元素、根据索引取元素、得到元素数量……。而接口有两个实现:ArrayList 和 LinkedList。
我们编写程序时,作为接口的使用者,可以用
List lst = new ArrayList();
然后把它当作 List 接口的一个实例即可。如果哪天不高兴,把 new ArrayList(); 改为 new LinkedList(); ,对我们写的程序来说不会有业务逻辑上的变化。
而对接口的实现者,就是sun公司(现在是Oracle公司),他们在实现 ArrayList 和 LinkedList 时,并没有限制这两个实现只能用到XXX系统里,而不能用到YYY系统里。也就是说,接口的实现者不关心接口会被什么人使用。
Java动态代理的基本例子网上有很多,在本文也是随便找了一篇阅读之后略微改了一下。
首先是定义一个接口(Animate):
package com.csjl.tangram.test;
public interface Animate {
public void printName();
public String getName();
}
然后是接口的实现(Dog、Cat):
package com.csjl.tangram.test;
public class Dog implements Animate {
@Override
public void printName() {
System.out.println("This is dog!");
}
@Override
public String getName() {
return "dog";
}
}
package com.csjl.tangram.test;
public class Cat implements Animate {
@Override
public void printName() {
System.out.println("This is cat!");
}
@Override
public String getName() {
return "cat";
}
}
然后是一个代理处理类(SimpleProxy):
package com.csjl.tangram.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class SimpleProxy implements InvocationHandler {
private Object subject;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Begin invoke method: " + method.getName());
Object result = method.invoke(this.subject, args);
System.out.println("Finished invoke method: " + method.getName());
return result;
}
public SimpleProxy(Object subject){
this.subject = subject;
}
}
最后是测试类(Client):
package com.csjl.tangram.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args){
Cat cat = new Cat();
InvocationHandler handler = new SimpleProxy(cat);
Animate animate =
(Animate)Proxy.newProxyInstance(
Cat.class.getClassLoader(),
Cat.class.getInterfaces(),
handler);
System.out.println("Animate: " + animate.getClass().getName());
System.out.println(">>>>>>>>>>>>>>>>>>>>");
animate.printName();
System.out.println();
System.out.println("animate.getName() = " + animate.getName());
System.out.println(">>>>>>>>>>>>>>>>>>>>");
}
}
最后运行 Client 类的结果是:
Animate: com.sun.proxy.$Proxy0
>>>>>>>>>>>>>>>>>>>>
Begin invoke method: printName
This is cat!
Finished invoke method: printName
Begin invoke method: getName
Finished invoke method: getName
animate.getName() = cat
>>>>>>>>>>>>>>>>>>>>
注意到第一条打印语句是:Animate: com.sun.prox.$Proxy0
这个本人没有深入研究,网上别人的文章说这是 jvm 自动创建的类。但不管怎么样,它确实实现了 Animate 接口。
把上面的代码分为几大块:
1、接口定义(Animate)
2、接口实现(Cat、Dog 类)
3、得到 Animate 实例(Client.main 方法中,Proxy.newProxInstance 语句之前的代码全部)
4、调用 Animate 方法,实现业务逻辑
根据前面说的面向接口编程,上面的几块代码中:1是接口定义;2是接口实现;3和4是接口使用。而对方法的调用,是由自定义的代理类来实现的,那么代理在具体调用方法前、后,以及调用发生异常时都可以进行一些动作。这就是Java能够实现AOP的前提条件。在上面的程序中,只是在调用前后打印了一些语句,实际项目中会复杂得多。
如果使用了AOP框架,上面的第3块代码中得到 Animate 实例的具体实现一般是由框架根据配置文件动态生成,使用哪个代理也可以由配置文件动态指定。当然在 spring AOP 中,机制会复杂得多、功能会强大得多。本文只描述对AOP基本思想的理解。
看看下面修改后的程序,应该可以对AOP思想有了比较清晰的理解
接口定义(Animate,同上,略过)
接口实现(Dog、Cat,同上,略过)
定义了抽象的代理类(AbstractProxy),定义了抽象的方法调用前、后的操作,具体怎么操作由子类完成
package com.csjl.tangram.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public abstract class AbstractProxy implements InvocationHandler {
private Object subject;
public Object getSubject(){
return subject;
}
public void setSubject(Object value){
subject = value;
}
public abstract boolean beforeInvoke(Method method, Object[] args);
public abstract Object afterInvoke(Method method, Object[] args, Object result);
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(this.beforeInvoke(method, args) == false)
return null;
Object result = method.invoke(this.subject, args);
return this.afterInvoke(method, args, result);
}
}
实现切片操作的代理类(RealProxy),实现代理类可以有多个,具体由哪个进行代理,可以由配置文件指定
package com.csjl.tangram.test;
import java.lang.reflect.Method;
public class RealProxy extends AbstractProxy {
@Override
public boolean beforeInvoke(Method method, Object[] args) {
System.out.println("调用方法 " + method.getName() + " 之前的操作,看我能先准备点什么");
return true;
}
@Override
public Object afterInvoke(Method method, Object[] args, Object result) {
System.out.println("完成对方法 " + method.getName() + " 的调用,看我需要做什么收尾工作");
return result;
}
}
最后是测试类
package com.csjl.tangram.test;
import java.lang.reflect.Proxy;
public class Client {
private static Animate getAnimate(){
try{
Class<?> cls = null;
// 使用框架时,如何实例化animate会从配置文件中读取
cls = Class.forName("com.csjl.tangram.test.Cat");
Animate animate = (Animate)cls.newInstance();
// 使用框架时,如何实现化 handler 会从配置文件中读取
cls = Class.forName("com.csjl.tangram.test.RealProxy");
RealProxy handler = (RealProxy)cls.newInstance();
handler.setSubject(animate);
Animate result =
(Animate)Proxy.newProxyInstance(
animate.getClass().getClassLoader(),
animate.getClass().getInterfaces(),
handler);
return result;
}catch(Exception ex){
ex.printStackTrace();
return null;
}
}
public static void f2(){
Animate animate = getAnimate();
System.out.println("我要调用接口了");
animate.printName();
}
public static void main(String[] args){
f2();
}
}
在以上测试类中的 getAnimate 方法,在实际项目中一般建议使用一个工厂类使用工厂模式创建,这里简化起见直接写到 Client 类中了。
从以上的代码结构中我们可以看到:
1、接口的定义和传统没什么两样
2、接口的实现和传统没什么两样
3、接口的调用和传统没什么两样,不一样的是修改 getAnimate 方法的实现,但这不会影响到主业务逻辑
4、但通过继承 AbstractProxy 类,可以在 Animate.printName() 和 Animate.getName() 方法的调用前后、异常发生时做一些逻辑处理工作。并且这些操作是可以通过配置进行动态修改的。
如果理解了Java动态代理,读者可以自己写一个小框架实现简单的AOP了!其实也就是上面代码中注释里提到的那些内容!