MENU

AOP面向切面编程

May 15, 2022 • 学习笔记

面向切面编程-AspectJ的使用

普通Java项目中,启用AspectJ

普通项目中启用AspectJ有点麻烦

  1. 安装AspectJ

    java -jar aspectj-1.9.5.jar 使用安装程序一步一步安装,需要按照安装程序结尾的提示配置CLASSPATH和PATH环境变量。

  2. Maven项目中pom.xml配置依赖

    <dependency>
        <groupId>aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.5.4</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.5</version>
    </dependency>
  3. 由于javac无法启用Aspect,所以还需要调用ajc命令编译一遍,但是为了方便可以加入一个maven插件,这样就可以直接运行了(IDEA中)

    <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>aspectj-maven-plugin</artifactId>
                    <version>1.7</version>
                    <configuration>
                        <complianceLevel>1.8</complianceLevel>
                        <source>1.8</source>
                        <target>1.8</target>
                        <showWeaveInfo>true</showWeaveInfo>
                        <verbose>true</verbose>
                        <Xlint>ignore</Xlint>
                        <encoding>UTF-8</encoding>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>compile</goal>
                                <goal>test-compile</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
    </build>

Spring 项目中启用AspectJ

Spring项目中启用Aspect就比较简单了

  1. pom.xml中添加依赖

    <!-- Spring AOP 切面 模块 -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>1.9.2</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.2</version>
            </dependency>
  1. ApplicationConetxt.xml中配置

    <!-- 配置开启@Aspect支持 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />

编写Aspect类

常用的以下几个注解

  • @Aspect : 这个需要在类上注解
  • @Pointcut :定义一个切面点,也可以省略,直接在其他阶段注解中增加pointcut参数
  • @Before :前置执行,在方法执行前执行,可以用来做参数校验,修改,填充等
  • @After : 后置执行,在方法结束后执行
  • @AfterReturning : 返回时执行,如果方法抛出异常就不会执行
  • @AfterThrowing : 方法异常时执行,可以用来做日志记录,事务回滚等
  • @Around : 环绕执行,调用proceedingJoinPoint.proceed()时执行切点方法

注:@Before和@After不可同时与@Around一起出现,否则会出现类似 Error:(51, 0) ajc: circular advice precedence: can't determine precedence between two or more pieces of advice that apply to the same join point: method-execution(void com.sundae.aspectj.dao.BaseDao.save())这样的错误

例子
//DaoAspect.java
@Aspect
public class DaoAspect {
    //定义一个切点
    @Pointcut("execution(* com.sundae.aspectj.dao.*.*(..))")
    public void point(){}

    // 前置通知
//    @Before("point()")
//    public void before() {
//        System.out.println("前置");
//    }
    
    // 后置通知 始终会执行
    @After("point()")
    public void after(JoinPoint joinPoint) {
        System.out.println("后置");
    }

    // 环绕通知
    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("around");
        Object result = proceedingJoinPoint.proceed();
        return result;
    }

    // 后置 发生异常时不会执行
    @AfterReturning("point()")
    public void returning() {
        System.out.println("After returning 后置");
    }

    // 发生异常
    @AfterThrowing(value = "point()", throwing = "ex")
    public void throwing(Exception ex) {
        System.out.println("发生异常了");
        System.out.println(ex);
    }

}

//BaseDao.java
public class BaseDao {
    public void save() throws Exception {
        System.out.println("BaseDao -> save()");
        throw new Exception();
    }
    public void rollback(){
        System.out.println("BaseDao -> rollback()");
    }
    public void commit() {
        System.out.println("BaseDao -> commit()");
    }
}
执行顺序
  • 无异常:@Around(proceed()之前的部分) → @Before → 方法执行 → @Around(proceed()之后的部分) → @After → @AfterReturning
  • 有异常:@Around(proceed(之前的部分)) → @Before → 扔异常ing → @After → @AfterThrowing
Pointcut字符串匹配规则匹配

execution(* com.sundae.aspectj.dao.*.*(..))

  • 第一个星号表示返回的类型
  • 第二个星号表示匹配dao包下所有的类
  • 第三个信号表示匹配任意名字的方法
  • 括号内的两个点,表示任意参数

如:

execution(* com.sundae.aspectj.dao.BaseDao.save(..))表示匹配BaseDao类中的任意save方法

execution(* com.sundae.aspectj.dao.BaseDao.save(String,int))表示匹配BaseDao类中的任意返回值的save(String,int)方法

execution(String com.sundae.aspectj.dao.BaseDao.save(..))表示匹配BaseDao类中的任意返回值为String的save(..)方法

扩展
  1. 可以在@Around中做很多事情,比如
@Around("point()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("around");
    try{
        BaseDao baseDao = (BaseDao) proceedingJoinPoint.getThis();
        Object result = proceedingJoinPoint.proceed();
        baseDao.commit();       //事务提交操作
        return result;
    }catch (Exception e){
        //TODO 异常日志记录
        //TODO 事务回滚操作
    }
    return null;
}

其中类似BaseDao baseDao = (BaseDao) proceedingJoinPoint.getThis();这样直接获取切点实例的方法耦合度高,且降低了通用性,也许可以使用反射来提高通用性,降低耦合。

  1. 可以以注解作为切入点,可以实现一个注解直接实现某些功能。