使用 AspectJ 框架实现 Spring AOP

AspectJ 是基于注释(Annotation)的,所以需要 JDK5.0 以上的支持。

AspectJ 支持的注解类型如下:

  • @Before
  • @After
  • @AfterReturning
  • @AfterThrowing
  • @Around

准备工作

首先定义一个简单的 bean,CustomerBo 实现了接口 ICustomerBo。ICustomerBo.java 如下:

package com.shiyanlou.spring.aop.aspectj;

public interface ICustomerBo {
    void addCustomer();
    void deleteCustomer();
    String AddCustomerReturnValue();
    void addCustomerThrowException() throws Exception;
    void addCustomerAround(String name);
}

CustomerBo.java 如下:

package com.shiyanlou.spring.aop.aspectj;

public class CustomerBo implements ICustomerBo {

    public void addCustomer() {
        System.out.println("addCustomer() is running ...");
    }

    public void deleteCustomer() {
        System.out.println("deleteCustomer() is running ...");
    }

    public String AddCustomerReturnValue() {
        System.out.println("AddCustomerReturnValue() is running ...");
        return "abc";
    }

    public void addCustomerThrowException() throws Exception {
        System.out.println("addCustomerThrowException() is running ...");
        throw new Exception("Generic Error");
    }

    public void addCustomerAround(String name) {
        System.out.println("addCustomerAround() is running ,args:"+name);

    }

}

简单的 AspectJ,Advice 和 Pointcut 结合在一起

首先没有引入 Aspect 之前,Advice 和 Pointcut 是混在一起的,步骤如下:

  • 创建一个 Aspect 类
  • 配置 Spring 配置文件

由于接下来要使用 aspectj 的 jar 包,首先添加 maven 依赖。需要在 pom.xml 添加:

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.2</version>
        </dependency>

注:我们在新建项目时就已经添加了这些依赖,这里写出来只是让同学们知道这些包的作用

创建 AspectJ 类,LoggingAspect.java 如下:

package com.shiyanlou.spring.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LoggingAspect {

    @Before("execution(public * com.shiyanlou.spring.aop.aspectj.CustomerBo.addCustomer(..))")
    public void logBefore(JoinPoint joinPoint){
        System.out.println("logBefore() is running ...");
        System.out.println("hijacked:"+joinPoint.getSignature().getName());
        System.out.println("**********");
    }

    @After("execution(public * com.shiyanlou.spring.aop.aspectj.CustomerBo.deleteCustomer(..))")
    public void logAfter(JoinPoint joinPoint){
        System.out.println("logAfter() is running ...");
        System.out.println("hijacked:"+joinPoint.getSignature().getName());
        System.out.println("**********");
    }
}

解释:

1、必须使用 @Aspect 在 LoggingAspect 声明之前注释,以便被框架扫描到。
2、此例 Advice 和 Pointcut 结合在一起,类中的具体方法 logBefore 和 logAfter 即为 Advice,是要注入的代码,Advice 方法上的表达式为 Pointcut 表达式,即定义了切入点,上例中 @Before 注释的表达式代表执行 CustomerBo.addCustomer 方法时注入 logBefore 代码。
3、在 LoggingAspect 方法上加入 @Before 或者 @After 等注释。
4、execution(public * com.shiyanlou.spring.aop.aspectj.CustomerBo.addCustomer(..)) 是 Aspect 的切入点表达式,其中,* 代表返回类型,后边的就要定义要拦截的方法名。这里写的的是 com.shiyanlou.spring.aop.aspectj.CustomerBo.addCustomer 表示拦截 CustomerBo 中的 addCustomer 方法,(..) 代表参数匹配,此处表示匹配任意数量的参数,可以是 0 个也可以是多个,如果你确定这个方法不需要使用参数可以直接用 (),还可以使用 () 来匹配一个任意类型的参数,还可以使用 ( , String ),这样代表匹配两个参数,第二个参数必须是 String 类型的参数。
5、AspectJ 表达式,可以对整个包定义,例如 execution ( * com.shiyanlou.spring.aop.aspectj..(..)) 表示切入点是 com.shiyanlou.spring.aop.aspectj 包中的任意一个类的任意方法,具体的表达式请自行搜索。

配置 SpringAopAspectj.xml 文件,如下:

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

    <aop:aspectj-autoproxy/>

    <bean id = "customerBo" class = "com.shiyanlou.spring.aop.aspectj.CustomerBo"/>

    <bean id = "logAspect" class = "com.shiyanlou.spring.aop.aspectj.LoggingAspect" />

</beans>

<aop:aspectj-autoproxy/> 启动 AspectJ 支持,这样 Spring 会自动寻找用 @Aspect 注释过的类,其他的配置与 Spring 普通 bean 配置一样。

执行 App.java 如下:

package com.shiyanlou.spring.aop.aspectj;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {

        ApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] { "SpringAOPAspectj.xml" });
        ICustomerBo customer = (ICustomerBo)appContext.getBean("customerBo");

        customer.addCustomer();

        System.out.println("-------------------------------------------");

        customer.deleteCustomer();

    }
}

控制台输入命令:

mvn clean compile
mvn exec:java -Dexec.mainClass="com.shiyanlou.spring.aop.aspectj.App"

实验结果如下:

将 Advice 和 Pointcut 分开

需要三步:

1、创建 Pointcut
2、创建 Advice
3、配置 Spring 的配置文件

定义 Pointcut,创建 PointcutsDefinition.java,如下:

package com.shiyanlou.spring.aop.aspectj;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class PointcutsDefinition {

    @Pointcut("execution(* com.shiyanlou.spring.aop.aspectj.CustomerBo.*(..))")
    public void customerLog() {
    }
}

解释:

1、类声明前加入 @Aspect 注释,以便被框架扫描到。
2、@Pointcut 是切入点声明,指定需要注入的代码的位置,如上例中指定切入点为 CustomerBo 类中的所有方法,在实际业务中往往是指定切入点到一个逻辑层,例如 execution (* com.shiyanlou.spring.aop.aspectj..(..)),表示 aop 切入点为 aspectj 包中所有类的所有方法,具体的表达式后边会有介绍。
3、方法 customerLog 是一个签名,在 Advice 中可以用此签名代替切入点表达式,所以不需要在方法体内编写实际代码,只起到助记功能,例如此处代表操作 CustomerBo 类时需要的切入点。
创建 LoggingAspect.java:

package com.shiyanlou.spring.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LoggingAspect {

    @Before("com.shiyanlou.spring.aop.aspectj.PointcutsDefinition.customerLog()")
    public void logBefore(JoinPoint joinPoint){
        System.out.println("logBefore() is running ...");
        System.out.println("hijacked:"+joinPoint.getSignature().getName());
        System.out.println("**********");
    }

    @After("com.shiyanlou.spring.aop.aspectj.PointcutsDefinition.customerLog()")
    public void logAfter(JoinPoint joinPoint){
        System.out.println("logAfter() is running ...");
        System.out.println("hijacked:"+joinPoint.getSignature().getName());
        System.out.println("**********");
    }
}

解释:

1、@Before 和 @After 使用 PointcutsDefinition 中的方法签名代替 Pointcut 表达式找到相应的切入点,即通过签名找到 PointcutsDefinition 中 customerLog 签名上的 Pointcut 表达式,表达式指定切入点为 CustomerBo 类中的所有方法。所以此例中 Advice 类 LoggingAspect,为 CustomerBo 中的所有方法都加入了 @Before 和 @After 两种类型的两种操作。
2、对于 PointcutsDefinition 来说,主要职责是定义 Pointcut,可以在其中定义多个切入点,并且可以用便于记忆的方法签名进行定义。
3、单独定义 Pointcut 的好处是,一是通过使用有意义的方法名,而不是难读的 Pointcut 表达式,使代码更加直观;二是 Pointcut 可以实现共享,被多个 Advice 直接调用。若有多个 Advice 调用某个 Pointcut,而这个 Pointcut 的表达式在将来有改变时,只需修改一个地方,维护更加方便。

配置 Spring 配置文件。

配置 SpringAOPAspectJ.xml 文件,如下:

<beans xmlns = "http://www.springframework.org/schema/beans"
    xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop = "http://www.springframework.org/schema/aop"
    xsi:schemaLocation = "http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy/>

    <bean id = "customerBo" class = "com.shiyanlou.spring.aop.aspectj.CustomerBo"/>

    <bean id = "logAspect" class = "com.shiyanlou.spring.aop.aspectj.LoggingAspect" />

</beans>

App.java 不变,运行测试代码 App.java:

mvn clean compile
mvn exec:java -Dexec.mainClass="com.shiyanlou.spring.aop.aspectj.App"

结果如下:

posted @ 2020-11-14 17:16  上杉家主-上杉绘梨衣  阅读(118)  评论(0编辑  收藏  举报