Spring 中注入 AspectJ 切面

编辑于2018年10月26日

最近在阅读《Spring 实战》(第四版),跟着书中的讲解做一些练习。其中,在注入 Aspect 切面那一节卡了一会。主要是对 AspectJ 不了解,Gradle 的一些用法不熟悉导致,于是决定通过一篇博客记录一下。在后面的阅读过程中,如果遇到其他的问题,我也会写到博客中。

相关知识

Spring 提供了 4 中类型的 AOP 支持:

  • 基于代理的经典 Spring AOP;
  • 纯 POJO 切面;
  • @AspectJ 注解驱动的切面;
  • 注入式 AspectJ 切面。

前三种都是 Spring AOP 实现的变体,Spring AOP 构建在动态代理之上,通过在代理类中包裹切面,Spring 在运行期间把切面织入到 Spring 管理的 bean 中。因为直到应用需要被代理 bean 时,Spring 才创建对象,所以不需要特殊的编译器来织入 Spring AOP 切面,而正因为 Spring 基于动态代理,所以 Spring 只支持方法连接点。

织入

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:

  • 编译期:切面在目标类编译时织入。这种方式需要特殊的编译器,例如 AspectJ 的 ajc。
  • 类加载期:切面在目标类加载到 JVM 时被织入。这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5 的加载时织入(load-time weaving,LTW) 就支持以这种方式织入切面。
  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。Spring AOP 就是以这种方式织入切面的。

注入 AspectJ 切面

正如上文介绍的 Spring AOP 基于动态代理实现,局限于方法拦截,而 AspectJ 提供了 Spring AOP 所不能支持的许多类型的切点。

AspectJ 切面与 Spring 是相互独立的。但是如果在执行通知时,切面依赖一个或多个类,我们可以借助 Spring 的依赖注入把 bean 装配紧 AspectJ 切面中。

根据书中的示例,编写一个表演和评论员的例子。

编写切面

使用 AspectJ 实现表演的评论员,创建 CriticAspect.aj 文件,注意这里使用 .aj 作为后缀

package concert;

public aspect CriticAspect {
    private CriticismEngine criticismEngine;

    pointcut performance():execution(* concert.Performance.perform(..));

    pointcut construct():execution(concert.CriticismEngineImpl.new());

    after():performance(){
        System.out.println(criticismEngine.getCriticism());
    }

    after():construct(){
        System.out.println("After Performance constructor");
    }


    before():construct(){
        System.out.println("Before Performance constructor");
    }

    public CriticismEngine getCriticismEngine() {
        return this.criticismEngine;
    }

    public void setCriticismEngine(CriticismEngine criticismEngine) {
        this.criticismEngine = criticismEngine;
    }
}

CriticAspect 的主要职责是在表演结束后为表演发表评论,但是不是自己发表评论,而是通过 CriticismEngine 来获取一个评论,这里就可以通过 Spring 注入。

实现 CriticismEngine 和 Performance

这里我就不贴出具体的实现了,主要就是定义两个接口,然后添加两个实现类。接口如下:

// Performance 接口
package concert;

public interface Performance {
    void perform();
}

// CriticismEngine 接口
package concert;

public interface CriticismEngine {
    String getCriticism();
}

具体代码见 Github

Spring 配置

这里需要注意的是 AspectJ 切面根本不需要 Spring 就可以织入到我们的应用中。但是如果想使用 Spring 依赖注入为 AspectJ 注入写作者,就需要配置。配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="performance" class="concert.Music" />

  <bean id="criticismEngine" class="concert.CriticismEngineImpl">
    <property name="criticismPool">
      <list>
        <value>Worst performance ever!</value>
        <value>I laughed, I cried, then I realized I was at the wrong show.</value>
        <value>A must see show!</value>
      </list>
    </property>
  </bean>

  <bean class="concert.CriticAspect" factory-method="aspectOf">
    <property name="criticismEngine" ref="criticismEngine" />
  </bean>
</beans>

主要配置了 CriticismEnginePerformance 的两个 bean(这就没什么好介绍的) 和 CriticAspect

其中 CriticAspect bean 的声明和其他的没什么区别,主要在于使用了 factory-method 属性。Spring 不负责创建 CriticAspect(由 AspectJ 创建),所以不能在 Spring 中简单地把 CriticAspect 声明为一个 bean,而需要一种方式为 Spring 提供已经由 AspectJ 创建的 CriticAspect 实例,从而可以注入 CriticismEngine。

幸好,所有的 AspectJ 切面都提供了一个静态的 aspectOf() 方法,可以返回切面的实例。所以为了获得切面的实例,需要使用 factory-method 来调用 aspectOf() 方法而不是调用 CriticAspect 的构造器方法。

编译

我使用 Gradle 作为构建工具,Gradle 提供了 aspectj.gradle 插件。相关配置如下:

buildscript {
    ext {
        aspectjVersion = "1.8.5"
    }
}

plugins {
    id "aspectj.gradle" version "0.1.6"
}

使用 aspectj.gradle 插件需要设置 aspectjVersion 属性。

总结

《Spring 实战》一书中的练习我都上传到了 Github,可以自行查阅。

这次主要的问题在于:

  1. 不知道 AspectJ 是什么,以为 AspectJ 特有的语言也是 java,创建 java 文件,编辑器一直不支持语法。
  2. Gradle 还不熟悉,使用插件折腾了半天。