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 {
	//被代理对象的方法
    @Override
    public void doSomething() {
        System.out.println("执行具体业务逻辑");
    }
}
// 动态代理处理器
class MyInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public 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 {
    @Override
    public 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代理的创建

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

外包干了3年,技术退步明显...

先说情况,大专毕业,18年通过校招进入湖南某软件公司,干了接近6年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能…

【阅读记录-章节4】Build a Large Language Model (From Scratch)

文章目录 4. Implementing a GPT model from scratch to generate text4.1 Coding an LLM architecture4.1.1 配置小型 GPT-2 模型4.1.2 DummyGPTModel代码示例4.1.3 准备输入数据并初始化 GPT 模型4.1.4 初始化并运行 GPT 模型 4.2 Normalizing activations with layer normal…

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

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

【数据结构】双向链表、单向循环链表、双向循环链表、栈、链栈

目录 一、双向链表 定义类和封装函数以及测试样例如下: 注意事项: 二、循环链表 单循环列表的类和函数封装如下: 注意事项: 三、双向循环链表 结点类和双循环链表的定义部分 函数封装之判空和尾插 双循环链表遍历 双循…

ModuleNotFoundError: No module named ‘_ssl‘ centos中的Python报错

1、检查系统有没有openssl,有的话,就是python安装时没有指定openssl openssl version,有输出版本号就有,没有的话,需要手动安装 下载地址 参见https://www.openssl.org/,包括以下版本: https:/…

C语言:树

在C语言中,树(Tree)是一种常见的数据结构,用于表示分层关系或层次结构的数据集合。树在计算机科学中广泛应用,包括但不限于文件系统、解析表达式、数据压缩、决策树等。 1. 基本概念 节点(Node&#xff0…

python写的服务,用docker制作镜像并且打包

步骤1 简单写一个python服务脚本app.py,通过http访问一个端口,收到helloworld from flask import Flask, request app Flask(__name__) app.route(/, methods[GET]) # 确保包括GET方法 def hello_world(): return Hello, World! if __name__ __main…

NSSCTF web刷题

1 虽然找到了flag,但是我要怎么去改他的代码,让他直接输出flag呢? (好像是要得到他的json代码,这题不让看) 2 wllm应该就是他的密码,进入许可了 意思是服务器可以执行通过POST的请求方式传入参数为wllm的命令,那这就是典型的命令执行,当然&#xff0c…

(0基础保姆教程)-JavaEE开课啦!--11课程(初识Spring MVC + Vue2.0 + Mybatis)-实验9

一、什么是Spring MVC? Spring MVC 是一个基于 Java 的 Web 框架,遵循 MVC 设计模式,用于构建企业级应用程序。它通过控制器(Controller)处理用户请求,模型(Model)处理业务逻辑,视图(View)展示数据,实现了请…

FCBP 认证考试要点摘要

理论知识 数据处理与分析:包括数据的收集、清洗、转换、存储等基础操作,以及数据分析方法,如描述性统计分析、相关性分析、数据挖掘算法等的理解和应用 。数据可视化:涉及图表类型的选择与应用,如柱状图、折线图、饼图…

「Qt Widget中文示例指南」如何为窗口实现流程布局?(二)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写,所有平台无差别运行,更提供了几乎所有开发过程中需要用到的工具。如今,Qt已被运用于超过70个行业、数千家企业,支持数百万设备及应用。 本文将展示如何为不…

智能产品综合开发 - 手势识别

1 实训选题目的 本次实训选择的题目是“基于树莓派的手势识别系统”,旨在为人们提供一种便捷的交互方式,使用户能够通过手势控制智能设备,摆脱传统的物理按键操作。通过本项目,我们希望能实现快速、灵活的手势识别,提升…

Redis【1】- 如何阅读Redis 源码

1 Redis 的简介 Redis 实际上是简称,全称为 Remote Dictionary Server (远程字典服务器),由 Salvatore Sanfilippo 写的高性能 key-value 存储系统,其完全开源免费,遵守 BSD 协议。Redis 与其他 key-value 缓存产品(如…

git的使用(简洁版)

什么是 Git? Git 是一个分布式版本控制系统 (DVCS),用于跟踪文件的更改并协调多人之间的工作。它由 Linus Torvalds 在 2005 年创建,最初是为了管理 Linux 内核的开发。Git 的主要目标是提供高效、易用的版本控制工具,使得开发者…

用于高吞吐量和低延迟的 JVM 性能调优

Java 虚拟机 (JVM) 调优是调整默认参数以满足我们的应用程序需求的过程。这包括通过选择合适的垃圾回收器来使用优化版本的 getter 来调整堆的大小等简单调整。 了解 Java 虚拟机 (JVM) 什么是 JVM? Java 虚拟机 &…

django authentication 登录注册

文章目录 前言一、django配置二、后端实现1.新建app2.编写view3.配置路由 三、前端编写1、index.html2、register.html3、 login.html 总结 前言 之前,写了django制作简易登录系统,这次利用django内置的authentication功能实现注册、登录 提示&#xff…

Python+Pytest+Yaml+Allure数据参数化(DDT)数据驱动(一)

我们在做数据之前要知道几个问题 1、在代码层面怎么来数据驱动 2、yaml文件是什么 3、怎么用yaml文件实现对应的数据驱动 我们用的是pytest框架所以相对来说是简单的,我们通过pytest框架来实现,而框架中要数据驱动用到我们装饰器就好啦pytest.mark.p…

WaveForms™ SDK 参考手册(翻译笔记与总结)

概述 WaveForms 提供了一个接口,允许用户与 Digilent Analog Design 硬件进行交互,例如 Analog DiscoveryTM、Analog Discovery 2TM、Analog Discovery ProTM、Digital DiscoveryTM、Discovery Power SupplyTM 和 Electronics ExplorerTM。虽然 WaveForm…

Python基础学习-12匿名函数lambda和map、filter

目录 1、匿名函数: lambda 2、Lambda的参数类型 3、map、 filter 4、本节总结 1、匿名函数: lambda 1)语法: lambda arg1, arg2, …, argN : expression using arg 2) lambda是一个表达式,而不是一个语…

pyqt5+yolo模型+多线程

界面开发 开发主窗口界面 from PyQt5 import QtWidgets from PyQt5 import QtCore,QtGui import sysclass MainWindow(QtWidgets.QMainWindow):def __init__(self):super().__init__()self.initUI()self.toolbar()# 创建菜单栏def initUI(self):menubar self.menuBar()file_m…