前言
Spring Framework是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架。IOC和AOP是Spring的两大核心思想。Spring容器IOC解析一文中聊了IOC的思想和原理,那么本篇我们来探究下AOP的思想及实现原理。
AOP和OOP概念
AOP(
Aspect-OrientedProgramming)面向切面编程,它将程序逻辑分解为所谓关注点的不同部分。跨越应用程序多个点的功能称为跨领域问题,这些跨领域问题在概念上与应用程序的业务逻辑分离。有许多常见的好例子,如日志记录,审计,声明式事务,安全性,缓存等。
OOP(Object-Oriented Programming)面向对象编程中模块化的关键单元是类,而在AOP中,模块化单元是切面。如果说依赖注入(IOC)可以将应用程序对象相互分离,那么AOP可以将交叉问题与它们所影响的对象分离。
AOP思想
AOP可以说是OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为时,OOP则显得无能为力。OOP允许定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平的散步到所有对象层次中,而与它所散步到的对象的核心功能毫无关系。这种散布在各处的无关的代码被称为横切代码。在OOP设计中,导致了大量的代码重复。而AOP技术恰恰相反,它将那些影响多个类的公共行为封装到一个重用模块即切面。减少重复代码、降低模块间的耦合度。用一句话概括AOP编程思想,即横向重复,纵向抽取。
AOP编程示意图
AOP的实现原理
通俗点讲IOC负责将对象动态的注入到容器。而 AOP实现的就是让容器中的对象都享有容器中的公共服务。那么容器是怎么做到的呢?那就是动态代理。即:AOP实现原理是动态代理。
动态代理其实不是什么新鲜的东西,学过设计模式的同学都应该知道代理模式,代理模式是一种静态代理。
静态代理类图
图中HelloImplProxy是代理类,需要我们Coding,在编译期间就生成的。
而动态代理就是用反射和动态编译将代理模式变成动态的。原理跟动态注入一样,代理模式在编译的时候就已经确定代理类将要代理谁,而动态代理在运行的时候才知道自己要代理谁。
动态代理类的生成依赖通过字节码动态生成加载技术,在运行时生成加载类。即,代理类的字节码将在运行时生成并载入当前代理的ClassLoader。
Spring的动态代理有两种:一种是JDK的动态代理;另一种是CGLib(CodeGeneration Library)的动态代理。
一、JDK动态代理
我们先来看JDK的动态代理。在看JDK动态代理实现之前,我们思考一下如何实现?
按照代理模式的实现方式,肯定是用一个代理类,让它也实现Hello接口,然后在其内部声明一个HelloImpl,然后分别调用sayHello()方法,并在调用前后加上我们需要的其它操作。但是这样显然是写死的,如何做到动态呢?我们知道,要实现代理,那么我们的代理类跟被代理类实现同一接口,但是动态代理的话我们根本不知道我们将要代理谁?也就不知道要实现哪个接口,那么要怎么办?我们只有知道要代理谁以后,才能给出相应的代理类,那么我们何不等知道要代理谁以后再去生成一个代理类呢?想到这里,我们好像想到了解决的办法,就是动态生成代理类!这时候反射有了用武之地,我们可以写一个方法来接收被代理类,这样我们就可以通过反射知道它的一切信息。
而JDK的动态代理主要就是通过反射跟动态编译来实现的,下面通过代码来看如何实现的。示例:
//被代理接口
public interface Hello {
void sayHello();
}
//被代理类
public class HelloImpl implements Hello {
@Override
public void sayHello() {
System.out.println("Hello Lucky!");
}
}
//Invocationhandler类
public class HelloInvocationHandler implements InvocationHandler {
private Object subject;
public HelloProxy(Object subject){
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoke "+ method.getName());
method.invoke(subject,args);
System.out.println("After invoke "+ method.getName());
return null;
}
}
//客户端测试类
public class Client {
public static void main(String[] args) {
HelloImpl helloImpl = new HelloImpl();
InvocationHandler handler = new HelloInvocationHandler(helloImpl);
Hello hello = (Hello) Proxy.newProxyInstance(handler.getClass().getClassLoader(),helloImpl.getClass().getInterfaces(),handler);
hello.sayHello();
}
}
//运行结果
Before invoke sayHello
Hello Lucky!
After invoke sayHello
JDK动态代理的两个核心分别是InvocationHandler和Proxy,
下面我们从源码角度分析一下这个过程的实现原理,先看InvocationHandler类
InvocationHandler是一个接口,只有一个方法:
public interface InvocationHandler {
public Object invoke(Object proxy, Methodmethod, Object[] args) throws Throwable;
}
在定义增强逻辑类时,需要实现该接口,并在invoke方法里实现增强逻辑和对真实对象方法的调用。
对于invoke方法的三个参数,proxy表示代理对象,method表示真实对象的方法,args表示真实对象方法的参数,这里也可以看出对真实对象方法的调用是通过反射来实现的。
增强逻辑定义好了以后,我们需要一个代理对象来执行,先看如何产生这个代理对象。代理对象的产生是通过调用Proxy类的静态方法:newProxyInstance,以下源码分析部分篇幅原因仅截取部分代码片段。
public static ObjectnewProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
//该方法需要三个参数:ClassLoader确保返回的代理对象和真实对象由同一个类加载器加载,interfaces用于定义代理类应该实现的方法,invocationHandler用于定义代理类的增强逻辑
//这一行代码很关键,这里是获取代理对象Class对象,Proxy类内部维护了代理
//对象的缓存,如果缓存里有则直接返回,如果没有,则生成它
Class> cl = getProxyClass0(loader, intfs);
//由Class对象获取构造器
final Constructor> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
// create proxy instance withdoPrivilege as the proxy class may
// implement non-publicinterfaces that requires a special permission
returnAccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
returnnewInstance(cons, ih);
}
});
} else {
//最后由构造器返回新的代理对象实例
return newInstance(cons, ih);
}
}
接下来我们看代理对象的Class对象是如何生成的,缓存逻辑略去不表,直接看没有缓存的情况,代理类Class对象由ProxyClassFactory工厂生成:
private static final classProxyClassFactory
implements BiFunction<ClassLoader, Class>[],Class>>
{
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
//验证代码略去
/*
* Generate the specified proxy class.
*/
//这里生成了代理对象二进制字节码流
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
//通过二进制字节码流加载代理类
return defineClass0(loader,proxyName,
proxyClassFile,0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw newIllegalArgumentException(e.toString());
}
}
}
}
最终生成二进制字节码流用到了Sun的ProxyGenerator类,
public staticbyte[] generateProxyClass(String s, Class aclass[])
{
ProxyGenerator proxygenerator = newProxyGenerator(s, aclass);
//该方法生成代理对象的二进制字节码流
byte abyte0[] =proxygenerator.generateClassFile();
//如果要保存该字节码文件,可以将其写到硬盘上,我们稍后分析
if(saveGeneratedFiles)
//保存文件部分略去
return abyte0;
}
在源码中我们可以通过saveGeneratedFiles变量保存生成的class文件,我们反编译上面的示例生成的class文件:
public final class $Proxy0 extends Proxy implements Hello{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandlervar1) throws {
super(var1);
}
public final boolean equals(Objectvar1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException Error var3) {
throw var3;
} catch (Throwablevar4) {
throw new UndeclaredThrowableException(var4);
}
}
//这里是Hello接口的sayHello方法,在代理对象$Proxy0上的调用被invocationHandler拦截,
//经由其invoke方法调用(即我们之前定义的HelloInvocationHandler的invoke方法),
//留意invoke方法的参数,我们在HelloInvocationHandler里定义invoke方法时并没有使用proxy参数,
//这里proxy参数的位置传入了this变量,即代理对象本身。
// 实际上就是调用HelloInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法
public final void sayHello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.jd.y.jdkproxy.Hello").getMethod("sayHello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
实现动态代理的关键部分,通过Proxy动态生成我们具体的代理类。
这个类主要的功能就是,根据被代理对象的信息,动态组装一个代理类,生成$Proxy0.java文件,然后将其编译成$Proxy0.class。这样就可以在运行的时候,根据我们具体的被代理对象生成我们想要的代理类了。这样一来,我们就不需要提前知道我们需要代理谁。也就是说,你想代理谁,想要什么样的代理,我们就给你生成一个什么样的代理类。然后在客户端我们就可以随意进行代理了。
也就是说main函数里面的proxy实际就是$Proxy0的一个实例对象。可知JDK动态代理是使用接口生成新的实现类,实现类里面则委托给InvocationHandler,InvocationHandler里面则调用被代理的类方法。
JDK动态代理类图
至此,总结一下,Proxy类通过ProxyClassFactory生成了继承Proxy类并实现了真实对象接口的 $ProxyX代理对象的二进制字节码流,并加载该字节码返回代理对象Class对象,由该Class对象经反射得到构造器构造了代理对象的实例。
二、CGLIB动态代理
接下来看CGLib如何实现动态代理。示例:
//定义回调函数
public class HelloMethodInterceptor implements MethodInterceptor {
//o 指代理对象 method指目标类中被拦截的方法 objects 指调用拦截方法所需参数 methodProxy 指用来调用目标类被拦截方法的方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Before invoke "+ method.getName());
methodProxy.invokeSuper(o,objects);
System.out.println("After invoke "+ method.getName());
return null;
}
}
//Cglib方式的客户端
public class ClientByCglib {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/gaofei12/Work/workspace-idea/demo-myself/demo/");
//实例化一个增强器,也就是cglib中的一个class generator
Enhancer eh = new Enhancer();
//设置目标类
eh.setSuperclass(HelloImpl.class);
// 设置拦截对象
eh.setCallback(new HelloMethodInterceptor());
// 生成代理类并返回一个实例
HelloImpl t = (HelloImpl) eh.create();
t.sayHello();
}
}
运行结果:
Before invoke sayHello
Hello Lucky!
After invoke sayHello
同时生成三个class文件:一个是代理类,一个是代理类的FastClass,一个是目标类的FastClass。
从上面的示例代码中,我们知道通过cglib生成代理类只需要一个目标类和一个回调函数(增强逻辑),下面我们从在代理对象上调用sayHello()方法出发,分析cglib动态代理的实现原理。
public class HelloImpl$EnhancerByCGLIB$a1ad184a extends HelloImpl implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
private static final Method CGLIB$sayHello$0$Method;
private static final MethodProxy CGLIB$sayHello$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$equals$1$Method;
private static final MethodProxy CGLIB$equals$1$Proxy;
private static final Method CGLIB$toString$2$Method;
private static final MethodProxy CGLIB$toString$2$Proxy;
private static final Method CGLIB$hashCode$3$Method;
private static final MethodProxy CGLIB$hashCode$3$Proxy;
private static final Method CGLIB$clone$4$Method;
private static final MethodProxy CGLIB$clone$4$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.jd.y.jdkproxy.HelloImpl$EnhancerByCGLIB$a1ad184a");
Class var1;
CGLIB$sayHello$0$Method = ReflectUtils.findMethods(new String[]{"sayHello", "()V"}, (var1 = Class.forName("com.jd.y.jdkproxy.HelloImpl")).getDeclaredMethods())[0];
CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$0");
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = var10000[0];
CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
}
final void CGLIB$sayHello$0() {
super.sayHello();
}
public final void sayHello() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
} else {
super.sayHello();
}
}
}
进入回调函数后,跟JDK动态代理InvocationHandler一样,先执行前置增强逻辑,然后将执行目标类的真实逻辑。
注意此处目标类的真实逻辑执行cglib的实现方式与JDK实现方式不同:JDK使用的是反射技术,而cglib则使用了FastClass构建方法索引+继承的方式访问目标类的方法。FastClass动态类为委托类方法调用语句建立索引,使用者根据方法签名(方法名+参数类型)得到索引值,再通过索引值进入相应的方法调用语句,得到调用结果。Fastclass 机制。由于篇幅原因上边只贴了部分代码,在这里抛砖引玉,感兴趣的同学自行读下源码,深入了解一下FastClass。
Cglib是通过直接继承被代理类,并委托为回调函数来做具体的事情。
CGLib动态代理类图
小结
这里我写的代理的功能,当然都是非常简单的写法,只是为了说明这个原理。当然,我们可以像Spring那样将这些AOP写到配置文件,因为网上好多写了怎么通过配置文件注入了,这里就不重复贴了。到这里,你可能会有一个疑问:你上面说,只要放到容器里的对象,都会有容器的公共服务,我怎么没看出来呢?好,那我们回顾一下我们的JDK代理方式的InvocationHandler类HelloInvocationHandler 和 CGLib回调函数HelloMethodInterceptor的代码。
从代码中不难看出,我们代理的功能里没有涉及到任何被代理对象的具体信息,这样有什么好处呢?这样的好处就是将代理要做的事情跟被代理的对象完全分开,这样一来我们就可以在代理和被代理之间随意的进行组合了。也就是说同一个功能我们只需要一个。同样的功能只有一个,那么这个功能不就是公共的功能吗?不管容器中有多少个对象,都可以享受容器提供的服务了。这就是AOP的好处。
JDK动态代理和CGLib方式的比较
类型 | 机制 | 回调方式 | 适用场景 | 效率 |
JDK动态代理委托机制 | 代理类和目标类都实现了同样的接口 | InvocationHandler持有目标类 ,代理类委托InvocationHandler去调用目标类的原始方法 反射 | 目标类是接口类 | 效率瓶颈在反射调用稍慢 |
CGLIB动态代理 | 继承机制 | 代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑通过FastClass方法索引调用 | 非接口类,非final类,非final方法 | 第一次调用因为要生成多个Class对象较JDK方式慢,多次调用因为有方法索引较反射方式快,如果方法过多switchcase过多其效率还需测试 |
小贴士:
1、CGLib是不能代理final类,或代理被final,private修饰的方法,cglib面对具体类代理,不能是接口。--- CGLib的实现机制 继承,JAVA中final private 修饰的是无法extend的。
2、jdk的代理面向的是接口代理。--- 因为JDK的代理类本身已经继承了Proxy,而Java不允许多重继承,但是允许实现多个接口。
不管是JDK动态代理,还是CGLib动态代理,他们是AOP思想的两种实现方式。我们既要理解其实现原理,更要明白AOP的思想,横向重复、纵向抽取,AOP也是OOP的补充和完善。