Spring AOP

Spring AOP

文章目录

1.概念

AOP(Aspect Oriented Programming) ,直译过来即为面向切面编程。AOP 是一种编程思想,是面向对象编程OOP的一种补充,提供了与 OOP 不同的抽象软件结构的视角。在 OOP 中,我们以类(class)作为我们的基本单元,而 AOP 中的基本单元是切面(Aspect)。好比下图,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。

image-20230221133831493

1.面向切面编程

在面向切面编程的思想里面,可以把功能分为两种:

  • 核心业务:登陆、注册、增、删、改、查等,都叫核心业务
  • 周边功能:日志、事务管理等为周边业务

在面向切面编程中,核心业务功能和周边功能是分别独立进行开发,两者不是耦合的。然后把切面功能和核心业务功能 “编织”在一起,便谓之AOP。

2.AOP的目的

AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。即,AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

在技术上,AOP可以在程序运行期间,在不修改源码的情况下对方法进行功能增强,如此一来,不难理解 AOP 其实就是代理模式的典型应用。

3.AOP实现的分类

按照 AOP 修改源代码的时机,可以将其分为两类:

  • 静态 AOP 实现:AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器)。例如 AspectJ。
  • 动态 AOP 实现: AOP 框架在运行阶段动态生成代理对象(在内存中以 JDK 或 CGlib 动态代理动态地生成 AOP 代理类)。如 SpringAOP。

SpringAOP:

  • JDK 代理:基于接口的动态代理技术。
  • cglib 代理 :基于父类的动态代理技术。

常用AOP实现比较:

类别 机制 原理 优点 缺点
静态AOP 静态织入 在编译期,切面直接以字节码的形式编译到目标字节码文件中 对系统无性能影响 灵活性不足
动态AOP JDK动态代理 在运行期,目标类加载后,为接口动态生成代理类,将切面织入到代理类中 相对于静态AOP更加灵活 1) 切入的关注点需要实现接口; 2) 对系统有一点性能损耗
动态字节码生成 CGLIB 在运行期,目标类加载后,动态生成目标类的子类,将切面逻辑加入到子类中 没有接口也可以织入 扩展类的实例方法用final修饰时,则无法进行织入
自定义类加载器 在运行期,目标类加载前,将切面逻辑加到目标字节码里 可以对绝大部分类进行织入 代码中如果使用了其他类加载器,则这些类将不会织入
字节码转换 在运行期,所有类加载器加载字节码前进行拦截 可以对所有类进行织入

4.AOP 术语

Unfortunately, AOP terminology is not particularly intuitive.

谓词(predicate):在计算机语言的环境下,谓词是指条件表达式的求值返回真或假的过程。

术语 中文 含义
Aspect 切面 横跨多个类的某个功能(如事务管理)。从概念上,它是切点和增强的结合。
Join point 联结点 程序执行过程中的一个时机(点)。例如方法的调用、异常的抛出。 在 Spring AOP 中,联结点总是方法的调用。
Advice 增强 在某个联结点上,某个切面执行的具体动作(这里对advice译为增强,为意译)。 很多AOP框架(包括Spring)都把增强建模为一个拦截器,并且相应地围绕联结点维护了一个拦截器链。
Pointcut 切点 一个用来匹配联结点的谓词。 Advice is associated with a pointcut expression and runs at any join point matched by the pointcut. For example, you could use an introduction to make a bean implement an IsModified interface, to simplify caching.
Introduction 引入 引入是为一个类声明额外的方法或字段。 Spring AOP lets you introduce new interfaces (and a corresponding implementation) to any advised object.
Target object 目标对象 要被切面增强的(原始)对象,也常称作advised object(增强对象,这种称呼并不好,有歧义)。 由于Spring是基于运行时代理的机制实现AOP的,因此目标对象总是一个proxied object(被代理的对象)。
AOP Proxy AOP代理 AOP为实现切面功能而创建的对象,故名之代理对象。 在Spring中,代理对象总是一个JDK代理或CGLIB代理对象。
Weaving 织入 描述了把增强处理添加到目标对象、并创建一个被增强的对象(代理)这一过程,不对应一份实体。 1) 织入可以发生在编译时、加载时、运行时 2) SpringAOP的织入总是发生在运行时。

增强的类型:

  • 前置增强
  • 返回后增强
  • 异常抛出增强
  • 后置增强
  • 环绕增强

2. Spring AOP的特性

1.能力与目标

Spring AOP does not need to control the class loader hierarchy and is thus suitable for use in a servlet container or application server.

Spring AOP当前只支持对Spring Bean的方法作为联结点,不支持对字段的拦截(尽管对字段拦截的支持不需要破坏Spring AOP的核心API)。如果希望拦截对字段的访问与更新,建议直接使用AspectJ。

SpringAOP使用时需要结合SpringIoC容器,因此SpringAOP无法对非常细粒度的对象进行增强,典型的例子就是domain objects,对于这些场景,选择AspectJ吧。

2.AOP机制

AOP技术在Spring中实现的内容:Spring框架监控切点方法的执行,一旦监控到切入点方法被运行,即使用动态代理机制,动态创建目标对象的代理对象,根据增强类别在代理对象的相应位置将Advice对应的功能织入,从而完成增强后的整个代码逻辑的执行。

Spring 的 AOP 实现底层就是对 JDK 代理、cglib 代理的方式进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。

image-20230221155026617

  • JDK 代理:基于接口的动态代理技术。
  • cglib 代理 :基于父类的动态代理技术。

默认情况下,Spring 会根据目标类是否实现了接口来决定采用哪种动态代理的方式。如果一个对象没有实现任何接口,则会使用CGLIB代理,否则使用JDK代理。

  • 当使用JDK代理的时候,所有该目标对象实现的接口都会被代理;

  • 如果需要,也可以强制使用CGLIB代理,方法是设置proxy-target-classtrue

    <aop:aspectj-autoproxy proxy-target-class="true"/>copyerrorcopied
    

Spring使用CGLIB代理时需注意如下事项:

  • final方法无法被增强,因为它们无法被在运行时生成的子类所覆盖;

  • 正常情况下,CGLIB代理是通过Objenesis创建的,但当JVM不允许绕过构造函数时,SpringAOP会对构造器进行双重调用来达成目的,此时Spring会记录相应的debug日志信息。

    Objenesis是一个轻量的Java库,作用是绕过构造器创建实例。

1.理解SpringAOP的代理

Spring AOP是基于代理的,牢记这一点很重要,这是本质特征!

假定有一个纯天然的POJO类:

public class SimplePojo implements Pojo {
   

    public void foo() {
   
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
   
        // some logic...
    }
}

对于POJO类的实例pojo,调用pojo的方法时毫无疑问会直接调用该对象的相应方法:

public class Main {
   

    public static void main(String[] args) {
   
        Pojo pojo = new SimplePojo();
        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}

image-20230221155511061

然而,如果pojo引用的是代理类的代理对象时,调用方式会发生改变:

public class Main {
   

    public static void main(String[] args) {
   
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

image-20230221155807299

However, once the call has finally reached the target object (the SimplePojo reference in this case), any method calls that it may make on itself.

  • Such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy.
  • It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to run.

综上,最好的方式永远是代码中不要出现有自调用的情况。其次,如果真的迫不得已,可以通过在代码中使用Spring提供的一些API来解决,如下:

这种方案首先使得代码与Spring发生了强耦合,其次使得这个类本身知道了自己即将被代理,后者与AOP的理念背道而驰了。

public class SimplePojo implements Pojo {
   

    public void foo() {
   
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
   
        // some logic...
    }
}
public class Main {
   

    public static void main(String[] args) {
   
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);

        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值