Spring aop 原理解析(AOP 简介 和 Java 动态
AOP是什么
在软件业,AOP为Aspect Oriented Programming的缩写,意为:[面向切面编程] 通过[预编译]方式和运行期动态代理实现程序功能的统一维护的一种技术(摘自百度百科)。不知道是不是又很多人和我一样,在第一次看到这句话的时候一头雾水。
在我的理解来看,AOP可以理解为一种独立于模块的,可以穿插在方法间执行的技术。当还没有接触AOP这一概念的时候,我们习惯于用OO的思想去解决问题,但并不是什么情况下OO都是合适的。
比方说,当我们想要去统计系统中某个类和它子类的方法调用的次数。如果我们用OO的思想,一般会这样。首先,定义一个工具类,然后让基类持有这个工具类。但可能有一天,需求变了,可能范围变成了某个包下的类,这个时候就麻烦了。因为有可能需要对这个包下的所有类都进行改动,这肯定不是我们想要的结果。
那么理想情况下是什么呢,假设有一个东西叫切面,在这个切面上我们可以定义一个切点以及一系列的操作,而只有当切点满足的时候(比如在某个包下的类的某个方法被执行),这些操作才会被执行,这样,给予刚刚的那种情况,我们只需要定义一个切面,其切点为某个包下的所有类的方法,然后在定义一个统计操作,这样每次方法被调用时,就会去触发统计操作。
其实对于AOP而言,AspectJ 也是一种非常优秀的解决方案,AspectJ 会在编译期对源码进行织入,从而达到方法增强的效果,不过要使用AspectJ 需要对字节码有一定的了解,而且还要学习AspectJ的语法,需要一定的学习成本。对于Spring 来说,采用动态织入的方法,在运行期生成代理类,从而达到增强的效果。
在了解spring中的aop之前,我们先来复习一下代理模式。 delagate.png (作图工具只找到了这条黑线,╮(╯▽╰)╭,凑合着看吧。)
代理类和被代理实现同一接口,同时代理类持有被代理类,并对代理类的接口进行增强,客户端在调用接口时,实际上调用的时被代理的接口。在spring-aop中,实际上使用了java自带的动态代理和cglib这个第三方生成类库,这个类库封装了asm(一个操作字节码的库), 可以在运行时动态生成class,这两种策略个有其使用场景。
java动态代理
我们首先先简单的了解一下java动态代理,要使用java的动态代理,首先需要实现java.lang.reflect.InvocationHandler
这个接口
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
一般来说,可以在自己实现的InvationHandler里去持有被代理的对象,然后通过java.lang.reflect.Proxy#newProxyInstance
去生成代理对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
这里 iterfaces
为代理对象所实现的接口,我们看一个简单的例子
在这个例子中,我们先定义一个顶层空接口
ISub
package com.iplay.dynamic_proxy;
public interface ISub {
}
之后我们分别定义两个接口IHello
和ICal
并分别继承ISub
package com.iplay.dynamic_proxy;
public interface IHello extends ISub{
void sayHello();
}
package com.iplay.dynamic_proxy;
public interface ICal extends ISub{
int add (int a, int b);
}
之后我们定义一个Sub
,分别实现IHello
和ICal
package com.iplay.dynamic_proxy;
public class Sub implements ICal, IHello {
public void sayHello() {
System.out.println("Hello");
}
public int add(int a, int b) {
return a + b;
}
}
之后我们定义一个CallHandler
并让它实现InvocationHandler
package com.iplay.dynamic_proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class CallHandler implements InvocationHandler {
private final ISub isub;
public CallHandler(ISub isub) {
this.isub = isub;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before called");
Object ret = method.invoke(isub, args);
System.out.println("after called");
return ret;
}
}
最后我们看一下客户端类
package com.iplay.dynamic_proxy;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
/**
* Hello world!
*
*/
public class App {
public static void main(String args[]) {
ISub isub = new Sub();
CallHandler callHandler = new CallHandler(isub);
Object proxy = Proxy.newProxyInstance(Sub.class.getClassLoader(), new Class<?>[] {ICal.class, IHello.class}, callHandler);
Class<?> clz = proxy.getClass();
System.out.println("sup clz " + clz.getSuperclass());
System.out.println(Modifier.toString(clz.getModifiers()) + " " + clz.getName());
Class<?>[] itfs = clz.getInterfaces();
for(int i=0; i<itfs.length; i++) {
System.out.println(itfs[i].getName());
}
Method[] methods = clz.getMethods();
for(int i=0; i<methods.length; i++) {
System.out.println(Modifier.toString(methods[i].getModifiers()) + " " + methods[i].getName());
}
((IHello) proxy).sayHello();
System.out.println(((ICal) proxy).add(1, 2));
}
}
执行后结果
sup clz class java.lang.reflect.Proxy
public final com.sun.proxy.$Proxy0
com.iplay.dynamic_proxy.ICal
com.iplay.dynamic_proxy.IHello
public final add
public final equals
public final toString
public final hashCode
public final sayHello
public static isProxyClass
public static getInvocationHandler
public static transient getProxyClass
public static newProxyInstance
public final wait
public final wait
public final native wait
public final native getClass
public final native notify
public final native notifyAll
before called
Hello
after called
before called
after called
3
可以看到Proxy#newInstance
生成了一个新的类$Proxy0
, 这个类是final
的,继承了Proxy
, 并实现了我们传入了的两个接口IHello
和ICal
并将实现的方法都设置为了final
,同时这个类持有了我们传入的CallHandler
,CallHandler
中持有被代理对象,所有当我们通过ICal
, IHello
调用目标对象时,实际是通过$Proxy
调用对应的方法,在调用时,$Proxy
会通过反射获取对应的Method,传给InvocationHandler#invoke
执行对应的操作。