• Spring5(六)——AspectJ(xml)


    一、AspectJ

    1、介绍

      AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,也可以说 AspectJ 是一个基于 Java 语言的 AOP 框架。通常我们在使用 Spring AOP 的时候,都会导入 AspectJ 的相关 jar 包。

    2、案例(xml)

      定义目标对象(被代理的对象)(与上一章相同)
      编写一个切面类(通知)

     1 // 创建切面类(包含各种通知)
     2 public class MyAspect {
     3 
     4     // JoinPoint 能获取目标方法的一些基本信息
     5     public void myBefore(JoinPoint joinPoint) {
     6         System.out.println("前置通知:方法增强myBefore()" + " , -->" + joinPoint.getSignature().getName());
     7     }
     8     
     9     // object:目标方法的返回值
    10     public void myAfterReturning(JoinPoint joinPoint, Object object) {
    11         System.out.println("后置通知:方法增强myAfterReturning()" + " , -->" + joinPoint.getSignature().getName() + " , -->" + object);
    12     }
    13 
    14     public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
    15         System.out.println("============环绕前==============");
    16         Object obj = joinPoint.proceed(); // 手动执行目标方法
    17         System.out.println("============环绕后==============");
    18         return obj;
    19     }
    20 
    21     public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
    22         System.out.println("抛出异常通知:" + e.getMessage());
    23     }
    24 
    25     public void myAfter() {
    26         System.out.println("最终通知:方法增强myAfter()");
    27     }
    28 
    29 }

      编写配置文件 application.xml

     1 <!-- 配置目标对象 -->
     2 <bean id="teacher" class="com.lx.spring.common.Teacher"/>
     3 <!-- 配置切面对象(通知) -->
     4 <bean id="myAspect" class="com.lx.spring.day3.MyAspect"/>
     5 
     6 <aop:config>
     7     <!-- 切入点表达式,指明了在哪里引入通知 -->
     8     <aop:pointcut id="myPointcut" expression="execution(* com.lx.spring.common.ITeacher.*(..))"/>
     9 
    10     <!-- 方法增强,指明了引入一个什么样的通知 -->
    11     <aop:aspect ref="myAspect">
    12         <aop:before method="myBefore" pointcut-ref="myPointcut"/>
    13         <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="object"/>
    14         <aop:around method="myAround" pointcut-ref="myPointcut"/>
    15         <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="e"/>
    16         <aop:after method="myAfter" pointcut-ref="myPointcut"/>
    17     </aop:aspect>
    18 </aop:config>
     1 // 测试类
     2 public class Main {
     3     public static void main(String[] args) {
     4         ApplicationContext app = new ClassPathXmlApplicationContext("app3.xml");
     5         ITeacher iTeacher = app.getBean(ITeacher.class);
     6 
     7         iTeacher.add(11, 24);
     8     }
     9 }
    10 
    11 // 结果一:未配置环绕通知时.注意:此时 最终通知 在 后置通知后面
    12 前置通知:方法增强myBefore() , -->add
    13 执行目标方法:老师正在做加法,结果为:35
    14 后置通知:方法增强myAfterReturning() , -->add , -->35
    15 最终通知:方法增强myAfter()
    16 
    17 
    18 // 结果二:配置环绕通知时.注意:此时 最终通知 在 后置通知前面
    19 前置通知:方法增强myBefore() , -->add
    20 ============环绕前==============
    21 执行目标方法:老师正在做加法,结果为:35
    22 最终通知:方法增强myAfter()
    23 ============环绕后==============
    24 后置通知:方法增强myAfterReturning() , -->add , -->35

      说明:这里有很多细节需要补充一下。深刻理解通知,重点思想在于:①在哪里(切点,或者说方法)引入?②引入一个什么样的通知?针对这两个问题,则不难理解AOP。
      ①切入点表达式:指明了在哪里(切点,在哪个方法)引入一个通知(即对目标方法的增强),也就是在哪些方法进行增强。execution 是 AspectJ 框架定义的一个切入点函数,其语法形式如下:

    1 execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
    2                 类修饰符           返回值           方法所在的包             方法名(参数)             方法抛出的异常

      那么不难理解,对满足以下规则的方法进行增强。也就是对这些方法引入一个通知。

    1 <aop:pointcut id="myPointcut" expression="execution(* com.lx.spring.common.ITeacher.*(..))"/>
    2                                         选择方法 任意返回值    此包下.此接口   任意方法名(任意参数)

      ②通知(方法增强):指明了对满足切点表达式的方法引入一个什么样的通知。

     1 <!-- 指明引入的切面(通知) -->
     2 <aop:aspect ref="myAspect">
     3     <!-- 对满足上面切入点表达式的方法配置一个前置通知 -->
     4     <!-- 即在目标方法前执行方法 myBefore -->
     5     <aop:before method="myBefore" pointcut-ref="myPointcut"/>
     6     
     7     <!-- 对满足上面切入点表达式的方法配置一个后置通知 -->
     8     <!-- 即在目标方法后执行方法 myAfterReturning -->
     9     <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="object"/>
    10 </aop:aspect>

    3、切入点表达式

      上一节中已经介绍过切入点表达式的相关语法,且理解不难。再补充几点,如果切入点表达式有多个不同目录呢?可以通过 || 来表示或的关系。

    1 <!--表示匹配com.lx.aop包下的,以Service结尾或者以Facade结尾的类的任意方法。-->
    2 <aop:pointcut id="myPointcut" expression="execution(* com.lx.aop.*Service.*(..)) || execution(* com.lx.aop.*Facade.*(..))"/>

      AOP 切入点表达式支持多种形式的定义规则:

     1 1、execution:匹配方法的执行(常用)
     2     execution(public *.*(..))
     3 2、within:匹配包或子包中的方法(了解)
     4     within(com.ys.aop..*)
     5 3、this:匹配实现接口的代理对象中的方法(了解)
     6     this(com.ys.aop.user.UserDAO)
     7 4、target:匹配实现接口的目标对象中的方法(了解)
     8     target(com.ys.aop.user.UserDAO)
     9 5、args:匹配参数格式符合标准的方法(了解)
    10     args(int,int)
    11 6、bean(id):对指定的bean所有的方法(了解)
    12     bean('userServiceId')

    4、通知类型

    通知类型
    接口
    描述
    前置通知
    (before)
    org.springframework.aop.aspectj.AspectJMethodBeforeAdvice
    在目标方法前调用,如果通过抛出异常,阻止方法运行。
    应用:各种校验。
    后置通知
    (afterReturning)
    org.springframework.aop.aspectj.AspectJAfterReturningAdvice
    在目标方法后调用,可以获得目标方法返回值,若目标方法抛出异常,通知无法执行。
    应用:常规数据处理。
    环绕通知
    (around)
    org.springframework.aop.aspectj.AspectJAroundAdvice
    在目标方法前后调用,可以阻止方法的执行,必须手动执行目标方法。
    应用:十分强大,可以做任何事情。
    异常通知
    (afterThrowing)
    org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
    目标方法抛出异常时调用,若目标方法没有抛出异常,无法执行。
    应用:包装异常信息
    最终通知
    (after)
    org.springframework.aop.aspectj.AspectJAfterAdvice
    目标方法执行完毕后执行,无论方法中是否出现异常
    应用:清理现场

      这里最重要的是around,环绕通知,它可以代替上面的任意通知。

      在程序中表示的意思如下:

     1 public class Main {
     2     public static void main(String[] args) {
     3         try {
     4             // 前置 before
     5             // 手动执行目标方法
     6             // 后置 afterReturning
     7         } catch (Exception e) {
     8             // 抛出异常通知 afterThrowing
     9         } finally {
    10            // 最终 after
    11         }
    12     }
    13 }

      源码:

    5、小结

      使用 <aop:config>进行配置,proxy-target-class="true",声明时使用cglib代理;如果不声明,Spring 会自动选择cglib代理还是JDK动态代理。
      SpringAOP 的具体加载步骤:
      ①当 spring 容器启动的时候,加载 spring 的配置文件。
      ②为配置文件中的所有 bean 创建对象。
      ③spring 容器会解析 aop:config 的配置,解析切入点表达式,用切入点表达式和纳入 spring 容器中的 bean 做匹配,如果匹配成功,则会为该 bean 创建代理对象,代理对象的方法 = 目标方法 + 通知;如果匹配不成功,不会创建代理对象。
      ④在客户端利用 context.getBean() 获取对象时,如果该对象有代理对象,则返回代理对象;如果没有,则返回目标对象
      说明:如果目标类没有实现接口,则 spring 容器会采用 cglib 的方式产生代理对象,如果实现了接口,则会采用 jdk 的方式。

    作者:Craftsman-L

    本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。

    如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!

  • 相关阅读:
    c++11-17 模板核心知识(七)—— 模板参数 按值传递 vs 按引用传递
    c++11-17 模板核心知识(六)—— 理解auto推导规则
    c++11-17 模板核心知识(五)—— 理解模板参数推导规则
    c++11-17 模板核心知识(四)—— 可变参数模板 Variadic Template
    c++11-17 模板核心知识(三)—— 非类型模板参数 Nontype Template Parameters
    c++11-17 模板核心知识(二)—— 类模板
    c++11-17 模板核心知识(一)—— 函数模板
    Docker修改Devicemapper存储驱动为Direct-lvm模式
    Linux配置/etc/resolv.conf详解
    深入浅出容器学习--Docker网络
  • 原文地址:https://www.cnblogs.com/originator/p/15271171.html
Copyright © 2020-2023  润新知