Spring AOP 系列的第一篇

先介绍一下 AOP 相干的一些概念。

呈现的契机

在事实中、咱们常常须要记录重要操作的流水以及打印相干的日志到日志文件

// 微信公众号:CoderLipublic class BizService01 {    public void dealBiz(BizDto bizDto) {       // 脱敏打印 + 统计上报到经营零碎        record(bizDto);        // 业务操作    }  private void record(BizDto bizDto){    // .....  }}

当存在 n 多个这样的 Service 的时候、咱们进一步的操作可能是:将其抽取到一个公共的中央进行记录保护

// 微信公众号:CoderLipublic class BizService01 {    public void dealBiz(BizDto bizDto) {        // 脱敏打印 + 统计上报到经营零碎        RecordUtils.record(bizDto);        // 业务操作    }    }

再进一步、咱们可能应用模板办法来设计。子类继承该根底服务

// 微信公众号:CoderLipublic abstract class BaseBizService {    public void dealBiz(BizDto bizDto){        // 脱敏打印 + 统计上报到经营零碎        if (isRecord()) {            RecordUtils.record(bizDto);        }        readDealBiz(bizDto);    }    protected abstract void readDealBiz(BizDto bizDto);    protected boolean isRecord(){        return true;    }}

这样子貌似是能解决对立解决非核心业务、然而如果咱们还须要进行其余的一些非核心业务解决的时候、比如说、权限测验、性能监控等、并且这些非核心业务他们的程序、在每一个业务场景中、程序可能不一样、是否执行这些非核心业务也是不确定的、那么单纯靠模板办法来解决的话、会显得十分费劲、甚至能够说、这种变动的流程不适宜模板办法了曾经。

这个时候 AOP 呈现了。而第一个比拟风行的 Java AOP 框架就是 AspectJ 了。

AspectJ 应用

应用 Idea 来演示一个 Hello World 程序。确保曾经装置并且启用了 AspectJ 插件

应用 Ajc 编译器、并配置 AspectJTools jar 的门路

我的项目 pom 依赖

<!-- 微信公众号:CoderLi--><dependency>    <groupId>org.aspectj</groupId>    <artifactId>aspectjrt</artifactId>    <version>1.9.7</version></dependency>
public class Main {    // 微信公众号:CoderLi    void sayHi(){        System.out.println("hello world");    }    public static void main(String[] args) {        Main main = new Main();        main.sayHi();    }}
// 切面。微信公众号:CoderLipublic aspect MainAspect {    // 切点    pointcut sayHiPointcut():call(* Main.sayHi());    // 前置告诉    before():sayHiPointcut(){        System.out.println("say Hi Before");    }    // 后置告诉    after ():sayHiPointcut(){        System.out.println("say Hi After");    }}

咱们应用了关键字 aspect 创立了一个切面。在这个切面外面、咱们应用了 pointcut 定义了一个切点。而所谓的切点就是切面须要利用的办法、而这些办法咱们称之为指标办法。在这个切面外面咱们还定义了两个告诉、一个是指标办法执行前的告诉、一个是执行后的告诉。

切面就是切点和告诉的组合体。

咱们看下 Main class 反编译的内容

public static void main(String[] args) {    Main main = new Main();    Main var10000 = main;    try {      // 前置告诉        MainAspect.aspectOf().ajc$before$com_demo_aspectj_MainAspect$1$acd0869f();        var10000.sayHi();// 指标办法    } catch (Throwable var3) {      // 后置告诉        MainAspect.aspectOf().ajc$after$com_demo_aspectj_MainAspect$2$acd0869f();        throw var3;    }        // 后置告诉    MainAspect.aspectOf().ajc$after$com_demo_aspectj_MainAspect$2$acd0869f();}

后置告诉在 catch 代码块中、确保异样的时候也能被告诉到。

告诉的类型:

  1. 前置告诉
  2. 后置告诉
  3. 异样告诉
  4. 后置返回告诉
  5. 盘绕告诉

相干概念

  • 连接点:程序执行的某个特定地位,比方某个办法调用前、调用后,办法抛出异样后,对类成员的拜访以及异样处理程序块的执行等。一个类或一段程序代码领有一些具备边界性质的特定点,这些代码中的特定点就是连接点。它本身还能够嵌套其余的 Joinpoint。AOP 中的 Joinpoint 能够有多种类型:构造方法调用,字段的设置和获取,办法的调用,办法的执行,异样的解决执行,类的初始化。Spring 仅反对办法执行类型的 Joinpoint。
  • 切点:如果连接点相当于数据中的记录,那么切点相当于查问条件,一个切点能够匹配多个连接点。所以切点示意一组 Joinpoint ,这些 Jointpoint 或是通过逻辑关系组合起来,或是通过通配、正则表达式等形式集中起来,它定义了相应的 Advice 将要产生的中央。
  • 告诉:是织入到指标类连接点上的一段程序代码。Spring 提供的告诉接口都是带方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice 等。咱们通过 AOP 将横切关注性能加到原有的业务逻辑上,这是对原有业务逻辑的一种加强,能够是前置、后置、返回后、抛出异样时等。其实 Advice 翻译成“加强”更正当,更能精确表白其本质。既然大部分文献都是称为告诉,咱们这里也称为告诉。
  • 织入: 织入是将Advice告诉增加到指标类具体连接点上的过程。编译器织入、动静织入。
  • Aspect 切面:Pointcut(切点)和Advice(告诉)组成的
Introduction:引介是一种非凡的告诉,它为类增加一些属性和办法。这样,即便一个业务类本来没有实现某个接口,通过引介性能,能够动静的为该业务类增加接口的实现逻辑,让业务类成为这个接口的实现类。

织入

后面咱们也看到编译之后的 Main class 类、能够看到它是实际性的扭转了咱们原有的 class 文件的。这称之为动态织入。

次要是 ajc 编译器在编译期将 aspect 类编译成 class 字节码之后、而后织入到指标类中。

还有一种是更加常见的织入形式--动静织入。动静织入是在运行期间将要加强的代码织入到指标类中、这样往往是通过动静代理实现的。比如说 Java JDK 的动静代理或者 CGLib 的动静代理。Spring AOP 就是采纳动静织入的。

Pointcut 表达式

切点的表达式。Spring Aop只反对其中的9种,外加Spring Aop本人裁减的一种一共是10种类型的表达式

execution:个别用于指定办法的执行,用的最多。
within:指定某些类型的全副办法执行,也可用来指定一个包。
this:Spring Aop是基于代理的,生成的bean也是一个代理对象,this就是这个代理对象,当这个对象能够转换为指定的类型时,对应的切入点就是它了,Spring Aop将失效。
target:当被代理的对象能够转换为指定的类型时,对应的切入点就是它了,Spring Aop将失效。
args:当执行的办法的参数是指定类型时失效。
@target:当代理的指标对象上领有指定的注解时失效。
@args:当执行的办法参数类型上领有指定的注解时失效。
@within:与@target相似,看官网文档和网上的说法都是@within只须要指标对象的类或者父类上有指定的注解,则@within会失效,而@target则是必须是指标对象的类上有指定的注解。而依据笔者的测试这两者都是只有指标类或父类上有指定的注解即可。
@annotation:当执行的办法上领有指定的注解时失效。
bean:当调用的办法是指定的bean的办法时失效

这个后续的文章再具体介绍