SpringAOP可以说是面试必问的内容之一,这是作为JAVA高级开发者必须掌握的技能之一。
今天我结合实例、源码分析一下这一套机制。默认大家对静态代理和动态代理已经有了基础的理解。
1、动态代理
静态代理和动态代理的差别在此不再多说,网上到处是。我主要结合源码想说说动态代理,它是SpringAop的基础。
1.1 JDK自带的代理机制
1.1.1 示例
接口定义:
package com.zzmlake;
public interface Human {
void doExec();
void doWork();
}
实现类,也是被被代理类:
package com.zzmlake;
public class Man implements Human{
@Override
public void doExec() {
System.out.println("I am running...");
}
@Override
public void doWork() {
System.out.println("I am codding...");
}
}
代理类必须实现InvocationHandler接口,重写invoke方法:
package com.zzmlake;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyHandler implements InvocationHandler {
private Object object;
public MyHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoke " + method.getName());
method.invoke(object, args);
System.out.println("After invoke " + method.getName());
return null;
}
public static void main(String[] args) {
Human human = new Man();
MyHandler handler = new MyHandler(human);
Human proxyHuman = (Human)Proxy.newProxyInstance(human.getClass().getClassLoader(), human.getClass().getInterfaces(), handler);
proxyHuman.doExec();
proxyHuman.doWork();
}
}
这里把测试代码一起贴出来了,执行结果如下:
Before invoke doExec
I am running...
After invoke doExec
Before invoke doWork
I am codding...
After invoke doWork
Process finished with exit code 0
doExec()和doWork()这两个方法调用时,在调用前后,都各自输出一次打印,这说明了几件事情:
- 通过代理类代理后,每一次调用方法,其实都是调用了代理类的invoke()方法;
- 代理接口实现的invoke()方法的参数就是当前所执行的方法的基本信息,可以通过method.invoke(object, args);实际去执行这个方法;
- 在invoke()方法中,我们可以在被代理类实际的方法执行前后,多做一些额外的工作。
这里面有几个关键方法,我们来看一下。
1.1.2 InvocationHandler接口
InvocationHandler和Proxy代理的关系,让我想起Runnable和Thread,他们之间的关系几乎差不多:
- 定义Runnable接口实现类,重写run()方法,把线程需要执行的实际代码写在run()里面;最后通过Thread实例去执行这段代码;
- 定义InvocationHandler接口实现类,重写invoke()方法,把对原方法的改造写在invoke()里面;最后通过Proxy实例的方法调用去执行这段代码;
感觉是不是很像?哈哈!
InvocationHandler接口源码如下:
//这个接口和Runnable接口基本类似,就声明了一个方法,其他什么都没
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler接口非常简单,就一个接口方法invoke(),invoke的中文意思就是被调用。我们通过注释看看参数说明:
- proxy:the proxy instance that the method was invoked on. 方法被调用的代理实例。
- method: instance corresponding to the interface method invoked on the proxy instance. 在代理实例中被调用的接口方法实例。
- args: an array of objects containing the values of the arguments passed in the method invocation on the proxy instance.其实就是method的参数了。
简单的举个例子,如果你调用a.myfun(10, “hello”),那么此时method就是myfun方法,args这个数组里面放的就是10和"hello"。
代理对象
在InvocationHandler接口实现类MyHandler 中,我们有如下定义:
private Object object;
这个object,就是被代理的对象。定义为Object类型,具有更好的通用性。当然,你也可以使用被代理的接口等其他类型。比如把被代理类的类型改成这样,结果完全不变:
private Human object;
public SecondTest(Human object){
this.object = object;
}
我们可以通过构造方法对其进行初始化,就是一个简单的赋值。类型是否冲突,就得程序员自己处理了。
代理内容
为什么我们要代理呢?我们要额外做点什么事情呢?这就体现在了invoke()方法!
InvocationHandler接口实现类里面,invoke()方法必须重写。我们对Java类进行动态代理的目的,就在这个方法中实现。你可以加入一些日志打印,就像我加的System.out.println("Before invoke " + method.getName());一样。也可以加入权限校验,各种逻辑判断等等其他代码进来。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
......(方法执行之前做点什么,写在前)
method.invoke(object, args);
......(方法执行之后继续做点什么,写在后)
return null;
}
要注意的是:如果直接这么写,被代理类的任何方法被调用时,我们额外加入的代码都会被执行。比如上面例子执行过程中,invoke()里面的打印语句执行了4次。实际开发中,我们需要对特定的方法进行处理,此时可以先做判断,比如:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("doWork")){
System.out.println("Before invoke " + method.getName());
method.invoke(object, args);
System.out.println("After invoke " + method.getName());
}else {
method.invoke(object, args);
}
return null;
}
这里,我们加入了一个判断if(method.getName().equals(“doWork”)),表面只有方法名为doWork,才进行额外的处理;否则直接执行,不做任何修改。执行结果:
I am running...
Before invoke doWork
I am codding...
After invoke doWork
这个机制应该是很清晰明的。执行被代理类中原本的方法,用的是method.invoke()。
1.1.3 method.invoke()
首先我们要清楚,Method也是个重要的类:
public final class Method extends Executable {
private Class<?> clazz;
private int slot;
private String name;
private Class<?> returnType;
private Class<?>[] parameterTypes;
private Class<?>[] exceptionTypes;
private int modifiers;
......
}
我们写出一个方法,在类加载时,也会被加载到内存中;在内存中其实就是Method对象。我们可以看到,方法名称,返回类型,参数类型,异常类型等等方法的基本属性全都有了。其实,这属于Java反射的内容。
invoke()方法如下:
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
最后面一句最关键:
return ma.invoke(obj, args);
这个invoke源码看不到,属于java底层实现。总之就是当前的方法被执行了,代理者是obj,参数是args。
1.1.4 Proxy.newProxyInstance
在测试代码中,我们使用了Proxy.newProxyInstance来得到一个代理类实例,然后用这个代理实例去调用被代理类的方法。这就是整个代理机制的核心方法。
简化后的核心代码如下:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException
{
......
final Class<?>[] intfs = interfaces.clone();
.....
//Look up or generate the designated proxy class.查找或创建一个设计好的代理类
Class<?> cl = getProxyClass0(loader, intfs);
//Invoke its constructor with the designated invocation handler.使用设计好的handler去调用构造器
try {
......
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
......
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
......
}
}
通过第二个参数,其实我们就已经能确认这种动态代理方式的最大缺点:你必须先定义接口,通过接口才能实现动态代理能力。
这里面最关键的应该有两句:
- Class<?> cl = getProxyClass0(loader, intfs);查找或创建一个设计好的代理类
- return cons.newInstance(new Object[]{h});使用构造器新建一个实例
getProxyClass0
直接看看这个方法的关键说明:
Returns the {@code java.lang.Class} object for a proxy class given a class loader and an array of interfaces. The proxy class
will be defined by the specified class loader and will implement all of the supplied interfaces. If a proxy class for the same permutation of interfaces has already been defined by the class loader, then the existing proxy class will be returned; otherwise, a proxy class for those interfaces will be generated dynamicallyand defined by the class loader.
大意是:通过给定的class loader和一组接口得到一个代理类。这个代理类将被指定的class loader定义,并且将实现所有提供的接口。如果这个代理类已经被定义过,它会被一直存放在缓存里面,此时可以直接返回定义好代理类;否则class loader将动态的创建一个。
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
调用链挺长的,我就上一段核心代码吧:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
这就是代理类的生成代码,defineClass0是一个native方法,在Jvm底层实现。
前面已经说过,jdk自带的动态代理功能最大的问题是:你必须先定义接口,通过接口才能实现动态代理能力!
1.2 cglib实现的代理机制
cglib实现动态代理的原理和jdk自带的并不一样,不需要专门去定义接口,而是通过拦截方法的方式进行的,示例如下:
package com.zzmlake;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibTest implements MethodInterceptor {
private Object targetObject;
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable{
Object obj = null;
if(method.getName().equals("doWork")){
System.out.println("Before intercept " + method.getName());
obj = method.invoke(targetObject, args);
System.out.println("After intercept" + method.getName());
}else{
obj = method.invoke(targetObject, args);
}
return obj;
}
public Object createProxyObject(Object obj){
this.targetObject = obj;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
Object proxy = enhancer.create();
return proxy;
}
static public void main(String [] args){
CglibTest cglibTest = new CglibTest();
Man man = (Man)cglibTest.createProxyObject(new Man());
man.doWork();
man.doExec();
}
}
这里我们定义了一个类,实现了MethodInterceptor接口,重写了intercept方法。在这个方法里,我们可以实现动态修改的内容。这里我还是使用上面定义好的那个类Man,也是对doWork方法进行单独处理,执行结果和JDK方式一样:
Before intercept doWork
I am codding...
After intercept doWork
I am running...
生成代理类需要使用Enhance类来完成,这个类的说明如下:
Generates dynamic subclasses to enable method interception. This class started as a substitute for the standard Dynamic Proxy support included with JDK 1.3, but one that allowed the proxies to extend a concrete base class, in addition to implementing interfaces. The dynamically generated subclasses override the non-final methods of the superclass and have hooks which callback to user-defined interceptor implementations.
第一句就说明了Enhance的作用:生成动态子类以实现方法拦截。最核心的几个方法:
- setSuperclass:Set the class which the generated class will extend设置需要进行动态代理的类。
- setCallback:必须指定一个CallBack,类说明里面有:Often a single callback will be used per enhanced class。一般直接用当前类即可,MethodInterceptor的基类也是CallBack。
- create:Generate a new class if necessary and uses the specified callbacks (if any) to create a new object instance.这个是核心的一个方法,生成我们想要的代理类实例。
可见,Cglib实现动态代理非常简单和灵活,它不需要被代理的类必须有接口,直接为被代理类生成一个子类,在子类中实现动态的修改。Cglib是基于asm实现的,非常遗憾的是,它的发展非常慢,已经跟不上JDK的发展速度了,执行的性能已经被JDK超过。
2、Spring AOP
Spring AOP我们一般称为切面编程,AOP是Aspect Orient Programming的缩写。它普遍应用于事务管理、日志、缓存等各方面。
Spring AOP是基于上面这两种动态代理来实现的,它会根据被代理类的实现方式,进行自动切换。如果被代理类有接口,则通过JDK来实现;否则使用Cglib来实现。JDK1.8以后,JDK动态代理的性能已经超过Cglib,这也是很多编码规范建议多使用接口的原因之一。
Spring AOP有4个主要的概念:
- 切入点(Pointcut):指明切入的地方,要代理的类、方法等;
- 通知(Advice):被代理的内容,在方法执行前、中、后要做什么事情;
- 切面(Aspect):完成切入点和通知的开发,就是切面编程的过程;
- 织入(Weaving):创建出代理对象的过程;
2.1 AOP切面编程
Spring AOP切面编程具体内容将在下篇详细说明。