美文网首页读书
Spring使用AspectJ进行AOP开发(基于注解)

Spring使用AspectJ进行AOP开发(基于注解)

作者: Java七七 | 来源:发表于2022-07-11 21:33 被阅读0次

    在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。

    为此,AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。

    关于注解的介绍如表 1 所示。


    image.png

    启用 @AspectJ 注解支持

    在使用 @AspectJ 注解进行 AOP 开发前,首先我们要先启用 @AspectJ 注解支持。

    我们可以通过以下 2 种方式来启用 @AspectJ 注解。

    1)使用 Java 配置类启用

    我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。

    <pre class="java sh_java snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

    1. @Configuration
    2. @ComponentScan(basePackages = "net.biancheng.c") //注解扫描
    3. @EnableAspectJAutoProxy //开启 AspectJ 的自动代理
    4. public class AppConfig {
    5. }

    </pre>

    2)基于 XML 配置启用

    在 Spring 的 XML 配置文件中,添加以下内容启用 @AspectJ 注解支持。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

    <pre class="xml sh_xml snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

    1. <context:component-scan base-package="net.biancheng.c"></context:component-scan>
    2. <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    </pre>

    定义切面 @Aspect

    我们可以通过 @Aspect 注解将一个 Bean 定义为切面。

    在启用了 @AspectJ 注解支持的情况下,Spring 会自动将 IoC 容器(ApplicationContext)中的所有使用了 @Aspect 注解的 Bean 识别为一个切面。

    我们可以在 XML 配置中通过一些配置将这个类定义为一个 Bean,如下。

    <pre class="xml sh_xml snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

    1. <bean id = "myAspect" class = "net.biancheng.c.MyAspect">
    2. ...
    3. </bean>

    </pre>

    在定义完 Bean 后,我们只需要在Bean 对应的 Java 类中使用一个 @Aspect 注解,将这个 Bean 定义为一个切面,代码如下。

    <pre class="java sh_java snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

    1. package net.biancheng.c;

    2. import org.aspectj.lang.annotation.*;

    3. @Aspect //定义为切面

    4. public class MyAspect {

    5. }

    </pre>

    全注解方式定义切面

    我们也可以在 Java 类上使用以下 2 个注解,使用全注解方式定义切面。

    <pre class="java sh_java snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

    1. package net.biancheng.c;

    2. import org.aspectj.lang.annotation.*;

    3. import org.springframework.stereotype.Component;

    4. @Component // 定义成 Bean

    5. @Aspect //定义为切面

    6. public class MyAspect {

    7. }

    </pre>

    在以上代码中共使用两个注解:

    • @Component 注解:将这个类的对象定义为一个 Bean;
    • @Aspect 注解:则是将这个 Bean 定义为一个切面。

    定义切点 @Pointcut

    在 AspectJ 中,我们可以使用 @Pointcut 注解用来定义一个切点。需要注意的是,定义为切点的方法,它的返回值类型必须为 void,示例代码如下。

    <pre class="java sh_java snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

    1. // 要求:方法必须是private,返回值类型为 void,名称自定义,没有参数
    2. @Pointcut("execution(net.biancheng...*(..))")
    3. private void myPointCut() {
    4. }

    </pre>

    @Pointcut 注解中有一个 value 属性,这个属性的值就是切入点表达式。有关切入点表达式的具体介绍请参考《使用AspectJ实现AOP(基于XML)》中的 execution 语法格式介绍。

    值得注意的是,我除了可以通过切入点表达式(execution)直接对切点进行定义外,还可以通过切入点方法的名称来引用其他的切入点。在使用方法名引用其他切入点时,还可以使用“&&”、“||”和“!”等表示“与”、“或”、“非”的含义,示例代码如下。

    <pre class="java sh_java snippet-formatted sh_sourceCode" style="margin: 0px; display: block; padding: 0px; font-size: 14px; line-height: 1.6em; color: rgb(102, 102, 102); white-space: pre-wrap; overflow-wrap: break-word; background: none; border: none; border-radius: 0px;">

    1. /**

      • 将 net.biancheng.c.dao包下 UserDao 类中的 get() 方法定义为一个切点
    2. */

    3. @Pointcut(value ="execution(* net.biancheng.c.dao.UserDao.get(..))")

    4. public void pointCut1(){

    5. }

    6. /**

      • 将 net.biancheng.c.dao包下 UserDao 类中的 delete() 方法定义为一个切点
    7. */

    8. @Pointcut(value ="execution(* net.biancheng.c.dao.UserDao.delete(..))")

    9. public void pointCut2(){

    10. }

    11. /**

      • 除了 net.biancheng.c.dao包下 UserDao 类中 get() 方法和 delete() 方法外,其他方法都定义为切点
      • ! 表示 非 ,即 "不是" 的含义,求补集
        • && 表示 与,即 ”并且“ ,求交集
      • || 表示 或,即 “或者”,求并集
    12. */

    13. @Pointcut(value ="!pointCut1() && !pointCut2()")

    14. public void pointCut3(){

    15. }

    </pre>

    定义通知

    AspectJ 为我们提供了以下 6 个注解,来定义 6 种不同类型的通知(Advice),如下表。

    <caption> </caption>


    image.png

    以上这些通知注解中都有一个 value 属性,这个 value 属性的取值就是这些通知(Advice)作用的切点(PointCut),它既可以是切入点表达式,也可以是切入点的引用(切入点对应的方法名称),示例代码如下。
    @Pointcut(value ="execution(* net.biancheng.c.dao.UserDao.get(..))")
    public void pointCut1(){
    }
    @Pointcut(value ="execution(* net.biancheng.c.dao.UserDao.delete(..))")
    public void pointCut2(){
    }
    @Pointcut(value ="!pointCut1() && !pointCut2()")
    public void pointCut3(){
    }
    //使用切入点引用
    @Before("MyAspect.pointCut3()")
    public void around() throws Throwable {
    System.out.println("环绕增强……");
    }
    //使用切入点表达式
    @AfterReturning(value = "execution(* net.biancheng.c.dao.UserDao.get(..))" ,returning = "returnValue")
    public void afterReturning(Object returnValue){
    System.out.println("方法返回值为:"+returnValue);
    }
    示例
    下面,我们就通过一个完整的实例,来演示下如何通过注解的方式实现 AspectJ AOP 开发。

    1. 新建一个名为 my-spring-asepctj-demo2 的 Java 项目,并将以下依赖 Jar 包导入到该项目中。
      commons-logging-1.2.jar
      spring-aop-5.3.13.jar
      spring-aspects-5.3.13.jar
      spring-beans-5.3.13.jar
      spring-context-5.3.13.jar
      spring-core-5.3.13.jar
      spring-expression-5.3.13.jar
      aspectjweaver-1.9.7.jar

    2. 在 net.biancheng.c.dao 包下,创建一个名为 UserDao 的接口,代码如下。
      package net.biancheng.c.dao;
      public interface UserDao {
      public void add();
      public void delete();
      public int modify();
      public void get();
      }

    3. 在 net.biancheng.c.dao.impl 包下,创建 UserDao 的实现类 UserDaoImpl,代码如下。
      package net.biancheng.c.dao.impl;
      import net.biancheng.c.dao.UserDao;
      import org.springframework.stereotype.Component;
      @Component("userDao")
      public class UserDaoImpl implements UserDao {
      @Override
      public void add() {
      System.out.println("正在执行 UserDao 的 add 方法");
      }
      @Override
      public void delete() {
      System.out.println("正在执行 UserDao 的 delete 方法");
      }
      @Override
      public int modify() {
      System.out.println("正在执行 UserDao 的 modify 方法");
      return 1;
      }
      @Override
      public void get() {
      System.out.println("正在执行 UserDao 的 get 方法");
      }
      }

    4. 在 net.biancheng.c.dao.config 包下,创建一个名为 AppConfig 的配置类,代码如下。
      package net.biancheng.c.dao.config;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.EnableAspectJAutoProxy;
      @Configuration
      @ComponentScan(basePackages = "net.biancheng.c") //注解扫描
      @EnableAspectJAutoProxy //开启 AspectJ 的自动代理
      public class AppConfig {
      }

    5. 在 net.biancheng.c 包下,创建一个名为 MyAspect 的切面类,代码如下。
      package net.biancheng.c;
      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.;
      import org.springframework.stereotype.Component;
      @Component // 定义成 Bean
      @Aspect //定义为切面
      public class MyAspect {
      @Before("execution(
      net.biancheng.c.dao.UserDao.add(..))")
      public void before(JoinPoint joinPoint) {
      System.out.println("前置增强……" + joinPoint);
      }
      @After("execution(* net.biancheng.c.dao.UserDao.get(..))")
      public void after(JoinPoint joinPoint) {
      System.out.println("最终增强……" + joinPoint);
      }
      /**

      • 将 net.biancheng.c.dao包下的 UserDao 类中的 get() 方法 定义为一个切点
        /
        @Pointcut(value = "execution(
        net.biancheng.c.dao.UserDao.get(..))")
        public void pointCut1() {
        }
        /**
      • 将 net.biancheng.c.dao包下的 UserDao 类中的 delete() 方法 定义为一个切点
        /
        @Pointcut(value = "execution(
        net.biancheng.c.dao.UserDao.delete(..))")
        public void pointCut2() {
        }
        //使用切入点引用
        @Around("MyAspect.pointCut2()")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕增强……1");
        proceedingJoinPoint.proceed();
        System.out.println("环绕增强……2");
        }
        //使用切入点表达式
        @AfterReturning(value = "execution(* net.biancheng.c.dao.UserDao.modify(..))", returning = "returnValue")
        public void afterReturning(Object returnValue) {
        System.out.println("后置返回增强……,方法返回值为:" + returnValue);
        }
        }
    6. 在 net.biancheng.c 包下,创建一个名为 MainApp 的类,代码如下。
      package net.biancheng.c;
      import net.biancheng.c.dao.UserDao;
      import net.biancheng.c.dao.config.AppConfig;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      public class MainApp {
      public static void main(String[] args) {
      ApplicationContext context2 = new AnnotationConfigApplicationContext(AppConfig.class);
      UserDao userDao = context2.getBean("userDao", UserDao.class);
      userDao.add();
      userDao.modify();
      userDao.delete();
      userDao.get();
      }
      }

    7. 执行 MainApp 中的 main() 方法,控制台输出如下。
      前置增强……execution(void net.biancheng.c.dao.UserDao.add())
      正在执行 UserDao 的 add 方法
      正在执行 UserDao 的 modify 方法
      后置返回增强……,方法返回值为:1
      环绕增强……1
      正在执行 UserDao 的 delete 方法
      环绕增强……2
      正在执行 UserDao 的 get 方法
      最终增强……execution(void net.biancheng.c.dao.UserDao.get())

    相关文章

      网友评论

        本文标题:Spring使用AspectJ进行AOP开发(基于注解)

        本文链接:https://www.haomeiwen.com/subject/bdnaurtx.html