当前位置: 首页 > news >正文

Spring AOP相关知识详解

文章目录

  • 1.AOP介绍
    • 1.1 面向切面编程 - Aspect Oriented Programming (AOP)
    • 1.2 优点
  • 2.AOP的概念
    • 2.1 连接点、切入点、通知、切面:
    • 2.2 注解
      • 2.2.1 通知类型
        • 2.2.1.1 通知的优先级排序
      • 2.2.2 其他重要注解
      • 2.2.3 示例代码(四种通知)
  • 3.Spring AOP配置方式
  • 4.AOP的应用
    • 4.1 日志
    • 4.2 异常处理
    • 4.3 在spring中aop的应用是事务
      • 4.3.1 事务的传播行为
      • 4.3.2 事务的隔离级别
      • 4.3.3 事务的配置方式
    • 4.4 在具体项目中的应用
  • 5.aop底层实现
    • 5.1 动态代理
      • 5.1.1 JDK动态代理(实现和被代理类一样的接口)
      • 5.1.2 CGLIB动态代理(创建目标类的子类)
      • 5.1.3 两种代理的区别
    • 5.2 后置处理器相关
      • 5.2.1 后置处理器
      • 5.2.2 增强器(封装切面的信息)
      • 5.2.3 在 getBean 时的创建流程
  • 6.设计模式
    • 6.1 代理模式
    • 6.2 责任链模式(依次调用增强器的通知方法)
  • 7.aop失效
    • 7.1 aop失效的场景
      • 7.1.1 同一个类内部中,不同方法间的调用
      • 7.1.2 多线程环境下,线程直接调用目标对象方法
      • 7.1.3 final 修饰的方法不能被重写
      • 7.1.4 private方法由于访问权限
    • 7.2 解决方法

1.AOP介绍

1.1 面向切面编程 - Aspect Oriented Programming (AOP)

AOP,作为OOP面向对象编程的一种功能补充。AOP是Spring框架重要的组件,作为IOC的补充。

是一种思想,本质是为了解耦。将横切逻辑代码抽取出去,通过简单的配置,使用动态代理的方式添加到目标方法中,做到无侵入、便捷。

Spring 框架通过定义切面, 通过拦截切点实现了不同业务模块的解耦。面向切面编程是一种编程范式,主要用于分离那些横切多个业务模块的关注点,比如日志记录、事务管理、权限验证等这些功能就属于横切关注点。

关注点是指在软件开发过程中,系统中某个特定的业务功能、特性、需求或者设计,是开发人员重点关注、考虑、实现的部分。

横切关注点是跨越多个模块、多个层次或者多个业务功能的关注点

1.2 优点

横向解决代码重复的问题
(1)解耦横切关注点:将与业务逻辑不直接相关的功能,横切的功能解耦。如日志记录、事务管理、权限验证等从业务代码中分离出来,使得业务逻辑更加清晰,易于理解和维护。

(2)代码复用:多个业务模块可以共用同一个切面,避免了代码的重复编写。

(3)灵活、可扩展:可以方便地添加、删除或修改切面的逻辑,而不需要对业务逻辑进行大规模的改动。

2.AOP的概念

我之前的一篇博文写的

2.1 连接点、切入点、通知、切面:

(1)连接点:可以成为切入点

(2)切入点(在哪干):需要增强的目标方法。用来确定在哪些地方应用切面的逻辑,比如可以定义在某个类的所有方法上,或者某个包下的特定方法上。

(3)通知(干什么):是定义在切面中的一段代码,指定了在切面的切点位置上要执行的具体动作逻辑。(什么时候做、做什么)。通知是在目标方法执行前、后执行的方法。通知用于在目标方法执行的特定时机插入额外的行为,从而实现对目标对象方法的增强或扩展。通知是在目标方法执行前、后执行的方法。比如前置通知可以在目标方法执行前做一些事情,后置通知在目标方法执行后做一些事情,环绕通知则可以在目标方法执行前后都进行相应的操作。

(4)切面=切入点+通知。切面是对横切关注点的抽象,它把切点和通知组合在一起。

(5)织入:织入把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。就是通过动态代理对目标对象方法进行增强的过程。运行时织入。

(6)目标对象:被一个或者多个切面所通知的对象,也被称作被通知对象。Spring AOP是通过运行时代理实现的,所以这个对象永远是一个被代理的对象。

2.2 注解

2.2.1 通知类型

(1)@Before 前置通知(Before Advice)
定义:在目标方法执行之前执行的通知。
作用:常用于进行一些准备工作,如参数校验、权限判断、初始化操作等。例如,在一个用户登录的方法前,使用前置通知可以进行用户名和密码的非空校验,若校验不通过则直接返回错误信息,不执行后续的登录逻辑。
(2)@After 后置通知(After Advice)
定义:在目标方法执行之后执行的通知,无论目标方法是否抛出异常都会执行。
作用:可用于进行一些资源清理、日志记录等操作。比如在一个文件上传的方法执行后,使用后置通知可以关闭文件流,释放相关资源,或者记录文件上传的结果信息,无论上传是否成功都可以进行相应的记录。
(3)@AfterReturning 正常返回通知(After Returning Advice)
定义:在目标方法正常执行完成并返回结果后执行的通知。
作用:可以对目标方法的返回值进行处理或记录。例如,在一个查询数据库获取用户信息的方法后,使用返回通知可以对查询到的用户信息进行加密处理,或者记录查询结果以便进行数据分析。
(4)@After-Throwing 异常通知(After Throwing Advice)
定义:在目标方法执行过程中抛出异常时执行的通知。
作用:主要用于处理异常情况,如记录异常信息、进行异常的统一处理或回滚事务等。比如在一个数据更新的方法中,如果出现数据库连接异常或数据冲突异常,异常通知可以捕获并记录这些异常,同时可以根据异常类型进行相应的处理,如提示用户重新操作或回滚到之前的状态。
(5)@Around 环绕通知(Around Advice)
定义:环绕通知可以在目标方法执行前后都添加自定义逻辑,它将目标方法的执行包裹在其中,是最强大也最灵活的一种通知类型。环绕通知是最常用的通知类型。
作用:可以决定目标方法是否执行、何时执行以及如何执行,还可以对目标方法的参数和返回值进行修改。例如,在一个远程服务调用的方法上添加环绕通知,可以在调用前进行网络连接的检查和参数的预处理,在调用后对返回结果进行缓存处理或错误处理等 。

@Aspect
public class PerformanceAspect {@Around("execution(* com.example.service..*.*(..))")public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {long startTime = System.currentTimeMillis();Object result = pjp.proceed();long endTime = System.currentTimeMillis();System.out.println("方法执行时间: " + (endTime - startTime) + " 毫秒");return result;}
}
2.2.1.1 通知的优先级排序

优先级:异常通知>后置通知>前置通知>后置返回通知。
(只要异常通知报错,无论前置通知、后置通知、后置返回通知报错,都将返回的是异常通知)

如果有多个通知想要在同一连接点运行会发生什么?
在“进入”连接点的情况下,最高优先级的通知会先执行(所以给定的两个前置通知中,优先级高的那个会先执行)。
在“退出”连接点的情况下,最高优先级的通知会最后执行。(所以给定的两个后置通知中, 优先级高的那个会第二个执行)。

2.2.2 其他重要注解

@Aspect:用来定义一个类作为切面。
@Pointcut:用于定义切点的位置。用于定义切点表达式,将切点表达式单独提取出来,以便在多个通知中重复使用,提高代码的可维护性。

@Aspect
public class LoggingAspect {@Pointcut("execution(* com.example.service..*.*(..))")public void serviceMethods() {}@Before("serviceMethods()")public void beforeServiceMethod() {System.out.println("服务方法执行前的日志记录");}
}

2.2.3 示例代码(四种通知)

@Aspect
public class Logging {/** Following is the definition for a pointcut to select*  all the methods available. So advice will be called*  for all the methods.*/@Pointcut("execution(* com.tutorialspoint.*.*(..))")private void selectAll(){}@Before("selectAll()")public void beforeAdvice(){System.out.println("Going to setup student profile.");}@After("selectAll()")public void afterAdvice(){System.out.println("Student profile has been setup.");}@AfterReturning(pointcut = "selectAll()", returning="retVal")public void afterReturningAdvice(Object retVal){System.out.println("Returning:" + retVal.toString() );}@AfterThrowing(pointcut = "selectAll()", throwing = "ex")public void AfterThrowingAdvice(IllegalArgumentException ex){System.out.println("There has been an exception: " + ex.toString());   }  
}

3.Spring AOP配置方式

支持XML模式、基于@AspectJ注解的两种配置方式。

未详细介绍

4.AOP的应用

4.1 日志

以我们常见的电商系统为例,比如说用户登录、商品下单、订单查询等这些业务模块,都可能需要记录操作日志,而日志记录这个功能并不属于具体某个业务模块的核心逻辑,但却贯穿于多个业务模块之中,这时候就适合使用 AOP 来处理。

4.2 异常处理

我的一篇博客

4.3 在spring中aop的应用是事务

别人写的博客
Spring AOP 为事务管理提供了一种非侵入式的实现方式,将事务管理这一横切关注点从业务逻辑代码中分离出来。
Spring 通过 AOP 的动态代理机制,为被事务管理的方法创建代理对象。当调用这些被代理的方法时,Spring 会根据方法上的事务配置,自动启动、提交或回滚事务。例如,我们常用的@Transactional注解就是基于 Spring AOP 实现事务管理的典型体现 。当一个方法被标记上@Transactional注解时,Spring AOP 会拦截该方法的调用,并在方法执行前开启一个事务,在方法正常执行完成后提交事务,如果方法执行过程中抛出异常,则会自动回滚事务,确保数据的一致性。

本质是依靠Spring框架提供的Bean生命周期相关回调接口和AOP结合完成的,简述如下:
1.通过自动代理创建器依次尝试为每个放入容器中的bean尝试进行代理
2.尝试进行代理的过程对于事务管理来说,就是利用事务管理涉及到的增强器advisor,即TransactionAttributeSourceAdvisor
3.判断当前增强器是否能够应用在当前bean上,怎么判断呢? —> advisor内部的pointCut!
4.如果能够应用,那么好,为当前bean创建代理对象返回,并且往代理对象内部添加一个TransactionInterceptor拦截器。
5.此时我们再从容器中获取,拿到的就是代理对象了,当我们调用代理对象的方法时,首先要经过代理对象内部拦截器链的处理,处理完后,最终才会调用被代理对象的方法。(这里其实就是责任链模式的应用)

4.3.1 事务的传播行为

在 Spring 事务管理中,事务的传播行为定义了多个事务方法之间相互调用时,事务如何在这些方法间传播。
例如,REQUIRED传播行为是最常用的一种,如果当前没有事务,就新建一个事务;如果已经存在一个事务,则加入到这个事务中。这确保了在一个业务流程中,多个相关的数据库操作可以在同一个事务的控制下,保证数据的一致性。
REQUIRES_NEW传播行为,它总是会开启一个新的事务,而不管当前是否已经存在事务,新事务与原有事务相互独立。

4.3.2 事务的隔离级别

READ_UNCOMMITTED(允许读取未提交的数据,可能导致脏读)、READ_COMMITTED(只能读取已提交的数据,避免脏读,但可能出现不可重复读)
REPEATABLE_READ(在同一个事务中多次读取的数据是一致的,可防止不可重复读,但可能出现幻读)
SERIALIZABLE(最高的隔离级别,完全串行化执行事务,可避免所有并发问题,但性能较差)

4.3.3 事务的配置方式

基于注解。通过在方法或类上添加@Transactional注解,可以轻松地将该方法或类纳入事务管理的范畴。注解中可以配置事务的各种属性,如传播行为、隔离级别、超时时间等。
此外,Spring AOP 还允许我们在配置文件或者基于 Java 的配置类中对事务进行全局配置,设置默认的事务属性,如默认的传播行为、隔离级别等。

4.4 在具体项目中的应用

在项目中的应用、使用场景

5.aop底层实现

Java中的静态代理和动态代理

5.1 动态代理

什么是动态代理:动态代理就是在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。
在这里插入图片描述

Spring的代理方式有两种,一种是 JDK动态代理,一种是CGLIB代理。Spring默认是JDK动态代理。
以下两种情况使用CGLIB代理:
①@EnableAspectJAutoProxy(proxyTargetClass = true) 强制要求Spring使用CGLIB代理。
②被代理的类不是接口的实现类。

5.1.1 JDK动态代理(实现和被代理类一样的接口)

JDK 动态代理是利用 Java 反射机制,在运行时动态创建代理类和代理对象的。它要求被代理的类必须实现一个或多个接口。代理类会实现与被代理类相同的接口,并在调用方法时通过反射机制转发到被代理类的相应方法上,同时可以在方法调用前后添加额外的逻辑。
使用场景:当被代理的类已经实现了接口,只需要对接口中定义的方法进行代理增强时。
总结:通过反射创建代理对象,在方法调用时通过invoke方法(反射)调用被代理类的方法,并且可以添加额外逻辑。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
interface IService {//用来调用的方法void doSomething();
}
// 实现接口的真实类
class ServiceImpl implements IService {//被代理对象的方法@Overridepublic void doSomething() {System.out.println("执行具体业务逻辑");}
}
// 动态代理处理器
class MyInvocationHandler implements InvocationHandler {private Object target;public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("在方法调用前执行额外逻辑,如日志记录或权限验证");// 调用目标对象的方法Object result = method.invoke(target, args);System.out.println("在方法调用后执行额外逻辑,如结果处理或资源清理");return result;}
}
//流程
public class JDKDynamicProxyExample {public static void main(String[] args) {// 创建真实对象IService realService = new ServiceImpl();// 创建动态代理对象IService proxyService = (IService) Proxy.newProxyInstance(realService.getClass().getClassLoader(),realService.getClass().getInterfaces(),new MyInvocationHandler(realService));// 调用代理对象的方法proxyService.doSomething();}
}

5.1.2 CGLIB动态代理(创建目标类的子类)

我写的很不具体

Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截。
它不需要类实现接口,可以直接为普通的类创建代理。
它通过在运行时动态生成字节码来创建代理类,与基于接口的 JDK 动态代理不同。CGLIB 会在运行时生成一个被代理类的子类,然后在这个子类中重写被代理类的方法,通过在重写的方法中添加一些额外的逻辑来实现代理的功能。

代码中的流程:1.创建一个Enhancer对象,用来创建代理对象。2.在这个对象上,设置要被代理的类。3.设置拦截器,这个拦截器是我们写的,拦截器里面是具体的功能:在方法执行前后的操作,调用方法。4.用Enhancer创建代理对象,通过代理对象调用目标方法。

举个例子🌰

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 这是我们要代理的目标类
class UserService {public void addUser(String username) {System.out.println("添加用户: " + username);}
}
// 这是实现了MethodInterceptor接口的拦截器类,用来添加代理逻辑
class MyMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 在调用目标方法之前添加的逻辑,这里是打印日志System.out.println("开始执行方法: " + method.getName());// 调用目标方法,通过methodProxy的invokeSuper方法来调用,注意这里的o是代理对象Object result = methodProxy.invokeSuper(o, objects);// 在调用目标方法之后添加的逻辑,这里也是打印日志System.out.println("方法执行完毕: " + method.getName());return result;}
}
//调用的例子 
public class CglibProxyExample {public static void main(String[] args) {// 1.创建Enhancer对象,它是CGLIB的核心类,用来创建代理对象Enhancer enhancer = new Enhancer();// 2.设置要代理的类,也就是UserService类enhancer.setSuperclass(UserService.class);// 3.设置拦截器,也就是我们刚才写的MyMethodInterceptor类enhancer.setCallback(new MyMethodInterceptor());// 4.创建代理对象UserService userServiceProxy = (UserService) enhancer.create();// 通过代理对象调用目标方法userServiceProxy.addUser("张三");}
}

5.1.3 两种代理的区别

JDK 动态代理要求目标类必须实现接口。当创建一个代理对象时,JDK 会在运行时动态生成一个代理类,这个代理类实现了与目标类相同的接口。然后通过反射机制,在代理类中调用目标类的同名方法,并在调用前后添加切面逻辑,从而实现对目标方法的增强。

CGLIB 通过继承目标类来创建代理类,在代理类中重写目标类的方法,从而实现对目标方法的增强。

5.2 后置处理器相关

5.2.1 后置处理器

在 Spring AOP 的实现中,后置处理器(BeanPostProcessor)是一个关键的组件。它允许在 Spring 容器创建和初始化 Bean 的过程中,对 Bean 进行额外的处理。
具体到 AOP,这个后置处理器承担了重要职责,它负责识别哪些 Bean 需要进行 AOP 代理创建,以及如何创建和配置这些代理。

5.2.2 增强器(封装切面的信息)

抽取切面方法为增强器。
增强器是对切面中各种通知方法以及切点信息等的一种封装。它包含了要在何时(切点)、以何种方式(通知类型)对目标对象的哪些方法进行增强的所有信息。
抽取过程:Spring 会在启动时扫描所有配置的切面类,解析其中的切面方法和切点表达式等。例如,对于一个包含了前置通知和后置通知的切面类,Spring 会将这些通知方法以及它们所对应的切点信息提取出来,封装成一个个增强器。

5.2.3 在 getBean 时的创建流程

(比如在 Spring 容器启动后,当应用程序需要获取某个 Bean 实例时)Bean 创建时,会调用 getBean 方法。此时,AOP 的后置处理器就会判断要创建的这个Bean是否为AOP目标类、是否有切面需要对它增强。
如果是,就会创建代理类,并将增强器的信息保存到代理类内部。(这样,代理类就知道在哪些方法调用时需要应用哪些增强逻辑。)

6.设计模式

6.1 代理模式

代理模式
代理模式(Proxy pattern): 为另一个对象提供一个替身或占位符,以控制对这个对象的访问。
在这里插入图片描述

举个简单的例子:我(client)如果要买(doOperation)房,可以找中介(proxy)买房,中介直接和卖方(target)买房。中介和卖方都实现买卖(doOperation)的操作。中介就是代理(proxy)。

6.2 责任链模式(依次调用增强器的通知方法)

责任链模式的原理:责任链模式是一种行为设计模式,它将请求的发送者和接收者解耦,让多个对象都有机会处理请求,形成一个链条,直到请求被处理为止。

在 AOP 中的应用:当代理类的某个方法被调用时,它会根据保存的增强器信息(通常会使用一些数据结构来存储这些信息,例如,可能会使用一个列表来保存所有适用于该代理类的增强器,每个增强器在列表中都有其特定的顺序和位置,以确定其执行的先后顺序。),以责任链的方式依次调用各个增强器所对应的通知方法。例如,如果有一个前置通知增强器和一个后置通知增强器,那么在目标方法被调用前,会先执行前置通知增强器中的前置通知方法,然后调用目标方法,最后再执行后置通知增强器中的后置通知方法。

7.aop失效

7.1 aop失效的场景

7.1.1 同一个类内部中,不同方法间的调用

当在一个类的内部方法中调用另一个被 AOP 增强的本类方法时,AOP 切面可能不会生效。
这是因为 Spring AOP 默认是基于代理的。而类内部方法调用是直接调用目标方法,不会通过代理对象,所以无法触发 AOP 的增强逻辑。

@Component
public class MyService {public void methodA() {// 直接调用本类的methodB,AOP切面不会生效methodB(); }@Transactional // 假设这是一个事务切面,正常情况下应该开启事务public void methodB() {// 业务逻辑代码}
}

7.1.2 多线程环境下,线程直接调用目标对象方法

在多线程场景中,如果线程直接调用目标对象的方法,而不是通过代理对象调用,AOP 切面也会失效。因为每个线程都有自己的执行路径,若不通过代理,就无法触发 AOP 的拦截和增强机制。
通过代理对象调用方法才能aop,如果没有代理对象直接调用方法aop失效。

public class MyService {public void doSomething() {System.out.println("执行目标方法");}
}
public class Main {public static void main(String[] args) {MyService target = new MyService();// 直接调用目标对象的方法,未通过代理对象,AOP不会生效//创建一个新的线程对象,它定义了新线程要执行的任务内容。在这里,它表示新线程启动后要执行的操作就是调用target对象(前面创建的MyService实例)的doSomething方法。new Thread(() -> target.doSomething()).start(); // 通过代理对象调用,AOP会生效MyService proxy = (MyService) ProxyFactory.getProxy(target); new Thread(() -> proxy.doSomething()).start(); }//通过一个名为ProxyFactory的工厂类的getProxy方法来为前面创建的目标对象target创建一个代理对象。然后将创建好的代理对象强制转换为MyService类型,并赋值给变量proxy。//new Thread后定义了新线程要执行的任务内容。这次是告诉新线程启动后要执行的操作是调用proxy对象的doSomething方法。
}

AOP 的切面逻辑是添加在代理对象上的,只有通过代理对象调用方法,才能触发切面的增强操作。

7.1.3 final 修饰的方法不能被重写

如果一个类中的方法被声明为 final,那么 Spring AOP 将无法对其进行代理和增强,因为 final 方法不能被重写。而 AOP 的实现原理通常是基于动态代理生成子类或实现接口来实现增强的,这与 final 的特性相冲突。

JDK 代理是基于接口的,它主要关注的是接口中定义的方法,而接口中的方法默认是public abstract的,不存在final修饰的情况。所以从这个角度来说,JDK 代理不会直接受到目标类中final方法的影响,因为它根本不涉及对目标类方法的重写,而是在实现接口的代理类中去调用目标类的方法。
但是如果在接口的实现类中,有一些方法是final的,并且这些方法在接口中也有定义,那么实际上在代理类调用这些final方法时,也无法对其进行增强。

CGLIB 通过继承目标类来创建代理类。当使用 CGLIB 创建代理对象时,它会在运行时动态生成一个目标类的子类作为代理类。这个代理类会重写目标类中的所有非final的方法。

@Component
public class MyFinalService {// 该方法被声明为final,AOP切面无法生效public final void finalMethod() {// 业务逻辑代码}
}

7.1.4 private方法由于访问权限

private 表示私有的访问权限,是最严格的访问控制级别。被 private 修饰的成员变量和方法只能在当前类的内部被访问,其他类包括子类都无法直接访问。
在基于代理的 AOP 实现中,无法被外部的代理类访问和增强,所以 AOP 对其无效。

@Component
public class MyPrivateService {// 该方法为private,AOP切面无法生效private void privateMethod() {// 业务逻辑代码}

7.2 解决方法

参考aop失效的原因,对应解决


未写知识:切面的实现过程、AOP代理的创建

相关文章:

Spring AOP相关知识详解

难 文章目录 1.AOP介绍1.1 面向切面编程 - Aspect Oriented Programming (AOP)1.2 优点 2.AOP的概念2.1 连接点、切入点、通知、切面:2.2 注解2.2.1 通知类型2.2.1.1 通知的优先级排序 2.2.2 其他重要注解2.2.3 示例代码(四种通知) 3.Spring …...

selinux和防火墙

第七章 selinux 一、selinux的说明 SELinux:安全强化的 linux,Security-Enhanced Linux的缩写 SELinux : 由美国国家安全局( NSA )开发,目的是为了避免资源的误用 SELinux: 是对程序、文件等权…...

【vue for beginner】Composition API 和 Options API 的区别

🌈Don’t worry , just coding! 内耗与overthinking只会削弱你的精力,虚度你的光阴,每天迈出一小步,回头时发现已经走了很远。 📗概念 vue2中的方式叫Options API ,vue3中叫Composition API。 Composition…...

jmeter5.6.3安装教程

一、官网下载 需要提前配置好jdk的环境变量 jmeter官网:https://jmeter.apache.org/download_jmeter.cgi 选择点击二进制的zip文件 下载成功后,默认解压下一步,更改安装路径就行(我安装在D盘) 实用jmeter的bin目录作为系统变量 然后把这…...

关于Spring基础了解

Spring简介 Spring框架是一个开源的Java应用框架,旨在简化企业级应用程序的开发。它提供了一系列强大的工具和服务,帮助开发者构建高质量的Java应用程序。Spring框架的核心理念是使开发过程更加模块化、可测试和可维护。 主要特性 依赖注入&#xff08…...

输入json 达到预览效果

下载 npm i vue-json-pretty2.4.0 <template><div class"newBranchesDialog"><t-base-dialogv-if"addDialogShow"title"Json数据配置"closeDialog"closeDialog":dialogVisible"addDialogShow":center"…...

DataLoade类与list ,iterator ,yield的用法

1 问题 探索DataLoader的属性&#xff0c;方法 Vscode中图标含意 list 与 iterator 的区别&#xff0c;尤其yield的用法 2 方法 知乎搜索DataLoader的属性&#xff0c;方法 pytorch基础的dataloader类是 from torch.utils.data.dataloader import Dataloader 其主要的参数如下&…...

model_selection.train_test_split函数介绍

目录 model_selection.train_test_split函数实战 model_selection.train_test_split函数 model_selection.train_test_split 是 Scikit-Learn 中用于将数据集拆分为训练集和测试集的函数。这个函数非常有用&#xff0c;因为在机器学习中&#xff0c;我们通常需要将数据集分为训…...

Springboot 读取 resource 目录下的Excel文件并下载

代码示例: GetMapping("/download") public void download(HttpServletResponse response) {try {String filename "测试.xls";OutputStream outputStream response.getOutputStream();// 获取springboot resource 路径下的文件InputStream inputStream…...

SQL EXISTS 子句的深入解析

SQL EXISTS 子句的深入解析 引言 SQL&#xff08;Structured Query Language&#xff09;作为一种强大的数据库查询语言&#xff0c;广泛应用于各种数据库管理系统中。在SQL查询中&#xff0c;EXISTS子句是一种非常实用的工具&#xff0c;用于检查子查询中是否存在至少一行数…...

33.Java冒泡排序

冒泡排序&#xff1a; 一种排序的方式&#xff0c;对要进行排序的数据中相邻的数据进行两两比较&#xff0c;将较大的数据放在后面&#xff0c;依次对所有的数据进行操作&#xff0c;直至所有数据按要求完成排序. package Javase;import sun.security.util.ByteArrayTagOrder…...

Docker容器ping不通外网问题排查及解决

Docker容器ping不通外网问题排查及解决 解决方案在最下面&#xff0c;不看过程的可直接拉到最下面。 一台虚拟机里突然遇到docker容器一直访问外网失败&#xff0c;网上看到这个解决方案&#xff0c;这边记录一下。 首先需要明确docker的网桥模式&#xff0c;网桥工作在二层…...

JavaScript 库 number-precision 如何使用?

number-precision 是一个 JavaScript 库&#xff0c;主要用于处理 JavaScript 中的数字精度问题。它提供了一些方法&#xff0c;帮助你进行数字运算时保持精度&#xff0c;尤其是在涉及到浮点数运算时&#xff0c;它能够避免传统 JavaScript 中精度丢失的问题。 例如&#xff…...

faiss库中ivf-sq(ScalarQuantizer,标量量化)代码解读-2

文件ScalarQuantizer.h 主要介绍这里面的枚举以及一些函数内容&#xff1a;QuantizerType、RangeStat、ScalarQuantizer、train、compute_codes、decode、SQuantizer、FlatCodesDistanceComputer、get_distance_computer、select_InvertedListScanner QuantizerType 量化类型…...

性能测试工具Grafana、InfluxDB和Collectd的搭建

一、性能监控组成简介 1、监控能力分工:这个系统组合能够覆盖从数据采集、存储到可视化的整个监控流程。Collectd可以收集各种系统和应用的性能指标,InfluxDB提供高效的时序数据存储,而 Grafana 则将这些数据以直观的方式呈现出来。2,实时性能监控:对于需要实时了解系统状…...

【ruby on rails】dup、deep_dup、clone的区别

一、区别 dup 浅复制&#xff1a;dup 方法创建对象的浅复制。 不复制冻结状态&#xff1a;dup 不会复制对象的冻结状态。 不复制单例方法&#xff1a;dup 不会复制对象的单例方法。 deep_dup 深复制&#xff1a;deep_dup 方法创建对象的深复制&#xff0c;递归复制嵌套的对象。…...

原生微信小程序画表格

wxml部分&#xff1a; <view class"table__scroll__view"><view class"table__header"><view class"table__header__item" wx:for"{{TableHeadtitle}}" wx:key"index">{{item.title}}</view></…...

Python实现IP代理池

文章目录 Python实现IP代理池一、引言二、步骤一&#xff1a;获取代理IP1、第一步&#xff1a;爬取代理IP2、第二步&#xff1a;验证代理IP的有效性 三、步骤二&#xff1a;构建IP代理池四、使用示例1、完整的使用示例2、注意事项3、处理网络问题 五、总结 Python实现IP代理池 …...

互联网直播/点播EasyDSS视频推拉流平台视频点播有哪些技术特点?

在数字化时代&#xff0c;视频点播应用已经成为我们生活中不可或缺的一部分。监控技术与视频点播的结合正悄然改变着我们获取和享受媒体内容的方式。这一变革不仅体现在技术层面的进步&#xff0c;更深刻地影响了我们。 EasyDSS视频直播点播平台是一款高性能流媒体服务软件。E…...

32.4 prometheus存储磁盘数据结构和存储参数

本节重点介绍 : prometheus存储磁盘数据结构介绍 indexchunkshead chunksTombstoneswal prometheus对block进行定时压实 compactprometheus 查看支持的存储参数 prometheus存储示意图 内存和disk之间的纽带 wal WAL目录中包含了多个连续编号的且大小为128M的文件&#xff0c…...

C7.【C++ Cont】范围for的使用和auto关键字

目录 1.知识回顾 2.范围for 格式 使用 运行结果 运行过程 范围for的本意 作用 注意 3.底层分析范围for的执行过程 反汇编代码 分析 4.auto关键字 格式 基本用法 在范围for中使用auto 1.知识回顾 for循环的使用参见25.【C语言】循环结构之for文章 2.范围for C…...

联通云服务器部署老项目tomcat记录

1.先在服务器上安装mysql和tomcat 2.tomcat修改端口 3.在联通云运控平台配置tomcat访问端口&#xff08;相当于向外部提供可访问端口&#xff09; 4.将tomcat项目放在服务器tomcat的webapps里面 5.在mysql里创建项目数据库&#xff0c;运行sql创建表和导入数据 6.在配置文…...

剪映自动批量替换视频、图片素材教程,视频批量复刻、混剪裂变等功能介绍

一、三种批量替换模式的区别 二、混剪裂变替换素材 三、分区混剪裂变替换素材 四、按组精确替换素材 五、绿色按钮教程 &#xff08;一&#xff09;如何附加音频和srt字幕 &#xff08;二&#xff09;如何替换固定文本的内容和样式 &#xff08;三&#xff09;如何附加…...

el-dialog中调用resetFields()方法重置表单报错

前言 在开发中&#xff0c;弹框和表单是两个常见的组件&#xff0c;它们通常一起使用以实现用户交互和数据输入。然而&#xff0c;当我们尝试在弹框中调用表单的 resetFields() 方法时&#xff0c;有时会遇到报错的情况。 一、用法错误 确保 this.$refs[ruleForm].resetFields…...

分布式系统接口,如何避免重复提交

分布式系统接口&#xff0c;如何避免重复提交 1、基于Token的幂等设计原理实现步骤技术选型 2、基于Token的幂等设计原理实现步骤适用场景 3、幂等性设计原理实现方式 4、分布式锁原理实现方式适用场景 5、请求去重原理实现方式 6.前端防护原理实现方式适用场景 7.延迟队列原理…...

AI 声音:数字音频、语音识别、TTS 简介与使用示例

在现代 AI 技术的推动下&#xff0c;声音处理领域取得了巨大进展。从语音识别&#xff08;ASR&#xff09;到文本转语音&#xff08;TTS&#xff09;&#xff0c;再到个性化声音克隆&#xff0c;这些技术已经深入到我们的日常生活中&#xff1a;语音助手、自动字幕生成、语音导…...

【论文速读】| 人工智能驱动的网络威胁情报自动化

基本信息 原文标题&#xff1a;AI-Driven Cyber Threat Intelligence Automation 原文作者&#xff1a;Shrit Shah, Fatemeh Khoda Parast 作者单位&#xff1a;加拿大圭尔夫大学计算机科学学院 关键词&#xff1a;网络威胁情报&#xff0c;AI自动化&#xff0c;攻击技术和…...

什么是域名监控?

域名监控是持续跟踪全球域名系统&#xff08;DNS&#xff09;中变化以发现恶意活动迹象的过程。组织可以对其拥有的域名进行监控&#xff0c;以判断是否有威胁行为者试图入侵其网络。他们还可以对客户的域名使用这种技术以执行类似的检查。 你可以将域名监控比作跟踪与自己实物…...

vue3 发送 axios 请求时没有接受到响应数据

<script setup> import Edit from ./components/Edit.vue import axios from axios import { onMounted,ref } from vue// TODO: 列表渲染 //装数据的列表 const list ref([]) const count ref(0) const getList async () > {//通过发送 /list 请求从后端拿到列表数…...

前端使用fontfaceobserver库实现字体设置

要使用FontFaceObserver来加载设置项目本地的字体&#xff0c;先确保字体文件位于项目中或者可以从服务端获取到&#xff0c;这样就可以使用FontFaceObserver来检测并加载这些字体 主要有以下几步&#xff1a; npm或者yarn安装引入fontfaceobserver字体资源引入和font-face配置…...

wordpress如何改默认后台地址/dz论坛seo设置

本文以如下树结构为例 深度优先(Deep First Search) 树的孩子称作子树&#xff0c;对于一个树进行深度优先遍历&#xff0c;即将其某一子树下所有节点遍历完再去遍历其他子树。遍历的顺序以根为参照可分为先序遍历&#xff0c;中序遍历&#xff0c;后序遍历。 遍历方式描述…...

金乡县住房与城乡建设局网站/网站推广优化方法

1.在已经可以运行的工程目录下&#xff0c;新建一个文件夹&#xff0c;以新的项目名字命名&#xff0c;分别复制Classes&#xff0c;proj.win32,Resources这三个文件到新建的文件夹中&#xff0c;此文件夹作为新的项目 2.在新项目文件夹中&#xff0c;将下所有以原项目命名的文…...

网上找装修设计师/产品seo基础优化

CSS既是一个美妙的创新也是一个不可错过的机会。它通过把表现和结构分离来改进网页设计的过程。但是&#xff0c;CSS有也许多限制。曾写过CSS的人都知道&#xff0c;做一个最简单的网站时&#xff0c;CSS会有很多重复的地方&#xff0c;导致很多覆盖问题&#xff0c;覆盖应用多…...

ffmpeg wordpress/重庆seo网页优化

SELECT * FROM article where userid4 order by sort asc LIMIT 0,10; 先根据写where 条件&#xff0c;再排序&#xff0c;在LIMIT.转载于:https://www.cnblogs.com/TangGe520/p/9271877.html...

佛山高端外贸网站建设/短视频seo营销系统

《Redis中的GETBIT和SETBIT(转载)》 《redis 用setbit(bitmap)统计活跃用户》...

快速制作简单的网站/怎么出售友情链接

ES6里新添加了两个很好用的东西&#xff0c;set和Array.from。 set是一种新的数据结构&#xff0c;它可以接收一个数组或者是类数组对象&#xff0c;自动去重其中的重复项目。 var arr [0,2,3,4,4,0,2];console.log(new Set(arr)) 结果如下&#xff0c;但是这里大家可以看到…...