【Spring AOP】Spring AOP 详解

news2024/11/15 12:13:32

Spring AOP 详解

  • 一. 什么是 AOP
  • 二. AOP 组成
    • 切面(Aspect)
    • 连接点(Join Point)
    • 切点(Pointcut)
    • 通知(Advice)
  • 三. Spring AOP 实现
    • 1. 添加 AOP 框架⽀持
    • 2. 定义切面和切点
    • 3. 定义相关通知
  • 四. Spring AOP 实现原理
    • 动态代理
    • JDK 和 CGLIB 实现的区别

一. 什么是 AOP

AOP(Aspect Oriented Programming):⾯向切⾯编程,它是⼀种思想,它是对某⼀类事情的集中处理。

⽐如⽤户登录权限的校验,没学 AOP 之前,我们所有需要判断⽤户是否已经登录的⻚⾯,都要各⾃实现或调⽤⽤户验证的⽅法,然⽽有了 AOP 之后,我们只需要在某⼀处配置⼀下,所有需要判断⽤户是否登录⻚⾯就全部可以实现⽤户登录验证了,不再需要每个⽅法中都调用相同的⽤户登录验证了。

⽽ AOP 是⼀种思想,⽽ Spring AOP 是⼀个框架,提供了⼀种对 AOP 思想的实现,它们的关系和 IoC 与 DI 类似。

除了统⼀的⽤户登录判断之外,AOP 还可以实现:

  • 统⼀⽇志记录
  • 统⼀⽅法执⾏时间统计
  • 统⼀的返回格式设置
  • 统⼀的异常处理
  • 事务的开启和提交等

也就是说使⽤ AOP 可以扩充多个对象的某个能⼒,所以 AOP 可以说是 OOP(Object Oriented Programming,⾯向对象编程)的补充和完善。

二. AOP 组成

切面(Aspect)

可以简单理解为一个解决某方面具体问题的一个类,类里面包含很多方法,这些方法就是切点和通知。

切⾯是包含了:通知、切点和切⾯的类,相当于 AOP 实现的某个功能的集合。

切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。

连接点(Join Point)

简单理解就是被拦截,触发动态代理的方法。
应⽤执⾏过程中能够插⼊切⾯的⼀个点,这个点可以是⽅法调⽤时,抛出异常时,甚⾄修改字段时。切⾯代码可以利⽤这些点插⼊到应⽤的正常流程之中,并添加新的⾏为。
连接点相当于需要被增强的某个 AOP 功能的所有⽅法。(即被增强的方法)

切点(Pointcut)

简单理解就是所有连接点的集合。
Pointcut 是匹配 Join Point 的谓词。
Pointcut 的作⽤就是提供⼀组规则(使⽤ AspectJ pointcut expression language 来描述)来匹配 Join Point,给满⾜规则的 Join Point 添加 Advice。
切点相当于保存了众多连接点的⼀个集合(如果把切点看成⼀个表,⽽连接点就是表中⼀条⼀条的数据)。

通知(Advice)

简单理解就是要增加的具体内容。

切⾯也是有⽬标的 ——它必须完成的⼯作。在 AOP 术语中,切⾯的⼯作被称之为通知。

通知:定义了切⾯是什么,何时使⽤,其描述了切⾯要完成的⼯作,还解决何时执⾏这个⼯作的问题。
Spring 切⾯类中,可以在⽅法上使⽤以下注解,会设置⽅法为通知⽅法,在满⾜条件后会通知本⽅法进⾏调⽤:

  • 前置通知使⽤ @Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
  • 后置通知使⽤ @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
  • 返回之后通知使⽤ @AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
  • 抛异常后通知使⽤ @AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
  • 环绕通知使⽤ @Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执⾏⾃定义的⾏为。

具体执行的先后顺序:

在这里插入图片描述

AOP 整个组成部分的概念如下图所示,以多个⻚⾯都要访问⽤户登录权限为例:

在这里插入图片描述

三. Spring AOP 实现

接下来我们使⽤ Spring AOP 来实现⼀下 AOP 的功能,完成的⽬标是拦截所有 UserController ⾥⾯的⽅法,每次调⽤ UserController 中任意⼀个⽅法时,都执⾏相应的通知事件。
Spring AOP 的实现步骤如下:

  1. 添加 Spring AOP 框架⽀持。
  2. 定义切⾯和切点。
  3. 定义通知。

1. 添加 AOP 框架⽀持

在 pom.xml 中添加如下配置:

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2. 定义切面和切点

切点指的是具体要处理的某⼀类问题,⽐如⽤户登录权限验证就是⼀个具体的问题,记录所有⽅法的执⾏⽇志就是⼀个具体的问题,切点定义的是某⼀类问题。
Spring AOP 切点的定义如下,在切点中我们要定义拦截的规则,具体实现如下:

@Aspect // 表明此类为⼀个切⾯
@Component
public class UserAspect {
    // 定义切点,这⾥使⽤ AspectJ 表达式语法
    @Pointcut("execution(* com.example.springboot3.controller.UserController.* (..))")
    public void pointcut(){ }
}

其中 pointcut ⽅法为空⽅法,它不需要有⽅法体,此⽅法名就是起到⼀个“标识”的作⽤,标识下⾯的通知⽅法具体指的是哪个切点(因为切点可能有很多个)。

切点表达式说明:

AspectJ ⽀持三种通配符
*:匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
. .:匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。
+:表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的所有⼦类包括本身

切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:

execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)

修饰符和异常可以省略,具体含义如下:

在这里插入图片描述
表达式示例

execution(* com.cad.demo.User.*(…)) :匹配 User 类⾥的所有⽅法。
execution(* com.cad.demo.User+.*(…)) :匹配该类的⼦类包括该类的所有⽅法。
execution(* com.cad.*.*(…)) :匹配 com.cad 包下的所有类的所有⽅法。
execution(* com.cad. .*.*(…)) :匹配 com.cad 包下、⼦孙包下所有类的所有⽅法。
execution(* addUser(String, int)) :匹配 addUser ⽅法,且第⼀个参数类型是 String,第⼆个参数类型是 int。

3. 定义相关通知

通知定义的是被拦截的⽅法具体要执⾏的业务,⽐如⽤户登录权限验证⽅法就是具体要执⾏的业务。
Spring AOP 中,可以在⽅法上使⽤以下注解,会设置⽅法为通知⽅法,在满⾜条件后会通知本
⽅法进⾏调⽤:

  • 前置通知使⽤@Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
  • 后置通知使⽤@After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
  • 返回之后通知使⽤@AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
  • 抛异常后通知使⽤@AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
  • 环绕通知使⽤@Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执⾏⾃定义的⾏为。

具体实现如下:

@Aspect
@Component
public class UserAspect {
    // 定义切点⽅法
    @Pointcut("execution(* com.example.springboot3.controller.UserController.* (..))")
    public void pointcut(){ }

    // 前置通知
    @Before("pointcut()") // 表示针对哪个切点的增强
    public void doBefore(){
        System.out.println("执⾏ Before ⽅法");
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter(){
        System.out.println("执⾏ After ⽅法");
    }

    // return 之前通知
    @AfterReturning("pointcut()")
    public void doAfterReturning(){
        System.out.println("执⾏ AfterReturning ⽅法");
    }

    // 抛出异常之前通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing(){
        System.out.println("执⾏ doAfterThrowing ⽅法");
    }

    // 添加环绕通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        Object obj = null;
        System.out.println("Around ⽅法开始执⾏");
        try {
            // 执⾏被拦截的⽅法
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("Around ⽅法结束执⾏");
        return obj;
    }
}

经过以上的代码我们就能实现 Spring AOP 了。

对于 Spring AOP 的理解:
在这里插入图片描述

你可能有点疑惑, 为什么这个 @Around 环绕通知 不一样, 代码这么多, 需要执行被拦截的方法, 而其他通知不用 ?

因为其他的通知虽然在方法执行前后会执行一些特定的逻辑, 但是不会阻止目标方法的执行.

而这个 @Around 环绕通知 就不一样了, 他会阻止目标方法的执行, 所以他需要自己在合适的时机调用目标方法,方法执行完还有有一个返回值, 我们还要将这个值返回出去。

四. Spring AOP 实现原理

Spring AOP 是构建在动态代理基础上,代理对象通过运行时生成,这些代理对象会包装目标对象。当客户端调用代理对象的方法时,代理对象可以在方法执行前、方法执行后或抛出异常时执行额外的逻辑。 因为代理对象不能直接操作目标对象的字段或属性。因此 Spring 对 AOP 的⽀持局限于⽅法级别的拦截。

Spring AOP ⽀持 JDK Proxy 和 CGLIB ⽅式实现动态代理。默认情况下,实现了接⼝的类,使⽤ AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。

在这里插入图片描述

Spring 的切面由包裹了目标对象的代理类实现. 代理类处理方法的调用, 执行额外的切面逻辑并调用目标方法.

织⼊(Weaving):代理的⽣成时机

织⼊是把切⾯应⽤到⽬标对象并创建新的代理对象的过程,切⾯在指定的连接点被织⼊到⽬标对象中。

在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:

  • 编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就是以这种⽅式织⼊切⾯的。
  • 类加载期:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器(ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊切⾯。
  • 运⾏期:切⾯在应⽤运⾏的某⼀时刻被织⼊。⼀般情况下,在织⼊切⾯时,AOP容器会为⽬标对象动态创建⼀个代理对象。SpringAOP就是以这种⽅式织⼊切⾯的。

动态代理

在运行期织入这种设计模式上称为动态代理模式,在实现的技术⼿段上,都是在 class 代码运⾏期,动态的织⼊字节码,因此叫做动态代理。
Spring 框架中的AOP,主要基于两种⽅式:JDK 及 CGLIB 的⽅式。这两种⽅式的代理⽬标都是被代理类中的⽅法,在运⾏期,动态的织⼊字节码⽣成代理类。(并且都是基于反射)

JDK 动态代理实现:

  • JDK 实现时,先通过实现 InvocationHandler 接⼝创建⽅法调⽤处理器,其中的参数是被代理对象,所以实现 InvocationHandler 接口的类中含有 被代理对象。
  • 通过 Proxy 来创建代理类。参数为被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建一个代理类。
  • 代理类和被代理类实现了相同的接口,那么代理类就可以有与被代理类相同的方法,因为调用被代理类是通过接口调用的,所以完全可以像调用被代理类一样去调用代理类。(注意并没有方法的真正实现,只是对外表现出具有相同的方法,当调用时会触发 InvocationHandler 的 invoke 方法 去真正执行)
  • 当调用代理类时,将触发 InvocationHandler 的 invoke 方法,并将被调用的方法传过去,然后 在 invoke 中会执行 额外的逻辑以及 被代理类真正要执行的方法。

在这里插入图片描述

所以代理类并没有真正实现这些方法,而是对外表现出具有与代理类相同的方法,当被调用时,触发 InvocationHandler 去真正执行,执行额外的逻辑以及被代理类真正要执行的方法。

以下为伪代码实现:

public class PayServiceJDKInvocationHandler implements InvocationHandler {

    //⽬标对象即就是被代理对象
    private Object target;

    public PayServiceJDKInvocationHandler( Object target) {
        this.target = target;
    }

    //proxy代理对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1.安全检查
        System.out.println("安全检查");
        //2.记录⽇志
        System.out.println("记录⽇志");
        //3.时间统计开始
        System.out.println("记录开始时间");
        //通过反射调⽤被代理类的⽅法
        Object retVal = method.invoke(target, args);
        //4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }
    public static void main(String[] args) {
        PayService target= new AliPayService();
        //⽅法调⽤处理器
        InvocationHandler handler =
                new PayServiceJDKInvocationHandler(target);
        //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
        PayService proxy = (PayService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{PayService.class},
                handler
        );
        proxy.pay();
    }
}

CGLIB 实现动态代理:
CGLIB 实现动态代理的原理与 JDK 动态代理有相似之处,但也有一些关键区别。以下是类似的描述 CGLIB 实现动态代理的过程:

  • 创建代理类: 在CGLIB中,首先通过字节码生成库创建一个代理类。这个代理类是被代理类的子类,不需要实现接口。CGLIB会生成代理类的字节码,这个字节码包含了代理类的方法和属性。

  • 重写方法: CGLIB会在代理类中重写被代理类的方法。这些重写的方法将委托给代理处理器(通常是用户自定义的MethodInterceptor)来处理方法调用。

  • 代理对象的创建: 当你使用CGLIB创建代理对象时,CGLIB会生成一个代理类的实例。这个代理对象是代理类的一个实例,因此它继承了被代理类。

  • 方法调用的委托: 当客户端代码调用代理对象的方法时,方法在内部会委托给代理处理器,也就是MethodInterceptor,来处理方法调用。代理处理器决定如何处理方法调用,可以在方法调用前后添加额外的逻辑,也可以选择调用被代理对象的方法。

在这里插入图片描述

以下为伪代码实现:

public class PayServiceCGLIBInterceptor implements MethodInterceptor {
    //被代理对象
    private Object target;

    public PayServiceCGLIBInterceptor(Object target){
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //1.安全检查
        System.out.println("安全检查");
        //2.记录⽇志
        System.out.println("记录⽇志");
        //3.时间统计开始
        System.out.println("记录开始时间");
        //通过cglib的代理⽅法调⽤
        Object retVal = methodProxy.invoke(target, args);
        //4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }

    public static void main(String[] args) {
        PayService target= new AliPayService();
        PayService proxy= (PayService) Enhancer.create(target.getClass(),
                new PayServiceCGLIBInterceptor(target));
        proxy.pay();
    }
}

JDK 和 CGLIB 实现的区别

  1. JDK 实现,要求被代理类必须实现接⼝,之后是通过 InvocationHandler 及 Proxy,在运⾏时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完成。

  2. CGLIB 实现,被代理类可以不实现接⼝,是通过继承被代理类,那么子类就拥有父类的所有方法, 在运⾏时动态的⽣成代理类对象。所以 被 final 修饰的最终类不能通过 CGLIB 实现动态代理。

总的来说,CGLIB与JDK动态代理的主要区别在于代理方式。CGLIB创建一个子类来充当代理对象,该子类继承自被代理类,而JDK动态代理则创建一个实现相同接口的代理类。CGLIB通过字节码生成技术实现,因此能够代理普通类,而不仅限于实现接口的类。与JDK动态代理相比,CGLIB的动态代理方式更加灵活,但也更复杂。

好啦! 以上就是对 Spring AOP 的讲解,希望能帮到你 !
评论区欢迎指正 !

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nginx windows安装部署,代理转发配置

一、安装 1、nginx官网下载 windows版本 nginx官网 下载后解压到本地 2、在nginx的配置文件是conf目录下的nginx.conf&#xff0c;默认配置的nginx监听的端口为80&#xff0c;如果本地电脑的80端口有被占用&#xff0c;如果本地80端口已经被使用则修改成其他端口。如下&…

【吴恩达深度学习】

第一周 1、修正线性单元ReLU 第二周、Logistic回归 1、样本矩阵X&#xff1a; 是一个m*nx的矩阵&#xff0c;表示m个样本&#xff08;一个竖列代表一个样本&#xff09;&#xff0c;每个样本有nx个特征。 2、标签矩阵Y&#xff1a;[y1,y2,y3,ym] m个训练样本分别对应的标签…

C++程序员必修第一课【C++基础课程】01:安装C++开发环境

1 本课主要内容&#xff1a; 了解 C 开发相关基础概念学会在 Windows10 上安装 Visual Studio 2019免费社区版新建第一个 "Hello World" C 程序&#xff0c;验证 C 开发环境 2 主要知识点&#xff1a; C 开发环境是同时指操作系统和C开发工具操作系统主要有 Window…

软件开发人员 Kubernetes 入门指南|Part 1

Kubernetes 是一个用于部署和管理容器的编排系统。使用 Kubernetes&#xff0c;用户可以通过自动执行管理任务&#xff08;例如在跨节点间扩展容器并在容器停止时重新启动任务&#xff09;&#xff0c;在不同环境中可靠地运行容器。 Kubernetes 提供的抽象可以让你从 Pod&am…

二维码智慧门牌管理系统:提升社会治理水平,创新市民服务方式

文章目录 前言一、适应市域社会治理现代化二、解决地名地址管理核心问题三、拓展多元化服务 前言 随着科技的不断发展&#xff0c;社会治理的方式和方法也在不断更新和升级。近年来&#xff0c;市域社会治理现代化已成为社会发展的必然趋势&#xff0c;而二维码智慧门牌管理系…

011:获取上证50的所有股票代码,并下载各个股K线数到excel表中

我们结合《获取上证50的所有股票代码》&#xff0c;《根据股票代码和起始日期获取K线数据到excel表》两文中的脚本&#xff0c;搞出新的脚本&#xff1a; import tkinter as tk from tkinter import messagebox from tkcalendar import Calendar import pandas as pd import…

阿里8年经验之谈 —— 如何编写有效的接口测试?

阿里妹导读&#xff1a;在所有的开发测试中&#xff0c;接口测试是必不可少的一项。有效且覆盖完整的接口测试&#xff0c;不仅能保障新功能的开发质量&#xff0c;还能让开发在修改功能逻辑的时候有回归的能力&#xff0c;同时也是能优雅地进行重构的前提。编写接口测试要遵守…

web前端面试-- js深拷贝的一些bug,特殊对象属性(RegExp,Date,Error,Symbol,Function)处理,循环引用weekmap处理

本人是一个web前端开发工程师&#xff0c;主要是vue框架&#xff0c;整理了一些面试题&#xff0c;今后也会一直更新&#xff0c;有好题目的同学欢迎评论区分享 ;-&#xff09; web面试题专栏&#xff1a;点击此处 文章目录 深拷贝和浅拷贝的区别浅拷贝示例深拷贝示例 特殊对象…

LeetCode-145-二叉树的后序遍历

文章目录 递归法非递归法 题目描述&#xff1a; 题目链接&#xff1a;LeetCode-145-二叉树的后序遍历 递归法 解题思路&#xff1a;递归&#xff0c;具体可以参考 LeetCode-144-二叉树的前序遍历 代码实现&#xff1a; class Solution {List<Integer> listnew ArrayList…

数据结构中常见的排序及其代码C语言版本

这里非常抱歉 没有做好动图 等我做好了动态单独做一期博客到时候大家结合动图将本篇博客联系起来看 更容易理解一些喔 常见的数据结构中的排序算法有冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序等。 冒泡排序&#xff1a;比较相邻的元素&#xff0c;…

京东数据分析软件:京东销售数据在哪里看?

京东数据分析可以帮助企业了解京东平台上的销售数据和用户行为数据&#xff0c;以便更有效地制定营销策略和优化经营。例如&#xff0c;通过分析京东销量数据&#xff0c;店家可以了解自己店铺的销售情况&#xff0c;发现问题并及时调整。这对于店家来说具有重要的作用&#xf…

塔望食观察 | 中国海参产业发展现状及挑战

海参&#xff0c;一个古老的物种&#xff0c;堪称海底活化石&#xff0c;据资料显示&#xff0c;海参在地球上存活超过6亿年&#xff0c;比恐龙还早。海参的药用、食疗和营养滋补价值极高&#xff0c;清朝学者赵学敏编的《本草纲目拾遗》有这样的叙述&#xff1a;“海参性温补&…

SpringBoot通过配置切换注册中心(多注册中心nacos和eureka)

场景&#xff1a; 因项目需要&#xff0c;一个springcloud微服务工程需要同时部署到A,B两个项目使用&#xff0c;但A项目使用Eureka注册中心&#xff0c;B项目使用Nacos注册中心&#xff0c;现在需要通过部署时修改配置来实现多注册中心的切换。 解决思路&#xff1a; 如果同时…

算法通关村第17关【黄金】| 跳跃游戏问题

1. 跳跃游戏 思路&#xff1a;每个nums[i]i代表当前最后覆盖范围&#xff0c;这里和字符串分割那道题一样求覆盖区间&#xff0c;取cur的最大值 class Solution {public boolean canJump(int[] nums) {int len nums.length;if(len 1){return true;}int cur 0;for(int i 0;…

如何建立线上线下相结合的数字化新零售体系?

身处今数字化时代&#xff0c;建立线上线下相结合的数字化新零售体系是企业成功的关键。蚓链数字化营销系统致力于帮助企业实现数字化转型&#xff0c;打通线上线下销售渠道&#xff0c;提升品牌影响力和用户黏性&#xff0c;那么具体是如何建立的&#xff1f; 1. 搭建数字化中…

虹科分享 | 确保冻干工艺开发中精确测量和数据完整性的5步指南

虹科分享 | 确保冻干工艺开发中精确测量和数据完整性的5步指南 介绍 冻干周期的工艺开发在冻干中起着至关重要的作用&#xff0c;因为它可以优化关键工艺参数&#xff0c;以实现理想的产品质量和工艺一致性。优化冻干工艺还可以缩短运行时间&#xff0c;尽早发现关键错误&…

基于threejs的3d可视化工厂示例

源码下载地址在文章末尾&#xff01; 效果演示 总览 内部设备 选中机器展示数据 源码下载地址 https://download.csdn.net/download/qq_43185384/88415706

torch-scatter安装失败解决办法

安装torch-scatter时可以直接下载whl安装&#xff0c;但是会遇到版本不一致的问题&#xff0c;虽然可以安装但是会提示.so文件不存在或者undefined symbol: 的问题&#xff0c;可以在下面的地址中找到跟自己的torch和cuda版本对应的torch-scatter安装文件。 https://pytorch-ge…

我的AI存储实践及思考

前言 我从2020年进入人工智能行业&#xff0c;开始为AI训练提供存储解决方案及技术支持&#xff0c;随着这几年行业以及公司的发展&#xff0c;采用的存储方案经历了几次大的演变 &#xff0c;最开始使用的是分布式并行文件系统Lustre&#xff0c;接着是BeeGFSCeph&#xff0c…

leetcode(力扣) 221. 最大正方形(动态规划)

文章目录 题目描述思路分析完整代码 题目描述 在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内&#xff0c;找到只包含 ‘1’ 的最大正方形&#xff0c;并返回其面积。 输入&#xff1a;matrix [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1…