Spring AOP 历险记(一)

Spring AOP 历险记(一)_第1张图片


AOP 简介

在学习 Spring AOP 之前,我们先来了解一下 AOP。我们都听过面向对象编程(OOP),那么 AOP 到底是什么呢?中文意思我想大家应该都是非常的耳熟能详了,中文翻译过来就是面向切面编程,当然也有些人会翻译成面向方面编程,不过还是感觉面向切面编程更好听点。来看下官方的定义:

AOP is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.

翻译过来就是,AOP 是一种编程范式,旨在通过分离横切关注点来增加模块化。它通过向现有代码添加其他行为而不修改现有代码本身来实现。相反,我们可以分别声明这个新代码和这些新行为。

【樱木天亥注】面向对象编程的基本单元是类(class),而面向切面编程的基本单元是切面(aspect)

Spring AOP 是什么

看完了上面 AOP 的定义,那么接下来我们来看下什么是 Spring AOP 呢?

Spring AOP 在 Spring 应用程序实现了面向切面编程。在 AOP 中,这些切面实现了一些关注点的模块化,诸如事务管理日志记录或跨越多种类型和对象的安全性(通常称为横切关注点)。

Spring AOP 的主要术语

大多数的技术都会形成自己的一套术语,Spring AOP 也不例外。而 Spring AOP 最主要的三个术语分别是通知(Advice)切点(Pointcut)连接点(Join point)。如图,展示了这三者是如何联系到一块的。

Spring AOP 历险记(一)_第2张图片
image

  • 建言(Advice)    通知定义了切面是什么以及何时使用,是在方法执行之前或之后要采取的实际操作。Spring 的面向切面编程框架在程序执行期间调用的时间代码段。

  • 切点(Pointcut)    切点是一个与连接点相匹配的表达式,用于确定是否需要执行建言(Advice)。切点有助于确定切面所通知的连接点的范围,它的定义会匹配通知所要织入的一个或多个连接点。切点使用与连接点匹配的不同类型的表达式,在 Spring 框架中使用 Aspect 切点表达式语言(SpEL)。

  • 连接点(Join Point)    连接点在应用程序中是具体的点,例如方法执行,异常处理,改变对象的变量值等。在 Spring AOP 中一个连接点总是一个方法的执行点,即只有方法执行点。

Spring AOP 其他常用的术语

  • 切面(Aspect)    一个切面是跨越多个类的横切关注点的模块化,比如事务管理。切面可以通过 XML 的方式来配置,也可以通过 Spring AspectJ 集成使用 @Aspect 注解来注入切面。切面是通知(Advice)和切点(Pointcut)的结合。通知和切点共同定义了切面饿的全部内容——它是什么,在何时和何处完成了其功能。
  • Introductions    introduction 允许向现有的类添加新的方法或属性。可以将新的方法和实例变量引入现有类而无需改变它们,为它们提供新的状态和行为。Spring AOP 允许我们为目标对象引入新的接口和对应的实现。
  • Target Object    织入 advice 的目标对象,目标对象也被称为 Target object。Spring AOP 是使用运行时代理实现切面的,因此该对象始终是代理对象,指的不是原来的类,而是织入 advice 后所产生的代理类。这意味着在运行时创建子类,其中,覆盖目标方法并根据配置包含建言(advice)。
  • Weaving    将 aspect 和其他对象连接起来, 并创建代理对象(adviced object)的过程。这可以在编译时,加载时或在运行时完成,而 Spring AOP 是在运行时执行编织。

织入(weaving)可以在目标对象的几个不同的生命周期执行:

  • 编译期织入(Compile Time):编译目标类时会织入切面,同时,要求要有一个特殊的编译器。
  • 类装载期织入(Classload Time):当目标类加载到 JVM 中时会织入切面,同时,要求要有一个特殊的类装载器。
  • 运行期织入(Runtime):在应用程序执行期间的某些时间点织入切面。AOP 容器将动态生成代理对象,该代理对象将在织入切面时委托给目标对象。

【樱木天亥注】AspectJ 织入编译器属于编译期织入;AspectJ 5 的装载期织入(LTW,load-time weaving)属于类装载期织入;Spring AOP 织入切面则属于运行期织入。

Spring AOP 的优点

1、AOP 是非侵入性的

  • 允许开发人员更加专注于业务逻辑的开发,而不是受困于横切关注点的问题
  • 类可以通过切面获得建言而无需将 Spring AOP 相关类或接口添加到类中

2、AOP 是通过纯 Java 语言实现的

  • 这意味着我们不需要特殊的编译单元或特殊的类加载器

3、通过 Spring IOC 容器实现依赖注入

4、能够将多个横切关注点织入类中而无需调用这些类的横切关注点

5、能够集中或模块化横切关注点,使得维持和改变这些切点变得容易维护

6、提供了 XML 或 @AspectJ 注解的多种灵活的方式来创建切面

7、易于配置

Spring AOP 的缺点

1、使用基于代理的 AOP,所以仅支持方法级别的建言,不支持属性级别的建言

2、当且仅当方法的可见性为 public 时,才支持建言(Advice)

  • 方法可见性为 private,protected 或者 default 时不支持建言

3、一个切面不能作为另一个切面的 advice target 。

  • 如果一个类被 @Aspect 标注, 则这个类就不能是其他切面的目标对象了, 因为使用 @Aspect 后, 这个类就会被排除在 auto-proxying 机制之外。

4、由于性能问题,建言不适用于细粒度的对象,只适合粗粒度的对象。

建言(advice)的类型

建言(Advice)的类型有一下五种。

  • Before advice 在连接点之前执行,但是不能阻止执行流程进入连接点,即不能人为地在 before advice 代码中阻止 join point 中代码的执行。
  • After returning advice 在连接点正常执行完成后执行,例如,一个方法执行完成不抛异常则执行这个 advice
  • After throwing advice 当一个连接点(join point)抛出异常后退出,则执行该 advice
  • After advice 无论连接点是正常退出还是抛出了异常退出,都会执行这个 advice
  • Around advice 围绕连接点的建言,比如方法调用。这是最常用的 advice,在 join point 执行前和 join point 执行后都执行的 advice。它还负责选择是否继续加入连接点还是返回自身的返回值,或者抛出异常来加速方法的执行。

AOP 代理(proxy)

我们都知道,Spring AOP 底层主要有两种实现,一种是 JDK 动态代理,一种是 CGLIB 动态代理。而 Spring AOP 代理的实现是通过 JDK 动态代理来创建一个具有目标类和通知调用的 Proxy 类,这些类被称为 AOP 代理类。

那么,JDK 动态代理和 CGLIB 动态代理最大不同是什么呢?前者的原理是 JDK 反射,并且只支持 Java 接口的代理;后者的原理是继承(extend)与覆写(override),因此能支持普通的 Java 类的代理。两种方式都是动态代理,即运行时实时生成代理。但是,由于JVM 的限制,CGLIB 无法替换被代理类已经被载入的字节码,只能生成并载入一个新的子类作为代理类,被代理类的字节码依然存在于JVM 中。

Spring AOP 示例

两种声明方式:

1、XML 方式



2、Java 配置的方式

//使用 Java 配置的方式,启用 AspectJ 风格的 Spring AOP
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

定义切面(Aspect)

@Component
@Aspect
public class EmployeeAspect {
    @Before("execution(* EmployeeManager.getEmployeeById(..))") //point-cut expression
    public void logBefore(JoinPoint joinPoint) {
        ……
    }
}

【樱木天亥注】仅仅使用 @Aspect 注解, 并不能将一个 Java 对象转换为 Bean, 因此我们还需要使用类似 @Component 之类的注解。

切点表达式

@Before("execution(* EmployeeManager.getEmployeeById(..))") //point-cut expression
    public void logBeforeAdvice(JoinPoint joinPoint) {
        ……
    }

【樱木天亥注】
1、execution( EmployeeManager.getEmployeeById(..)) 这个切点表达式表示这个切点将会匹配 EmployeeManager 类下面所有的 getEmployeeById 方法,而不管它的参数类型和数量是什么。
2、@Before 表示当前的 Advice 是在连接点(Join point)之前执行。

连接点(Join point)

//@Component
public class EmployeeManager
{
    public EmployeeDTO getEmployeeById(Integer employeeId) {
        System.out.println("Method getEmployeeById() called");
        return new EmployeeDTO();
    }
}

【樱木天亥注】如果前面定义切面(Aspect)时没有添加 @Component 注解,则这里应该要加上,不然可能不能将一个 Java 对象转换为 Bean。

声明建言(Advice)

Advice 与切点表达式紧密关联,会在匹配的连接点执行前,执行后或者周围(around)执行。切点表达式既可以是简单的一个 pointcut 名字的引用, 又可以是完整的 pointcut 表达式。这里简单的举几个例子说明如下。

Around advice

@Around("execution(* EmployeeManager.getEmployeeById(..))")
    public Object logAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
        ……
    }

Before advice

@Before("execution(* EmployeeManager.getEmployeeById(..))") //point-cut expression
    public void logBeforeAdvice(JoinPoint joinPoint) {
        ……
    }



参考来源
1、Introduction to Spring AOP
2、Spring AOP Tutorial Example
3、Spring AOP Example Tutorial – Aspect, Advice, Pointcut, JoinPoint, Annotations, XML Configuration
4、Overview of Spring Aspect Oriented Programming (AOP)
5、Spring AOP 是什么?
6、彻底征服 Spring AOP 之 理论篇
7、《Spring实战(第 4 版)》

你可能感兴趣的:(Spring AOP 历险记(一))