Spring AOP原理解析
java
原文:http://blog.jobbole.com/28791/git
笔记:程序员
简介: AOP(Aspect Orient Programming),也就是面向方面编程,做为面向对象编程的一种补充,专门用于处理系统中分布于各个模块(不一样方法)中的交叉关注点的问题,在 Java EE 应用中,经常经过 AOP 来处理一些具备横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等。AOP 实现的关键就在于 AOP 框架自动建立的 AOP 代理,AOP 代理主要分为静态代理和动态代理两大类,静态代理以 AspectJ 为表明;而动态代理则以 Spring AOP 为表明。本文会从 AspectJ 分析起,逐渐深刻,并介绍 CGLIB 来介绍 Spring AOP 框架的实现原理。github
AOP(Aspect Orient Programming),做为面向对象编程的一种补充,普遍应用于处理一些具备横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等。AOP 实现的关键就在于 AOP 框架自动建立的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,所以也称为编译时加强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,所以也被称为运行时加强。编程
Spring AOP 原理剖析缓存
经过Spring AOP配置使用能够知道:安全
AOP 代理实际上是由 AOP 框架动态生成的一个对象,该对象可做为目标对象使用。AOP 代理包含了目标对象的所有方法,但 AOP 代理中的方法与目标对象的方法存在差别:AOP 方法在特定切入点添加了加强处理,并回调了目标对象的方法。bash
AOP 代理所包含的方法与目标对象的方法示意图如图 3 所示。框架
图 3.AOP 代理的方法与目标对象的方法工具
Spring 的 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理。所以,AOP 代理能够直接使用容器中的其余 Bean 实例做为目标,这种关系可由 IoC 容器的依赖注入提供。
纵观 AOP 编程,其中须要程序员参与的只有 3 个部分:
● 定义普通业务组件。
● 定义切入点,一个切入点可能横切多个业务组件。
● 定义加强处理,加强处理就是在 AOP 框架为普通业务组件织入的处理动做。
上面 3 个部分的第一个部分是最日常不过的事情,无须额外说明。那么进行 AOP 编程的关键就是定义切入点和定义加强处理。一旦定义了合适的切入点和加强处理,AOP 框架将会自动生成 AOP 代理,而 AOP 代理的方法大体有以下公式:
代理对象的方法 = 加强处理 + 被代理对象的方法
在上面这个业务定义中,不难发现 Spring AOP 的实现原理其实很简单:AOP 框架负责动态地生成 AOP 代理类,这个代理类的方法则由 Advice 和回调目标对象的方法所组成。
对于前面提到的图 2 所示的软件调用结构:当方法 一、方法 二、方法 3 ……都须要去调用某个具备“横切”性质的方法时,传统的作法是程序员去手动修改方法 一、方法 二、方法 3 ……、经过代码来调用这个具备“横切”性质的方法,但这种作法的可扩展性很差,由于每次都要改代码。
因而 AOP 框架出现了,AOP 框架则能够“动态的”生成一个新的代理类,而这个代理类所包含的方法 一、方法 二、方法 3 ……也增长了调用这个具备“横切”性质的方法——但这种调用由 AOP 框架自动生成的代理类来负责,所以具备了极好的扩展性。程序员无需手动修改方法 一、方法 二、方法 3 的代码,程序员只要定义切入点便可—— AOP 框架所生成的 AOP 代理类中包含了新的方法 一、访法 二、方法 3,而 AOP 框架会根据切入点来决定是否要在方法 一、方法 二、方法 3 中回调具备“横切”性质的方法。
简而言之:AOP 原理的奥妙就在于动态地生成了代理类,这个代理类实现了图 2 的调用——这种调用无需程序员修改代码。
接下来介绍的 CGLIB 就是一个代理生成库,下面介绍如何使用 CGLIB 来生成代理类。
使用 CGLIB 生成代理类
CGLIB(Code Generation Library),简单来讲,就是一个代码生成类库。它能够在运行时候动态是生成某个类的子类。
此处使用前面定义的 Chinese 类,如今改成直接使用 CGLIB 来生成代理,这个代理类一样能够实现 Spring AOP 代理所达到的效果。
下面先为 CGLIB 提供一个拦截器实现类:
清单 12.AroundAdvice.java
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
AroundAdvice
implements
MethodInterceptor
{
public
Object
intercept
(
Object
target
,
Method
method
,
Object
[
]
args
,
MethodProxy
proxy
)
throws
java
.
lang
.
Throwable
{
System
.
out
.
println
(
"执行目标方法以前,模拟开始事务 ..."
)
;
// 执行目标方法,并保存目标方法执行后的返回值
Object
rvt
=
proxy
.
invokeSuper
(
target
,
new
String
[
]
{
"被改变的参数"
}
)
;
System
.
out
.
println
(
"执行目标方法以后,模拟结束事务 ..."
)
;
return
rvt
+
" 新增的内容"
;
}
}
|
上面这个 AroundAdvice.java 的做用就像前面介绍的 Around Advice,它能够在调用目标方法以前、调用目标方法以后织入加强处理。
接下来程序提供一个 ChineseProxyFactory 类,这个 ChineseProxyFactory 类会经过 CGLIB 来为 Chinese 生成代理类:
清单 13.ChineseProxyFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
ChineseProxyFactory
{
public
static
Chinese
getAuthInstance
(
)
{
Enhancer
en
=
new
Enhancer
(
)
;
// 设置要代理的目标类
en
.
setSuperclass
(
Chinese
.
class
)
;
// 设置要代理的拦截器
en
.
setCallback
(
new
AroundAdvice
(
)
)
;
// 生成代理类的实例
return
(
Chinese
)
en
.
create
(
)
;
}
}
|
上面粗体字代码就是使用 CGLIB 的 Enhancer 生成代理对象的关键代码,此时的 Enhancer 将以 Chinese 类做为目标类,以 AroundAdvice 对象做为“Advice”,程序将会生成一个 Chinese 的子类,这个子类就是 CGLIB 生成代理类,它可做为 Chinese 对象使用,但它加强了 Chinese 类的方法。
测试 Chinese 代理类的主程序以下:
清单 14.Main.java
1
2
3
4
5
6
7
8
9
10
|
public
class
Main
{
public
static
void
main
(
String
[
]
args
)
{
Chinese
chin
=
ChineseProxyFactory
.
getAuthInstance
(
)
;
System
.
out
.
println
(
chin
.
sayHello
(
"孙悟空"
)
)
;
chin
.
eat
(
"西瓜"
)
;
System
.
out
.
println
(
chin
.
getClass
(
)
)
;
}
}
|
运行上面主程序,看到以下输出结果:
执行目标方法以前,模拟开始事务 …
— 正在执行 sayHello 方法 —
执行目标方法以后,模拟结束事务 …
被改变的参数 Hello , CGLIB 新增的内容
执行目标方法以前,模拟开始事务 …
我正在吃 : 被改变的参数
执行目标方法以后,模拟结束事务 …
class lee.Chinese EnhancerByCGLIB 4bd097d9
从上面输出结果来看,CGLIB 生成的代理彻底能够做为 Chinese 对象来使用,并且 CGLIB 代理对象的 sayHello()、eat() 两个方法已经增长了事务控制(只是模拟),这个 CGLIB 代理其实就是 Spring AOP 所生成的 AOP 代理。
经过程序最后的输出,不难发现这个代理对象的实现类是 lee.Chinese EnhancerByCGLIB 4bd097d9,这就是 CGLIB 所生成的代理类,这个代理类的格式与前面 Spring AOP 所生成的代理类的格式彻底相同。
这就是 Spring AOP 的根本所在:Spring AOP 就是经过 CGLIB 来动态地生成代理对象,这个代理对象就是所谓的 AOP 代理,而 AOP 代理的方法则经过在目标对象的切入点动态地织入加强处理,从而完成了对目标方法的加强。
小结
AOP 普遍应用于处理一些具备横切性质的系统级服务,AOP 的出现是对 OOP 的良好补充,它使得开发者能用更优雅的方式处理具备横切性质的服务。不论是那种 AOP 实现,不管是 AspectJ、仍是 Spring AOP,它们都须要动态地生成一个 AOP 代理类,区别只是生成 AOP 代理类的时机不一样:AspectJ 采用编译时生成 AOP 代理类,所以具备更好的性能,但须要使用特定的编译器进行处理;而 Spring AOP 则采用运行时生成 AOP 代理类,所以无需使用特定编译器进行处理。因为 Spring AOP 须要在每次运行时生成 AOP 代理,所以性能略差一些。
CGLIB实现动态代理原理:
原文:http://blog.jobbole.com/105423/?utm_source=blog.jobbole.com&utm_medium=relatedPosts
jdk中的动态代理经过反射类Proxy
和InvocationHandler
回调接口实现,要求委托类必须实现一个接口,只能对该类接口中定义的方法实现代理,这在实际编程中有必定的局限性。
使用cglib[Code Generation Library]实现动态代理,并不要求委托类必须实现接口,底层采用asm字节码生成框架生成代理类的字节码,下面经过一个例子看看使用CGLib如何实现动态代理。
一、定义业务逻辑
public class UserServiceImpl { public void add() { System.out.println("This is add service"); } public void delete(int id) { System.out.println("This is delete service:delete " + id ); } }
二、实现MethodInterceptor
接口,定义方法的拦截器
public class MyMethodInterceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable { System.out.println("Before:" + method); Object object = proxy.invokeSuper(obj, arg); System.out.println("After:" + method); return object; } }
三、利用Enhancer
类生成代理类;
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserServiceImpl.class); enhancer.setCallback(new MyMethodInterceptor()); UserServiceImpl userService = (UserServiceImpl)enhancer.create();
四、userService.add()
的执行结果:
Before: add This is add service After: add
代理对象的生成过程由Enhancer类实现,大概步骤以下:
一、生成代理类Class的二进制字节码;
二、经过Class.forName
加载二进制字节码,生成Class对象;
三、经过反射机制获取实例构造,并初始化代理类对象。
Enhancer是CGLib的字节码加强器,能够方便的对类进行扩展,内部调用GeneratorStrategy.generate
方法生成代理类的字节码,经过如下方式能够生成class文件。
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\\\Code\\\\whywhy\\\\target\\\\classes\\\\zzzzzz")
使用 反编译工具 procyon 查看代理类实现
java -jar procyon-decompiler-0.5.30.jar UserService$$EnhancerByCGLIB$$394dddeb;
反编译以后的代理类add方法实现以下:
import net.sf.cglib.core.Signature;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
//
// Decompiled by Procyon v0.5.30 // public class UserService$$EnhancerByCGLIB$$394dddeb extends UserService implements Factory { private boolean CGLIB$BOUND; private static final ThreadLocal CGLIB$THREAD_CALLBACKS; private static final Callback[] CGLIB$STATIC_CALLBACKS; private MethodInterceptor CGLIB$CALLBACK_0; private static final Method CGLIB$add$0$Method; private static final MethodProxy CGLIB$add$0$Proxy; private static final Object[] CGLIB$emptyArgs; static void CGLIB$STATICHOOK2() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; final Class<?> forName = Class.forName("UserService$$EnhancerByCGLIB$$394dddeb"); final Class<?> forName3; CGLIB$add$0$Method = ReflectUtils.findMethods(new String[] { "add", "()V" }, (forName3 = Class.forName("UserService")).getDeclaredMethods())[0]; CGLIB$add$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "add", "CGLIB$add$0"); } final void CGLIB$add$0() { super.add(); } public final void add() { MethodInterceptor cglib$CALLBACK_2; MethodInterceptor cglib$CALLBACK_0; if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) { CGLIB$BIND_CALLBACKS(this); cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0); } if (cglib$CALLBACK_0 != null) { cglib$CALLBACK_2.intercept((Object)this, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Method, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$emptyArgs, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Proxy); return; } super.add(); } static { CGLIB$STATICHOOK2(); } }
经过cglib生成的字节码相比jdk实现来讲显得更加复杂。
一、代理类UserService$$EnhancerByCGLIB$$394dddeb
继承了委托类UserSevice
,且委托类的final方法不能被代理;
二、代理类为每一个委托方法都生成两个方法,以add方法为例,一个是重写的add方法,一个是CGLIB$add$0方法,该方法直接调用委托类的add方法;
三、当执行代理对象的add方法时,会先判断是否存在实现了MethodInterceptor接口的对象cglib$CALLBACK_0
,若是存在,则调用MethodInterceptor对象的intercept
方法:
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) { System.out.println("Before:" + method); Object object = proxy.invokeSuper(obj, arg); System.out.println("After:" + method); return object; }
参数分别为:一、代理对象;二、委托类方法;三、方法参数;四、代理方法的MethodProxy对象。
四、每一个被代理的方法都对应一个MethodProxy对象,methodProxy.invokeSuper
方法最终调用委托类的add方法,实现以下:
public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } }
单看invokeSuper
方法的实现,彷佛看不出委托类add方法调用,在MethodProxy实现中,经过FastClassInfo维护了委托类和代理类的FastClass。
private static class FastClassInfo { FastClass f1; FastClass f2; int i1; int i2; }
以add方法的methodProxy为例,f1指向委托类对象,f2指向代理类对象,i1和i2分别是方法add和CGLIB$add$0在对象中索引位置。
FastClass其实就是对Class对象进行特殊处理,提出下标概念index,经过索引保存方法的引用信息,将原先的反射调用,转化为方法的直接调用,从而体现所谓的fast,下面经过一个例子了解一下FastClass的实现机制。
一、定义原类
class Test { public void f(){ System.out.println("f method"); } public void g(){ System.out.println("g method"); } }
二、定义Fast类
class FastTest { public int getIndex(String signature){ switch(signature.hashCode()){ case 3078479: return 1; case 3108270: return 2; } return -1; } public Object invoke(int index, Object o, Object[] ol){ Test t = (Test) o; switch(index){ case 1: t.f(); return null; case 2: t.g(); return null; } return null; } }
在FastTest中有两个方法,getIndex
中对Test类的每一个方法根据hash创建索引,invoke
根据指定的索引,直接调用目标方法,避免了反射调用。因此当调用methodProxy.invokeSuper
方法时,其实是调用代理类的CGLIB$add$0
方法,CGLIB$add$0
直接调用了委托类的add方法。
一、jdk动态代理生成的代理类和委托类实现了相同的接口; 二、cglib动态代理中生成的字节码更加复杂,生成的代理类是委托类的子类,且不能处理被final关键字修饰的方法; 三、jdk采用反射机制调用委托类的方法,cglib采用相似索引的方式直接调用委托类方法;