作者 | GitChat
责编 | 郭芮
出品 | CSDN 博客
AOP(Aspect Oriented Programming)面向切面编程是 Spring 框架最核心的组件之一,它通过对程序结构的另一种考虑,补充了 OOP(Object-Oriented Programming)面向对象编程。在 OOP 中模块化的关键单元是类,而在 AOP 中,模块化单元是切面。也就是说 AOP 关注的不再是类,而是一系列类里面需要共同能力的行为。
本文内容主要包括:
讲解 OOP 与 AOP 的简单对比,以及 AOP 的基础名词,比如面试中经常会被问到的 point cut、advice、join point 等。
讲解面试经常会被问到的 JDK 动态代理和 CGLIB 代理原理以及区别。
讲解 Spring 框架中基于 Schema 的 AOP 实现原理。
讲解 Spring 框架中如何基于 AOP 实现的事务管理。
AOP 基础概念
我们知道在 OOP 中模块化的关键单元是类,类封装了一类对象的行为和状态,当多个类有共同的属性和行为时候我们把这些共同的东西封装为一个基类,然后多个类可以通过继承基类的方式来复用这些共同的东西,如果子类需要定制基类行为则可以使用多态。OOP 中使用类来提供封装,继承,多态三个特性。
当我们需要在多个不相关的类的某些已有的行为里面添加一个共同的非业务逻辑时候,比如我们需要统计一些业务方法的执行耗时时候,以往做法需要在统计耗时的行为里面写入计算耗时的代码,在 OOP 里面这种不涉及业务的散落在多个类的行为里面的代码叫做横切(Cross-cutting)代码,OOP 中这种方式缺点一是业务逻辑行为受到了计算耗时代码干扰(业务逻辑行为应该只专注业务),缺点二是计算耗时的代码不能被复用。
而在 AOP 中模块化单元是切面(Aspect),它将那些影响多个类的共同行为封装到可重用的模块中,然后你就可以决定在什么时候对哪些类的哪些行为执行进行拦截(切点),并使用封装好的可重用模块里面的行为(通知)对其拦截的业务行为进行功能增强,而不需要修改业务模块的代码,切面就是对此的一个抽象描述。
AOP 中有以下基础概念:
Join point(连接点):程序执行期间的某一个点,例如执行方法或处理异常时候的点。在 Spring AOP 中,连接点总是表示方法的执行。
Advice(通知):通知是指一个切面在特定的连接点要做的事情。通知分为方法执行前通知,方法执行后通知,环绕通知等。许多 AOP 框架(包括 Spring)都将通知建模为拦截器,在连接点周围维护一系列拦截器(形成拦截器链),对连接点的方法进行增强。
Pointcut(切点):一个匹配连接点(Join point)的谓词表达式。通知(Advice)与切点表达式关联,并在切点匹配的任何连接点(Join point)(例如,执行具有特定名称的方法)上运行。切点是匹配连接点(Join point)的表达式的概念,是AOP的核心,并且 Spring 默认使用 AspectJ 作为切入点表达式语言。
Aspect(切面):它是一个跨越多个类的模块化的关注点,它是通知(Advice)和切点(Pointcut)合起来的抽象,它定义了一个切点(Pointcut)用来匹配连接点(Join point),也就是需要对需要拦截的那些方法进行定义;它定义了一系列的通知(Advice)用来对拦截到的方法进行增强;
Target object(目标对象):被一个或者多个切面(Aspect)通知的对象,也就是需要被 AOP 进行拦截对方法进行增强(使用通知)的对象,也称为被通知的对象。由于在 AOP 里面使用运行时代理,所以目标对象一直是被代理的对象。
AOP proxy(AOP 代理):为了实现切面(Aspect)功能使用 AOP 框架创建一个对象,在 Spring 框架里面一个 AOP 代理要么指 JDK 动态代理,要么指 CgLIB 代理。
Weaving(织入):是将切面应用到目标对象的过程,这个过程可以是在编译时(例如使用 AspectJ 编译器),类加载时,运行时完成。Spring AOP 和其它纯 Java AOP 框架一样,是在运行时执行植入。
Advisor:这个概念是从 Spring 1.2的 AOP 支持中提出的,一个 Advisor 相当于一个小型的切面,不同的是它只有一个通知(Advice),Advisor 在事务管理里面会经常遇到,这个后面会讲到。
相比 OOP,AOP 有以下优点:
业务代码更加简洁,例如当需要在业务行为前后做一些事情时候,只需要在该行为前后配置切面进行处理,无须修改业务行为代码。
切面逻辑封装性好,并且可以被复用,例如我们可以把打日志的逻辑封装为一个切面,那么我们就可以在多个相关或者不相关的类的多个方法上配置该切面。
JDK 动态代理和 CGLIB 代理原理、区别
在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类,本节就简单来介绍这两种代理的原理和区别。
JDK 动态代理
由于 JDK 代理是对接口进行代理,所以首先写一个接口类:
public interface UserServiceBo {
public int add;
}
然后实现该接口如下:
public class UserServiceImpl implements UserServiceBo {
@Override
public int add {
System.out.println("--------------------add----------------------");
return 0;
}
}
JDK 动态代理是需要实现 InvocationHandler 接口,这里创建一个 InvocationHandler 的实现类:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
super;
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//(1)
System.out.println("-----------------begin "+method.getName+"-----------------");
//(2)
Object result = method.invoke(target, args);
//(3)
System.out.println("-----------------end "+method.getName+"-----------------");
return result;
}
public Object getProxy{
//(4)
return Proxy.newProxyInstance(Thread.currentThread.getContextClassLoader, target.getClass.getInterfaces, this);
}
}
建立一个测试类:
public static void main(String[] args) {
//(5)打开这个开关,可以把生成的代理类保存到磁盘
System.getProperties.put("sun.misc.ProxyGenerator.saveGeneratedFiles