Spring AOP and AspectJ

Spring AOPAspectJJava中最流行的 AOP 框架

AOP

AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理动态代理两大类,其中静态代理是指在编译阶段修改目标类字节码,属于静态编入,不需要代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。其中aspectj为静态代理,Spring AOP是动态代理

AOP核心概念

  • Advice:通知,是指拦截到jointpoint之后所要做的事情即特定的Jointpoint处运行的代码。
  • JoinPoint:连接点,它定义在哪里(哪些点)加入你的逻辑功能,基本每个方法的前后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点
  • PointCut:切入点的集合,即一组Joinpoint,就是说一个Advice可能在多个地方织入。比如一个类里,有15个方法,那就有几十个连接点了,让切点来筛选连接点,选中那几个你想要的方法。
  • Aspect:切面,实际是通知和切入点的组合,通知说明了干什么和什么时候干(什么时候通过方法名中的beforeafteraround等),而切入点说明了在哪干(指定到底是哪个方法)。
  • Weaving:织入,把切面应用到目标对象来创建新的代理对象的过程。

Aspectj

ApectJ主要采用的是编译期织入,在这个期间使用AspectJacj编译器把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

img

举例

安装 AspectJ 见:https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/index.html

  • 业务逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.zsr.test.aspectj;
public class HelloWorld {
// 模拟业务逻辑
public void sayHello() {
System.out.println("Hello AspectJ!");
}
public static void main(String[] args) {
HelloWorld h = new HelloWorld();
h.sayHello();
}
}
  • 切面Aspect

使用AspectJ编写一个Aspect

1
2
3
4
5
6
7
8
9
10
package com.zsr.test.aspectj;
public aspect TestAspect {
//指定执行 Hello.sayHello() 方法时执行下面代码块
void around():call(void HelloWorld.sayHello()){
System.out.println("开始事务 ...");
proceed();
System.out.println("事务结束 ...");
}
}
  • ajc编译
1
ajc -d . HelloWorld.java TestAspect.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// HelloWorld.class 反编译
public class HelloWorld
{
public void sayHello()
{
System.out.println("Hello AspectJ!");
}
private static final void sayHello_aroundBody1$advice(HelloWorld target, TestAspect ajc$aspectInstance, AroundClosure ajc$aroundClosure)
{
System.out.println("开始事务 ...");
AroundClosure localAroundClosure = ajc$aroundClosure;
sayHello_aroundBody0(target);
System.out.println("事务结束 ...");
}
public static void main(String[] args)
{
HelloWorld h = new HelloWorld();
HelloWorld localHelloWorld1 = h;
sayHello_aroundBody1$advice(localHelloWorld1, TestAspect.aspectOf(), null);
}
private static final void sayHello_aroundBody0(HelloWorld paramHelloWorld)
{
paramHelloWorld.sayHello();
}
}

可以发现切面代码织入了HelloWorld.class

1
2
3
4
5
6
7
8
9
10
// TestAspect.class 反编译
public class TestAspect
{
public void ajc$around$com_zsr_test_aspectj_TestAspect$1$8852f95(AroundClosure ajc$aroundClosure)
{
System.out.println("开始事务 ...");
ajc$around$com_zsr_test_aspectj_TestAspect$1$8852f95proceed(ajc$aroundClosure);
System.out.println("事务结束 ...");
}
}

Aspect编译后的class文件可以更明显的看出执行的逻辑。proceed方法就是回调执行被代理类中的方法。

  • 执行结果
1
2
3
4
5
6
7
➜ aspectj java com.zsr.test.aspectj.HelloWorld
开始事务 ...
Hello AspectJ!
事务结束 ...
➜ aspectj pwd
/Users/nali/Documents/workspace/test/src/main/java/com/zsr/test/aspectj
➜ aspectj

Spring AOP

Spring AOP使用的是动态代理增强,动态代理不会改变类的字节码,而是动态的生成代理对象。Spring AOP的动态代理机制有两种方式:JDK动态代理CGLIB动态代理

  • JDK动态代理

    JDK动态代理需要被代理的类必须实现一个接口,底层通过反射实现

  • CGLIB动态代理

    CGLIB动态代理可以不用需要被代理类必须实现接口,底层通过继承实现,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的

img

创建AOP代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// DefaultAopProxyFactory.java
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 如果目标类没有实现接口或者proxy-target-class="true"则采用CGLIB代理
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException()
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}

如果配置了<aop:aspectj-autoproxy proxy-target-class="true"/>或者目标类没有实现接口,则使用CGLIB代理;如果目标类实现了接口,默认使用JDK代理

Spring AOP vs ApectJ

Spring AOPApectJ 的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是:

  1. Spring AOP 并不尝试提供完整的AOP功能,Spring AOP 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题,因此在AOP的功能完善方面,相对来说AspectJ具有更大的优势。
  2. AspectJAOP的实现方式上依赖于特殊编译器(ajc编译器),而Spring回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别

注意:

Spring 2.0后便使用了与AspectJ一样的注解,新增了对AspectJ切点表达式支持。需要注意Spring 只是使用了与 AspectJ 一样的注解,但仍然没有使用 AspectJ 的编译器,底层仍然是动态代理技术的实现

参考

Spring AOP的实现原理

Comparing Spring AOP and AspectJ

AOP概念,原理,应用介绍

Spring AOP (三) AspectJ

热评文章