AOP -面向切面编程

news/2024/11/17 23:48:51/文章来源:https://www.cnblogs.com/wztblogs/p/18350107

AOP -面向切面编程

aop是oop(面向对象编程)的补充和完善。oop是一种纵向开发,然而当面对需要一些横向的功能如日志,就会导致大量重复的代码。

aop利用横切把一些重复的非核心的代码插入到核心代码周围,不需要重复写很多遍。

应用场景:

  • 日志记录,在方法的执行前后插入日志功能

  • 事务处理,在方法开始前开启事务,方法结束后提交事务或回滚

  • 安全控制,在系统中包含某些需要安全控制的操作,进行权限判断等

  • 性能监控,在方法执行前记录事件戳,方法执行完后计算方法执行的时间

  • 异常处理,处理方法过程中的异常,可以记录日志、发送邮件等

  • 缓存控制,在方法执行前查询缓存中是否有数据

  • 动态代理,AOP的实现方式之一就是动态代理

 

1.AOP中的名词

  • 横切关注点:业务处理的主要流程是核心关注点,其他的一些非核心的业务如权限认证、日志、事务等就是横切关注点,他们发生在核心关注点的多处

  • 通知(通知):提取出来的重复的非核心代码

    • 前置通知:在被代理的目标方法前执行

    • 返回通知:在被代理的目标方法成功结束后执行

    • 异常通知:在被代理的目标方法异常结束后执行

    • 后置通知:在被代理的目标方法最终结束后执行

    • 环绕通知:使用 try...catch...finally 结构环绕整个被代理的目标方法,包括上面四种 通知对应的所有位置

  • 连接点: 能够被插入通知的 方法

  • 切入点:被选中的连接点,也就是真正被插入通知的方法

  • 切面:切入点和通知的结合,是一个类

  • 目标: 被代理的目标对象,也就是向哪个对象中插入代码

  • 代理:向目标对象应用通知后创建的对象

  • 织入:把通知应用到目标对象上这个动作

 

2.AOP的实现

2.1 aop的底层技术

img

在底层是通过代理技术来实现的,主要分为两种情况:jdk动态代理和cglib

  • jdk动态代理是原生的实现方式,需要被代理的目标类必须实现接口。然后通过接口创建一个 代理类,所以代理对象和目标对象实现了同一个接口。

  • cglib:代理类继承了目标类,不需要目标类有接口

在实现层的上一层还有 AspectJ 注解层,AspectJ是早期的AOP实现框架,SpringAOP借助了这个框架来实现

所以在导入依赖的时候需要导入 spring的aop依赖和 aspectj的依赖,以及这两个框架整合的依赖。

 

2.2 初步使用aop

使用aop的前提是有横向切入的需求,这里我们通过 给计算类添加日志的功能来使用aop。

  1. 导入依赖

    由上面可以得知,需要三个依赖:aop、aspectj、spring-aspectj

    aop依赖可以通过spring-context 进行传递, aspectj 可以通过spring-aspectj依赖进行传递

    所以导入 spring-context 和 spring-aspectj即可

    <dependency>   <groupId>org.springframework</groupId>   <artifactId>spring-aspects</artifactId>   <version>6.0.6</version>
    </dependency>
  2. 正常写计算类的核心业务代码

    • 准备接口(有接口的话,底层的实现技术就是 jdk 动态代理)

      package com.ztone.service;
      ​
      public interface Calculate {   int add(int i,int j);
      ​   int sub(int i,int j);
      ​   int mul(int i,int j);
      ​   int div(int i,int j);
      }
    • 实现计算接口

      package com.ztone.impl;
      ​
      import com.ztone.service.Calculate;
      import org.springframework.stereotype.Component;
      ​
      @Component
      public class CalculateImpl implements Calculate {   @Override   public int add(int i, int j) {       int result = i + j;       return result;   }
      ​   @Override   public int sub(int i, int j) {       int result = i - j;       return result;   }
      ​   @Override   public int mul(int i, int j) {       int result = i * j;       return result;   }
      ​   @Override   public int div(int i, int j) {       int result = i / j;       return result;   }
      }
      ​

      不要忘了把这个类放入ioc容器,因为aop功能只针对aop容器内的对象

       

  3. 编写通知类,并且定义通知方法

    现在我们想在 CalculateImpl 类中的四个计算方法的前后都加上日志,那么就需要一个类来写这个重复的代码,这个类就是通知类或者叫增强类,这个类中的方法就是增强方法,里面存放增强代码

    具体的要把这些方法插入到哪个位置,用注解来指定

    • 前置通知 @Before

    • 后置通知 @AfterReturning

    • 异常通知 @AfterThrowing

    • 最后通知 @After

    • 环绕通知 @Around

    知道了哪个增强方法放在核心代码的前或后,还需要配置切点表达式,就是要把这些增强方法插入到哪个类的哪个方法,切点表达式是就是这些注解的 参数 例如 @Before("execution(* com.ztone.impl.* .* (..))")

    execution(* com.ztone.impl.* .* (..))

    这个表达式 execution是固定写法,括号中 第一个星号表示不考虑方法的返回值和修饰符,com.ztone.impl 表示哪个包,第二个星号表示这个包下的哪个类,第三个星号表示这个类下的哪个方法,后面括号中两个点表示不考虑这个方法的返回值

    package com.ztone.advice;
    ​
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    ​
    @Component
    @Aspect
    public class LogAdvice {   @Before("execution(* com.ztone.impl.*.*(..))")   public void start(){       System.out.println("方法开始了");   }
    ​   @AfterReturning("execution(* com.ztone.impl.*.*(..))")   public void after(){       System.out.println("方法结束了");   }
    ​   @AfterThrowing("execution(* com.ztone.impl.*.*(..))")   public void error(){       System.out.println("方法出错了");   }
    }

    增强类也要放入ioc容器 用 @Component

    还要使用 @Aspect 注解 表示该类是一个切面

     

  4. 在配置类中开启aspect注解的支持

    使用 @EnableAspectJAutoProxy 注解

    package com.ztone.config;
    ​
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    ​
    @Configuration
    @ComponentScan("com.ztone")
    @EnableAspectJAutoProxy
    public class JavaConfig {
    }
  5. 测试

    @SpringJUnitConfig(value = JavaConfig.class)
    public class CalculateTest {
    ​   @Autowired   private Calculate calculate;
    ​   @Test   public void test(){       int add = calculate.add(1, 1);       System.out.println(add);   }
    }

    这里用的是spring 的junit测试,不用自己创建aop容器

    还需要注意的一点是:在声明 CalculateImpl 类时,用的是 接口接值,原因是有了接口底层实现就是用的jdk动态代理,然后根据这个接口创建一个代理类,如果用 目标类去接值的话,对象的类型就不同了会报错

    ioc容器中存储的 也是 创建的代理类,而不是 目标类

    屏幕截图 2024-07-28 120010

     

2.3 在增强方法中获取目标方法信息

  • 获取目标方法的信息(方法名、参数、访问修饰符、所属类的信息),可以在所有增强方法中获取

    需要在要获取信息的增强方法中添加一个参数 JoinPoint

    @Component
    @Aspect
    public class MyAdvice {   @Before("execution(* com.ztone.impl.*.*(..))")   public void before(JoinPoint joinPoint){       //获取类的信息       String simpleName = joinPoint.getTarget().getClass().getSimpleName();       //获取目标方法名和修饰符       String name = joinPoint.getSignature().getName();       joinPoint.getSignature().getModifiers();       //获取参数列表       Object[] args = joinPoint.getArgs();          }
    }
  • 获取目标方法的返回值,只能在后置增强中获取 @AfterReturning

    • 在后置增强方法中添加一个Object 类型的参数

    • 在@AfterReturning 注解中添加 returning 属性,值就是后置增强方法的形参

    @AfterReturning(value = "execution(* com.ztone.impl.*.*(..))",returning = "returning")
    public void afterReturning(Object returning){   System.out.println(returning);
    }

    返回值就是 后置增强方法的形参

  • 获取目标方法的异常,只能在异常增强中获取 @AfterThrowing

    • 在异常增强方法中添加一个Throwable类型的参数

    • 在 @AfterThrowing 注解中添加 throwing 属性,值是 异常增强方法的形参

    @AfterThrowing(value = "execution(* com.ztone.impl.*.*(..))",throwing = "throwable")
    public void afterThrowing(Throwable throwable){
    ​
    }

 

 

2.4 切面表达式

固定语法:execution(1 2 3.4.5(6))

  1. 访问修饰符

    四个访问修饰符 public、private、default、protect

  2. 方法返回值

    String、int 、void 等

    访问修饰符和返回值 绑定在一起,如果两者都不考虑可以用 * 代替,但是不能只考虑一个而另一个不考虑

  3. 包的位置

    com.ztone.service.impl

    单层模糊:com.ztone.service.* 表示 service包下的所有包

    多层模糊:com..impl 表示com 和 impl 包之间可以有任意层

    但是.. 不能开头,开头只能是包名或 *

  4. 类的名称

    CalculateImpl

    模糊:用 * 代替表示所有类

    部分模糊: *Impl 表示以Impl结尾的类

  5. 方法名称

    和类相同

  6. 形参列表

    没有参数:()

    有具体参数:(String,int)

    模糊参数:(..) 所有参数都行

    部分模糊:(String ..) 第一个参数是String

 

切点表达式的提取和复用:

如果在多个增强方法中的切点表达式相同,那么就可以把切点表达式提取出来,后期修改直接修改一个即可。

使用到的注解是 @Pointcut

在一个方法上使用这个注解,注解的值就是提取的切点表达式,然后再增强方法的注解上调用这个方法即可。

package com.ztone.pointcut;
​
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
​
@Component
public class MyPointcut {   @Pointcut("execution(* com.ztone.impl.*.*(..))")   public void myPc(){}
}

这里是在一个类中提取出来,以保证在其他类中也能调用到该方法

 

@Component
@Aspect
public class LogAdvice {   @Before("com.ztone.pointcut.MyPointcut.myPc()")   public void start(){       System.out.println("方法开始了");   }
​   @AfterReturning("com.ztone.pointcut.MyPointcut.myPc()")   public void after(){       System.out.println("方法结束了");   }
​   @AfterThrowing("com.ztone.pointcut.MyPointcut.myPc()")   public void error(){       System.out.println("方法出错了");   }
}

使用的时候就是用类的全限定符.方法名

 

2.5 环绕通知

环绕通知就是将 前置、后置、异常等通知结合起来。

比如给一个目标方法添加事务,可以在前置通知中开启事务,在后置通知中结束事务,在异常通知中进行事务回滚

需要写三个通知方法

如果使用环绕通知,用一个方法就够了,使用的注解是 @Around

该方法接收一个参数 ProceeedingJoinPoint ,这个参数可以获取到目标方法的信息并且可以执行目标方法,使用proceed()

环绕通知方法必须返回一个Object 类型的返回值,这个值就是执行目标方法返回的值

package com.ztone.advice;
​
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
​
@Component
@Aspect
public class TxAdvice {
​   @Around("com.ztone.pointcut.MyPointcut.myPc()")   public Object txAd(ProceedingJoinPoint joinPoint){       Object[] args = joinPoint.getArgs();       Object result = null;
​       try {           System.out.println("事务开启");           result = joinPoint.proceed(args);           System.out.println("事务结束");       } catch (Throwable e) {           System.out.println("事务回滚");           throw new RuntimeException(e);       }       return result;   }
}

joinPoint.proceed(args); 就是执行目标方法

 

2.6 通知的优先级

如果有多个通知,想要某个通知先执行可以用 @Order 注解来设置通知的优先级,注意@Order的值越小优先级越高

优先级高的通知,前置通知会先执行,后置通知最后执行,相当于把优先级低的通知包裹起来

 

 

3.xml方式实现aop

  1. 首先准备一个增强类

    @Component
    public class LogAdvice {   public void start(){       System.out.println("方法开始了");   }    public void after(){       System.out.println("方法结束了");   }
    ​   public void error(){       System.out.println("方法出错了");   }
    }

    一定要使用@Component 加入到aop容器中

  2. 在xml标签中配置

    • 在最外层用 <aop:config > 标签包裹所有的aop配置

    • 声明切点标签,使用 <aop:pointcut > 相当于 @PointCut 注解

      • id:切点的标识符

      • expression:切点表达式

    • 配置切面 使用 <aop:aspect > 相当于 @Aspect 注解

      • ref :引用的增强类

      • order:切面的优先级

      • 在标签里面使用

        • <aop:before > 表示前置增强

          • method:增强类中的前置增强方法

          • pointcut-ref:引用的切点,或者可以使用 pointcut直接指定切点表达式

        • <aop:after-running > 前两个同理,returning属性指定后置方法的返回值

        • <aop:after-throwing > 前两个同理,throwing属性指定后置方法的返回值

<?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"      xmlns:context="http://www.springframework.org/schema/context"      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">      <context:component-scan base-package="com.ztone"/>      <aop:config>       <aop:pointcut id="myPc" expression="execution(* com.ztone.service.impl.*.*(..))"/>
​       <aop:aspect ref="txAdvice" order="5">           <aop:before method="before" pointcut-ref="myPc"/>           <aop:after-returning method="afterReturning" pointcut-ref="myPc" returning="returning"/>           <aop:after-throwing method="afterThrowing" pointcut-ref="myPc" throwing="t"/>       </aop:aspect>   </aop:config>
​
</beans>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/780129.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OneKeyAdmin 后台任意文件下载

无它唯手熟尔,还是太菜了侵权声明 本文章中的所有内容(包括但不限于文字、图像和其他媒体)仅供教育和参考目的。如果在本文章中使用了任何受版权保护的材料,我们满怀敬意地承认该内容的版权归原作者所有。 如果您是版权持有人,并且认为您的作品被侵犯,请通过以下方式与我…

适合新手进行接口与自动化测试练习的推荐网站!!!

对于新手来说,接口与自动化测试练习网站是提升技能的重要资源。以下是一些适合新手进行接口与自动化测试练习的推荐网站: 1、httpbin.org httpbin.org是一个在线提供HTTP服务的网站,它提供了丰富的HTTP接口,用于测试HTTP请求和响应的各种信息,如cookie、IP、headers和登录…

《最新出炉》系列小成篇-Python+Playwright自动化测试-67 - 模拟手机浏览器兼容性测试

1.简介 在日常工作中,我们会遇到需要使用不同的硬件设备测试兼容性的问题,尤其是现在手机型号基本上是每个厂家每年发布一款新机型,而且手机的屏幕大小分辨率五花八门的,我们基本不可能全部机型都用真机测试一遍,playwright提供了模仿机型的功能,我们可以使用playwright来…

在IIS上部署ASP.NET Core Web API和Blazor Wasm详细教程

前言 前段时间我们完成了七天.NET 8 操作 SQLite 入门到实战的开发系列教程,有不少同学留言问如何将项目发布部署到IIS上面运行。本篇文章我们就一起来讲讲在IIS上部署ASP.NET Core Web API和Blazor Wasm。 前提条件 安装.NET Core SDKhttps://dotnet.microsoft.com/zh-cn/dow…

Sublime Text 4 如何配置成完美的OI编辑器,优雅的打比赛

Sublime Text 完整配置编译文件点击新建编译系统(Tools -> Build system -> New build system)改为以下代码: Ubuntu : {"encoding": "utf-8","working_dir": "$file_path","shell_cmd": "g++ \"$file_name…

打造个性化Typora写作体验的必备插件

该文档为 typora_plugin 的 README 文档,为了方便国内的同学阅读而转载到本博客。该文档为 typora_plugin 的 README 文档,为了方便国内的同学阅读而转载到本博客。 原文地址:https://github.com/obgnail/typora_plugin 插件地址:可以去 GitHub 上下载,也可以在我的公众号…

使用蓝奏云备份大于一百兆的压缩包——分卷,手机

开源软件地址:Flut Renamer 1.分卷 2.更名 3.上传成功。

读零信任网络:在不可信网络中构建安全系统13运行时安全

读零信任网络:在不可信网络中构建安全系统13运行时安全1. 建立分发系统的信任 1.1. 分发是选择交付给下游使用者的工件的过程,构建系统会产生许多工件,其中一部分需要交付给下游使用者 1.2. 工件发布1.2.1. 在不改变工件内容的情况下指定一个工件作为权威发布版本的操作被称…

暑假模拟15

暑假模拟15 \(T_A\) 串串 简单字符串题,考虑枚举回文中心,哈希维护,没什么思维量,但是不好调。 wang54321 有高级 manacher 做法,大家快去膜拜她。 #include<bits/stdc++.h> using namespace std; #define ll long long const int N=1e6+100; const ll mod=10000010…

Kubernetes-Argo CD

Kubernetes高级工具Argo CD https://argo-cd.readthedocs.io/en/stable/ Argo CD 是针对 Kubernetes 的声明式 GitOps 持续交付工具。 Argo CD 是一个为 Kubernetes 而生的,遵循声明式 GitOps 理念的持续部署(CD)工具。 Argo CD 可在 Git 存储库更改时自动同步和部署应用程序…

Kubernetes高级工具Argo CD

Kubernetes高级工具Argo CD https://argo-cd.readthedocs.io/en/stable/ Argo CD 是针对 Kubernetes 的声明式 GitOps 持续交付工具。 Argo CD 是一个为 Kubernetes 而生的,遵循声明式 GitOps 理念的持续部署(CD)工具。 Argo CD 可在 Git 存储库更改时自动同步和部署应用程序…