首页 > 编程语言 >AOP -面向切面编程

AOP -面向切面编程

时间:2024-08-09 08:54:48浏览次数:14  
标签:ztone int com 编程 通知 切面 AOP 方法 public

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>

标签:ztone,int,com,编程,通知,切面,AOP,方法,public
From: https://www.cnblogs.com/wztblogs/p/18350107

相关文章

  • 哪种编程语言更适合学习数据结构和算法:C++、Java 还是 Python?
    作为一名工程专业的学生,​​我正在尝试决定使用哪种编程语言来学习数据结构和算法(DSA)。我正在考虑C++,它提供高性能和强大的标准模板库,但对于初学者来说可能很复杂。Java具有强大的语法和内置集合,使DSA概念更容易掌握,尽管我不确定它与C++相比的性能。Python以其简单性和......
  • 函数式编程-Stream流
    一、函数式编程-Stream流1、概述1.1为什么学?能够看懂公司里的代码大数量下处理集合效率高代码可读性高消灭嵌套地狱普通写法与函数式编程写法对比:普通写法//查询未成年作家的评分在70以上的书籍由于洋流影响所以作家和书籍可能出现重复,需要进行去重List<B......
  • 51单片机之模块化编程
    一、模块化编程与传统方式编程的区别传统方式编程:在这种编程方式中,所有的函数通常都被放置在同一个文件main.c中。当项目中使用的模块较多时,这个文件中会包含大量的代码,导致代码难以组织和管理,也影响了编程者的思路。这种方式缺乏清晰的结构划分,使得代码的可读性和可维护性降......
  • Java设计模式和AOP编程
    Java六大设计原则;Java23种设计模式(在此介绍三种设计模式)Java设计模式单例模式应用场景:spring中bean的作用域用的就是单例模式//基本的单例模式————懒汉式publicclassstudent{//3.创建static修饰的成员变量privatestaticstudentstu;//1.设计私......
  • 不需要学编程,自制自己的操作系统!一个0基础自制操作系统的软件!详细教程!
    不需要学编程,自制自己的操作系统!一个0基础自制操作系统的软件!详细教程创建.py文件,内容如下fromtkinterimport*code='\n[org0x7c00]\n\nstart:\n\t\n\tmovbp,0x8000\n\tmovsp,bp\n\n\t\n\tmovax,0x0600\n\tmovbx,0x0700\n\tmovcx,0\n\tmovdx,0x184f\n\tint......
  • Java多线程编程中的常见问题及优化策略
    Java多线程编程中的常见问题及优化策略大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!多线程的基本概念在Java中,多线程是指程序中可以同时运行多个线程,每个线程可以执行不同的任务。多线程可以提高程序的执行效率,但同时也带来了一些挑战。线程安全......
  • 2024睿抗机器人开发者大赛(RAICOM) CAIP编程技能赛 国一
    最后91分,国一。前几题都AK了,最后一题先是输出0,得了个1分。花了一个小时都没解决这题,难受ing,其实到最后差不多要改对了(降落那一部分没时间改),但是没时间了,hhhh。拿到国一,简直圆梦啦!!!本科拿的国三,差0.02秒就是国二,从此内心蒙上阴影。哭死ing研一终于拿了个编程比赛的国一,也算......
  • 【人工智能】【机器学习】-好书推荐之《Python神经网络编程》
    目录内容概览编程环境面向对象学习目标如果你是想要自学机器学习相关知识的读者,我相信看完这篇文章的介绍后,你会对机器学习有更清晰的认识。帮助你走进机器学习的殿堂。《Python神经网络编程》(原书名:MakeYourOwnNeuralNetwork)是一本深度学习领域的入门级书籍,由Tar......
  • 系统编程 day5 文件4
    函数time(time_t*tloc),返回值为time_t;可以读取秒数函数ctime(consttime_t*timep),返回值为获得时间字符串首地址,char*可以将秒数转化为年月日时分秒函数localtime structtm*tm_info=localtime(&tm);返回本地实时时间命令函数:软链接函数symlink(传参:被链接,新链接);返......
  • 结队编程 - 华为OD统一考试(D卷)
    OD统一考试(D卷)分值:200分题解:Java/Python/C++题目描述某部门计划通过结队编程来进行项目开发,已知该部门有N名员工,每个员工有独一无二的职级,每三个员工形成一个小组进行结队编程,结队分组规则如下:从部门中选出序号分别为i、j、k的3名员工,他们的职级分别为le......