如何理解AOP

是什么

AOP即面向切面编程,是一种思想,是对OOP的补充。简单的来说就是把公共的逻辑抽离出来,

让开发者可以跟关注于业务逻辑的开发

说简单点AOP就是 在不惊动原有的代码的基础上对功能进行增强操作

概念:

连接点:JIONPoint,可以被AOP控制的方法(运行时刻被拦截到的方法

通知:advice 增强,切面的具体行为

切入点:决定在哪些方法上生效

切面:描述切入点和通知的关系

通知类型:

@Before

@After

@AfterReturning

@AfterThrowing 在抛错之后

@Aroud 环绕通知:在目标方法执行之前和之后都能插入逻辑,甚至可以决定是否执行目标方法。

总结:

AOP的底层实现方法

一共有三种吧,常见的像代理,还有AspectJ框架提供的两种方法

第一种就是AJC增强,它就是指AJC编译器在编译过程中把通知的增强功能植入目标类的Class文件中

来达到增强的效果,然后它的执行时机就是通知类上有Aspect注解,不需要component注解,该通知类

就没有被Spring所管理。所有AOP的执行底层是通过AJC去修改Class文件,而不是动态代理,这时候没有

Spring容器也可以进行aop增强,但需要用到maven一个插件,这种方式用得很少

第二宗就是通过agent底层来实现aop,它是agent编译器在加载目标类的时候,因为是在加载类的时候么,

Class文件已经编译完了,它就在加载目标类的时候修改对应的Class文件,达到增强的功能。它的执行时机

跟AJC编译差不多的,也是通知类上有Aspect注解,不需要component注解。区别就是给JVM加一个参数 -

javaagent,后面指定maven目录下的一个jar包路径

第三种就是我们常用的代理增强了。代理的本质就是生成一个代理对象,类似于房东和中介的,中介就是代理

这个代理对象和切入点有着相同的方法,然后代理对象的方法就是根据需求增强过的,然后调用切入点的方法

不走它的方法,而是走代理对象在增强过的方法。而怎么保证代理对象和切入点有着相同的方法呢,这就分为

两派,一种是jdk代理,一种是cglib代理。

jdk代理是Java自带的代理,主要是采用了多态和反射的方式。它是代理对象和目标类实现同一个接口,就可

以保证两者都有同样的方法

jdk代理通过Proxy.newProxyInstance()获取到代理对象

newProxyInstance方法的三个参数:

第一个参数,类加载器

第二个参数,接口集合,因为jdk代理需要目标对象实现接口

第三个参数,一个函数式接口,实现增强方法逻辑,这个函数式接口也有三个参数:

第一个参数,目标对象

第二个参数,需要被增强的方法

第三个参数,方法执行的参数

proxy.newProxyInstance()方法底层会根据传入的接口集合,在运行时在本类目录下生成一个$proxy0类

实现这些方法,然后里面的所有方法都会被InvocationHandler方法给拦截下来进行增强(包括equals和hashcode)

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
29
30
public final class $Proxy0 extends Proxy implements Test { 
private static Method m1;
private static Method m2;
private static Method m3;

// $Proxy0 类的构造方法
// 参数为 invocationHandler
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}

static {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("com.xxx.Test").getMethod("test", new Class[0]);
}

public final void test() {
this.h.invoke(this, m3, null);
return;
}

public final int hashCode() {
return this.h.invoke(this, m2, null);
}

public final boolean equals(Object o) {
return this.h.invoke(this, m1, new Object[]{o});
}
}

cglib代理不是jdk自带的代理,需要导外部依赖,其底层是让代理类继承目标类,通过Enhancer.create获取代理对象

create方法的两个参数:

第一个参数:目标类

第二个参数:一个函数式接口,实现增强方法逻辑,这个函数式接口有四个参数:

第一个参数,代理对象自己

第二个参数,需要被增强的方法

第三个参数,方法执行的参数

第四个参数,MethodProxy,代理不走反射的关键

其实AOP的重点是如何获取目标类的原始方法,并把它放到特定的上下文环境下执行

对于这一点,jdk代理采用的是反射,你看,我都采用反射了,我还拿不到你原始方法吗

而cglib采用的就是继承,我都继承了,我肯定能通过super.xx拿到你的原始方法

还有就是,代理模式则是在运行期生成新的字节码。AspectJ对代码的侵入性很强,因此没有怎么流行 但因为它是在编译器和类加载期修改字节码,故性能较高。且能突破代理只能重写方法来实现增强的限 制,如能增强构造方法,静态方法,final修饰的方法

Spring的AOP是怎么实现的

Spring的AOP是采用动态代理实现的

至于是jdk还是cglib,Spring容器代理时,会有一个proxyBean类,这个类会传入目标对象和通知

,然后这个类还有一个boolean proxyTargetClass,如果设置为false,那就会去检查目标类是否实现了

接口,有就用jdk,无就用cglib。如果为true,那就直接用cglib

一个方法被多个切面AOP了?

需要给切面标明@Order(x)x越小,说明你那个优先级越高。

如果无标明,那就会按注册的顺序来执行,但此时是随机的,不可知的

因为 Spring AOP 内部会把所有的切面包装成 Advisor,然后放到一个集合里。虽然这个集合是个List,但

虽然用的是 ArrayList(顺序表),但插入顺序本身没保证稳定,因为:

- 不同 BeanFactory 扫描/注册 Bean 的顺序可能不同。
- 不同 JDK、不同文件系统,class 扫描顺序也可能不同。
- 所以“默认顺序”没有规范,才会被说成是 **不可知的**。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before() {
System.out.println("LogAspect before");
}
}

@Aspect
@Component
public class SecurityAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before() {
System.out.println("SecurityAspect before");
}
}
//执行顺序不一定的



AOP失效?

当然这只限于代理类的

类中自调用方法,同一个类中A调用B方法,B方法的AOP不会生效

调用内部类方法

调用静态方法,静态方法不会被AOP

调用final方法,final方法不会被AOP

目标类不符合规则,如jdk代理时没继承接口,cglib代理时是final类

cglib 和jdk代理哪个快

在 JDK1.6、JDK1.7、JDK1.8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 CGLib 代理效率,只有当进行大量调用的时候,JDK1.6 和 JDK1.7 比 CGLib 代理效率低一点,但是到 JDK1.8 的时候,JDK 代理效率高于 CGLib 代理。所以如果有接口使用 JDK 动态代理,如果没有接口使用 CGLIB 代理。