Spring AOP:面向切面编程 详解代理模式

news2025/7/22 10:47:18

文章目录

  • AOP介绍
  • 什么是Spring AOP?
    • 快速入门SpringAop
      • 引入依赖
      • Aop的优点
    • Spring Aop 的核心概念
      • 切点(Pointcut)
      • 连接点、
      • 通知
      • 切面
      • 通知类型
      • @PointCut注解
      • 切面优先级@Order
      • 切点表达式
        • execution
        • within
        • this
        • target
        • args
        • @annotation
          • 自定义注解
  • Spring AOP原理
    • 代理模式(委托模式)
      • 静态代理
      • 动态代理
        • JDK动态代理类实现步骤
      • CGLIB动态代理
        • CGLIB 动态代理类实现步骤
  • 总结

AOP介绍

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

什么是面向切面编程呢?切面就是指某⼀类特定问题

所以AOP也可以理解为面向特定方法编程.

什么是面向特定方法编程?

比如说上篇博客中写到的登录校验拦截器,就是对于某一问题的集中处理, AOP是一种思想,拦截器是AOP的一种实现,统一处理异常,统一返回数据格式,也是 AOP的一种实现

Spring框架实现了这种思想,提供了拦截器相关技术的接口

简单来说: AOP是⼀种思想,是对某⼀类事情的集中处理

什么是Spring AOP?

SpringAop是Spring框架提供的一种面向切面编程的技术。也就是说对 AOP思想的一种实现。它通过将横切关注点(例如日志记录、事务管理、安全性检查等)从主业务逻辑代码中分离出来,以模块化的方式实现对这些关注点的管理和重用。

在Spring AOP中,切面(Aspect)是一个模块化的关注点,它可以跨越多个对象,例如日志记录、事务管理等。切面通过定义切点(Pointcut)和增强(Advice)来介入目标对象的方法执行过程。

在日常的代码编写中,有以下例子:

在这里插入图片描述

假如,我们要对这里面的接口实现改良,让执行时间减少,这里就定位到某些业务代码逻辑等改进,但是不是所有都是不好的,所以我们需要进行测试每个接口,业务执行的时间,那么就有如下的代码,来进行时间检测:

在这里插入图片描述
此时的方法固然是可以的,但是那么多接口都需要这样写吗??答案是不可能的,此时这就是一类特定的问题,此时我们就可以使用AOP思想来进行解决

AOP就可以做到在不改动这些原始方法的基础上, 针对特定的方法进行功能的增强.
AOP的作用:在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性: 解耦)

快速入门SpringAop

引入依赖

Maven依赖

	<dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-aop</artifactId>
   </dependency>

编写Aop程序
记录Controller中每个方法的执行时间

@Slf4j
@Component
@Aspect
public class TimeAspect {
    @Around("execution(* com.spring.aop.springaop.controller.*.*(..))")
    public Object readTime (ProceedingJoinPoint pjp) throws Throwable {
    	//开始时间
        long begin=System.currentTimeMillis();
        //执行方法
        Object result=pjp.proceed();
        //结束时间
        long end=System.currentTimeMillis();
        //记录耗时时间
        log.info(pjp.getSignature()+"耗时:{}",end-begin+"ms");
        return result;
    }


}

在这里插入图片描述
运行程序,观察日志
在这里插入图片描述
对于程序进行简单的讲解:

  1. @Aspect:标识这是⼀个切面类
  2. @Around:环绕通知,在目标方法的前后都会被执行.后面的表达式表示对哪些方法进行增强.
  3. ProceedingJoinPoint.proceed() 让原始方法执行

Aop的优点

提高代码复用率:通过将通用功能,如日志记录或权限检查,封装在独立的切面中,多个地方能够复用这些功能。(减少重复代码,提高开发效率)
解耦业务逻辑:AOP 使得关注点的逻辑与核心业务逻辑分离,降低了系统各部分之间的依赖性。(代码无侵入)
集中处理关注点:相关的代码可以集中于一个地方进行管理,简化了维护过程。(维护方便)

Spring Aop 的核心概念

切点(Pointcut)

也称为切入点
作用:提供⼀组规则告诉程序对哪些方法来进行功能增强.

execution(* com.spring.aop.springaop.controller..(…))是切点表达式

连接点、

满足切点表达式规则的方法,就是连接点,也就是可以被AOP控制的方法
即com.spring.aop.springaop.controller路径下的类路径下的方法

在这里插入图片描述

通知

通知就是具体要做的工作,指哪些重复的逻辑,也就是共性功能(最终体现为⼀个方法)
比如上述中记录耗时方法就是一个通知

切面

切面(Aspect)=切点(Pointcut)+通知(Advice)

通过切面就能够描述当前AOP程序需要针对于哪些方法,在什么时候什么样的操作
被@Aspect注解修饰的类,称为切面类

通知类型

通知类型解释
@Around环绕通知,在目标方法前,后都被执行
@Before前置通知,在目标方法前被执行
@After后置通知,在目标方法后被执行,无论是否有异常都会执行
@AfterReturning返回后通知,在目标方法后执行 ,有异常不会执行
@AfterThrowing异常后通知,在发生异常后执行

测试程序,加深了解

编写切面类

@Slf4j
@Aspect
@Component
public class AspectDemo1 {
    @Pointcut("execution(* com.spring.aop.springaop.controller.*.*(..))")
    public void pt(){}

    @Around("pt()")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        log.info("目标方法执行前....");
        //执行目标方法
        Object result = null;
        try {
            result = pjp.proceed();
        } catch (Exception e) {
            log.error("do Around throwing....");
        }
        //记录方法执行结束时间
        log.info("目标方法执行后....");

        return result;
    }

    @Before("pt()")
    public void doBefore() {
        log.info("doBefore....");

    }
    @After("pt()")
    public void doAfter(){
        log.info("doAfter....");
    }

    @AfterReturning("pt()")
    public void doAfterReturning(){
        log.info("doAfterReturnin....");
    }

    @AfterThrowing("pt()")
    public void doAfterThrowing(){
        log.info("doAfterThrowing....");
    }

}

测试类

@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public Integer t1(){
        log.info("执行t1");
        int a = 10/0;
        return 1;
    }
    @MyAspect
    @RequestMapping("/t2")
    public Boolean t2(){
        log.info("执行t2");
        return true;
    }
    @RequestMapping("/t3")
    public String t3(){
        log.info("执行t3");
        return "t3";
    }
}

运行test/t1,除数异常
在这里插入图片描述
程序发生异常的情况下:

• @AfterReturning 标识的通知方法不会执行, @AfterThrowing 标识的通知方法执行了

• @Around 环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会在执行了

注意事项:
• @Around 环绕通知需要调用ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行.

• @Around 环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的.

• 一个切面类可以有多个切点.

@PointCut注解

@Slf4j
@Aspect
@Component
public class AspectDemo1 {
    @Pointcut("execution(* com.spring.aop.springaop.controller.*.*(..))")
    public void pt(){}
}
  @Before("com.spring.aop.springaop.aspect.AspectDemo1.pt()")
    public void doBefore() {
        log.info("AspectDemo2 doBefore....");

    }

当切点定义使用private修饰时,仅能在当前切面类中使用

当其他切面类也要使用当前切点定义时,就需要把private改为public.引用方式为:全限定类名.方法名()

切面优先级@Order

@Slf4j
@Aspect
@Order(1)
@Component
public class AspectDemo4 {

    @Before("execution(* com.spring.aop.springaop.controller.*.*(..))")
    public void doBefore() {
        log.info("AspectDemo4 doBefore....");

    }
    @After("execution(* com.spring.aop.springaop.controller.*.*(..))")
    public void doAfter(){
        log.info("AspectDemo4 doAfter....");
    }

}
@Slf4j
@Aspect
@Order(3)
@Component
public class AspectDemo2 {

//    @Before("execution(* com.spring.aop.springaop.controller.*.*(..))")
    @Before("com.spring.aop.springaop.aspect.AspectDemo1.pt()")
    public void doBefore() {
        log.info("AspectDemo2 doBefore....");

    }
    @After("execution(* com.spring.aop.springaop.controller.*.*(..))")
    public void doAfter(){
        log.info("AspectDemo2 doAfter....");
    }

}
@Slf4j
@Order(5)
@Aspect
@Component
public class AspectDemo3 {

    @Before("execution(* com.spring.aop.springaop.controller.*.*(..))")
    public void doBefore() {
        log.info("AspectDemo3 doBefore....");

    }
    @After("execution(* com.spring.aop.springaop.controller.*.*(..))")
    public void doAfter(){
        log.info("AspectDemo3 doAfter....");
    }

}

指定执行顺序在这里插入图片描述

通过观察运行结果可以发现:
@Order注解标识的切面类,执行顺序如下
• @Before 通知:数字越小先执行
• @After 通知:数字越大先执行

@Order 控制切面的优先级,先执行优先级较高的切面,再执行优先级较低的切面,最终执行目标方法.
在这里插入图片描述

切点表达式

execution

execution([可见性] 返回类型 [声明类型].方法名(参数)[异常]),其中[]内的是可选的,其他的还支持通配符的使用:

  1. *: 匹配所有

  2. … : 匹配多个包或多个参数

  3. +: 表示类及其子类

  4. 运算符:&&、||、!

within

是用来指定类型的,指定类型中的所有方法将被拦截是用来指定类型的,指定类型中的所有方法将被拦截

within(com.demo.service.impl.UserServiceImpl) 匹配UserServiceImpl类对应对象的所有方法调用,并且只能是UserServiceImpl对象,不能是它的子对象

within(com.demo…*)匹配com.demo包及其子包下面的所有类的所有方法的外部调用

this

SpringAOP是基于代理的,this就代表代理对象,语法是this(type),当生成的代理对象可以转化为type指定的类型时表示匹配。

this(com.demo.service.UserService)匹配生成的代理对象是UserService类型的所有方法的外部调用

target

SpringAOP是基于代理的,target表示被代理的目标对象,当被代理的目标对象可以转换为指定的类型时则表示匹配。

target(com.demo.service.IUserService) 匹配所有被代理的目标对象能够转化成IuserService类型的所有方法的外部调用。

args

args用来匹配方法参数

args() 匹配不带参数的方法

args(java.lang.String) 匹配方法参数是String类型的

args(…) 带任意参数的方法

args(java.lang.String,…) 匹配第一个参数是String类型的,其他参数任意

@annotation

带有相应标注的任意方法,比如@Transactional或其他自定义注解
在这里插入图片描述

自定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}

public class MyAspectImpl {

    @Around("@annotation(com.spring.aop.springaop.aspect.MyAspect)")
//@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public Object recordTime(ProceedingJoinPoint pjp) {
        log.info("目标方法执行前....");
        //执行目标方法
        Object result = null;
        try {
            result = pjp.proceed();
        } catch (Throwable e) {
            log.error("do Around throwing....");
        }

        log.info("目标方法执行后....");

        return result;
    }
}


测试类

@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    @MyAspect
    @RequestMapping("/u2")
    public String u2(){
        log.info("执行u2");
        return "u2";
    }
}

在这里插入图片描述

@Target 标识了 Annotation 所修饰的对象范围, 即该注解可以用在什么地方

常用取值:
ElementType.TYPE: 用于描述类、接口(包括注解类型)或enum声明
ElementType.METHOD: 描述方法
ElementType.PARAMETER: 描述参数
ElementType.TYPE_USE: 可以标注任意类型

@Retention 指Annotation被保留的时长短,标明注解的生命周期

@Retention 的取值有三种:

  1. RetentionPolicy.SOURCE:表示注解仅存在于源代码中,编译成字节码后会被丢弃.
    这意味着在运行时无法获取到该注解的信息,只能在编译时使用.比如 @SuppressWarnings 以及lombok提供的注解 @Data @Slf4j

  2. RetentionPolicy.CLASS:编译时注解.表示注解存在于源代码和字节码中,但在运行时会被丢弃.这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时无法获
    取.通常用于⼀些框架和工具的注解.

  3. RetentionPolicy.RUNTIME:运行时注解.表示注解存在于源代码,字节码和运行时中.这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息.通常用于⼀些需要在运行时处理的注解,如Spring的@Controller @ResponseBody

Spring AOP原理

Spring AOP是基于动态代理实现的

代理模式(委托模式)

定义:为其他对象提供⼀种代理以控制对这个对象的访问.
作用:通过提供⼀个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是是通过代理类间接调用.
在某些情况下,⼀个对象不适合或者不能直接引用另⼀个对象,而代理对象可以在客户端和目标对象之间起到中介的作用.

使用代理前:
在这里插入图片描述
使用代理后
在这里插入图片描述
生活中的代理:
房屋中介:卖方会把房屋授权给中介,由中介来代理看房,房屋咨询等服务.
艺人经纪人:广告商找艺人拍广告,需要经过经纪人,由经纪人来和艺人进行沟通.
代理模式中的主要角色

  1. Subject: 业务接口类.可以是抽象类或者接口(不⼀定有)
  2. RealSubject: 业务实现类.具体的业务执行,也就是被代理对象.
  3. Proxy: 代理类.RealSubject的代理.

比如房屋租赁
Subject:提前定义了房东做的事情,交给中介代理
RealSubject:房东
Proxy:中介

UML类图

在这里插入图片描述
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强.

根据代理的创建时期,代理模式分为静态代理和动态代理.

• 静态代理: 由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的.class文件就已经存在了.

• 动态代理: 在程序运行时,运用反射机制动态创建而成.

静态代理

我们通过代码来加深理解.以房租租赁为例

定义接口(房东要中介做的事情,subject)

public interface HouseSubject {
    void rentHouse();
}

实现接口(房东出租房子)

public class RealHouseSubject implements HouseSubject{
    @Override
    public void rentHouse() {
        System.out.println("我是房东, 我出租房子");
    }
}

代理(中介帮房东出租房子)

public class HouseProxy implements HouseSubject{
		//将被代理对象声明为成员变量
    private HouseSubject realHouseSubject;

    public HouseProxy(HouseSubject realHouseSubject) {
        this.realHouseSubject = realHouseSubject;
    }

    @Override
    public void rentHouse() {
        System.out.println("我是中介, 我帮房东开始代理");
        realHouseSubject.rentHouse();
        System.out.println("我是中介, 我帮房东结束代理");
    }
}

使用

public class Main {
    public static void main(String[] args) {
        HouseSubject target = new RealHouseSubject();
        HouseSubject houseSubject = new HouseProxy(target);
        houseSubject.rentHouse();
}

在这里插入图片描述
从上述程序可以看出,虽然静态代理也完成了对目标对象的代理,但是由于代码都写死了,对目标对象的每个方法的增强都是手动完成的,非常不灵活.
所以日常开发几乎看不到静态代理的场景.

如果需要增加新的需求,又要修改和新增业务实现类和代理类

既然代理的流程是⼀样的,有没有⼀种办法,让他们通过⼀个代理类来实现呢?
这就需要动态代理技术了.

动态代理

Java也对动态代理进行了实现,并给我们提供了⼀些API,常见的实现方式有两种:

  1. JDK动态代理
  2. CGLIB动态代理

动态代理在我们日常开发中使用的相对较少,但是在框架中几乎是必用的⼀门技术.学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助.

JDK动态代理类实现步骤

定义JDK动态代理类

实现InvocationHandler类

public class JDKInvocation implements InvocationHandler {
    /**
     * 目标对象, 被代理对象
     */
    private Object target;

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

    //调用目标方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	//代理增强内容
        System.out.println("我开始代理...");
        //通过反射调用被代理类的方法
        Object invoke = method.invoke(target, args);
        //代理增强内容
        System.out.println("我结束代理");
        return invoke;
    }
}

创建代理对象并使用

public class Main {
    public static void main(String[] args) {
        HouseSubject target = new RealHouseSubject();
        //JDK动态代理
        //动态创建一个代理类
        创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建 
        HouseSubject houseProxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                new Class[]{HouseSubject.class}, new JDKInvocation(target));
        houseProxy.rentHouse();
        }
}

代码讲解:InvocationHandler接口是Java动态代理的关键接口之⼀,它定义了⼀个单一方法invoke() 用于处理被代理对象的方法调用

通过实现 InvocationHandler 接口,可以对被代理对象的方法进行功能增强.
Proxy 类中使用频率最高的方法是: newProxyInstance() ,这个方法主要用来生成⼀个代理对象

通过Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
interfaces,InvocationHandler h) 方法创建代理对象

Loader: 类加载器,用于加载代理对象.

interfaces: 被代理类实现的⼀些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的⼀些类)

h: 实现了 InvocationHandler接口的对象

JDK动态代理有⼀个最致命的问题是其只能代理实现了接口的类.
有些场景下,我们的业务代码是直接实现的,并没有接口定义.为了解决这个问题,我们可以用CGLIB动态代理机制来解决.

CGLIB动态代理

CGLIB(Code Generation Library)是⼀个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成.

CGLIB通过继承方式实现代理,很多知名的开源框架都使用到了CGLIB.

例如Spring中的Aop模块中: 如果目标对象实现了接口,则默认采用JDK动态代理,否则采用CGLIB动态代理.

CGLIB 动态代理类实现步骤
  1. 定义⼀个类(被代理类)
  2. 自定义MethodInterceptor 并重写intercept 方法, intercept 用于增强目标方法,和JDK动态代理中的invoke 方法类似
  3. 通过Enhancer类的create()创建代理类

添加依赖

<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.3.0</version>
</dependency>

实现MethodInterceptor接口

public class CGlibMethodInterceptor implements MethodInterceptor {
	//目标对象
    private Object target;

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

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("我开始代理");
        Object invoke = method.invoke(target, args);
        System.out.println("我结束代理");
        return invoke;
    }
}

创建代理类

public class Main {
    public static void main(String[] args) {
        HouseSubject target = new RealHouseSubject();
        HouseSubject houseProxy = (HouseSubject) Enhancer.create(target.getClass(), new CGlibMethodInterceptor(target));
        houseProxy.rentHouse();
    }
}

代码讲解:MethodInterceptor 和JDK动态代理中的 InvocationHandler 类似,它只定义了⼀个方法intercept() ,用于增强目标方法.

Enhancer.create()用来生成⼀个代理对象
public static Object create(Class type, Callback callback) {
}
参数说明:
type: 被代理类的类型(类或接口)
callback: 自定义方法拦截器 MethodInterceptor

总结

  1. AOP是⼀种思想,是对某⼀类事情的集中处理.Spring框架实现了AOP,称之为SpringAOP
  2. Spring AOP常见实现方式有两种:1.基于注解@Aspect来实现2.基于自定义注解来实现,还有一些更原始的方式,比如基于xml配置的方式,但比较少见
  3. Spring AOP 是基于动态代理实现的,有两种方式:1.基本JDK动态代理实现 2.基于CGLIB动态代理实现.运行使用哪种方式与项目配置和代理的对象有关.

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

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

相关文章

零知开源——STM32F407VET6驱动ILI9486 TFT显示屏 实现Flappy Bird游戏教程

简介 本教程使用STM32F407VET6零知增强板驱动3.5寸 ILI9486的TFT触摸屏扩展板实现经典Flappy Bird游戏。通过触摸屏控制小鸟跳跃&#xff0c;躲避障碍物柱体&#xff0c;挑战最高分。项目涉及STM32底层驱动、图形库移植、触摸控制和游戏逻辑设计。 目录 简介 一、硬件准备 二…

数据安全中心是什么?如何做好数据安全管理?

目录 一、数据安全中心是什么 &#xff08;一&#xff09;数据安全中心的定义 &#xff08;二&#xff09;数据安全中心的功能 1. 数据分类分级 2. 访问控制 3. 数据加密 4. 安全审计 5. 威胁检测与响应 二、数据安全管理的重要性 三、如何借助数据安全中心做好数据安…

Monorepo 详解:现代前端工程的架构革命

以下是一篇关于 Monorepo 技术的详细技术博客&#xff0c;采用 Markdown 格式&#xff0c;适合发布在技术社区或团队知识库中。 &#x1f9e9; 深入理解 Monorepo&#xff1a;现代项目管理的利器 在现代软件开发中&#xff0c;项目规模日益庞大&#xff0c;模块之间的依赖关系…

16-前端Web实战(Tlias案例-部门管理)

在前面的课程中&#xff0c;我们学习了Vue工程化的基础内容、TS、ElementPlus&#xff0c;那接下来呢&#xff0c;我们要通过一个案例&#xff0c;加强大家对于Vue项目的理解&#xff0c;并掌握Vue项目的开发。 这个案例呢&#xff0c;就是我们之前所做的Tlias智能学习辅助系统…

电路学习(二)之电容

电容的基本功能是通交流隔直流、存储电量&#xff0c;在电路中可以进行滤波、充放电。 1.什么是电容&#xff1f; &#xff08;1&#xff09;电容定义&#xff1a;电容器代表了器件存储电荷的能力&#xff0c;通俗来理解是两块不连通的导体与绝缘的中间体组成。当给电容充电时…

CTA-861-G-2017中文pdf版

CTA-861-G标准&#xff08;2016年11月发布&#xff09;规范未压缩高速数字接口的DTV配置&#xff0c;涵盖视频格式、色彩编码、辅助信息传输等&#xff0c;适用于DVI、HDMI等接口&#xff0c;还涉及EDID数据结构及HDR元数据等内容。

港大NVMIT开源Fast-dLLM:无需重新训练模型,直接提升扩散语言模型的推理效率

作者&#xff1a;吴成岳&#xff0c;香港大学博士生 原文&#xff1a;https://mp.weixin.qq.com/s/o0a-swHZOplknnNxpqlsaA 最近的Gemini Diffusion语言模型展现了惊人的throughput和效果&#xff0c;但是开源的扩散语言模型由于缺少kv cache以及在并行解码的时候性能严重下降等…

ESP32-C3 Vscode+ESP-IDF开发环境搭建 保姆级教程

1.背景 最近esp32的芯片很火&#xff0c;因为芯片自带了WIFI和BLE功能&#xff0c;是物联网项目开发的首选芯片&#xff0c;所以&#xff0c;我也想搞个简单的esp32芯片试试看。于是&#xff0c;我设计了一个简单的板子。如下 这块板子很简单&#xff0c;主要的电路来自于乐鑫…

解决vscode打开一个单片机工程文件(IAR/keil MDK)因无法找到头文件导致的结构体成员不自动补全问题。

最近一直在用vscode安装c/c插件后编辑STM32标准库&#xff08;keil MDK&#xff09;项目源文件&#xff0c;因为我感觉vscode在代码编辑方面比keil MDK本身优秀太多。发现打开工程后&#xff0c;结构体变量的成员在输入“.”后不自己弹出的问题&#xff0c;后来查找各方资料&am…

【Node.js 深度解析】npm install 遭遇:npm ERR! code CERT_HAS_EXPIRED 错误的终极解决方案

目录 &#x1f4da; 目录&#xff1a;洞悉症结&#xff0c;精准施治 &#x1f50d; 一、精准剖析&#xff1a;CERT_HAS_EXPIRED 的本质 &#x1f575;️ 二、深度溯源&#xff1a;证书失效的 N 重诱因 &#x1f4a1; 三、高效解决策略&#xff1a;六脉神剑&#xff0c;招招…

Vue内置组件Teleport和Suspense

一. Vue内置组件Teleport 认识Teleport( teleport&#xff1a;允许我们把组件的模板渲染到特定的元素上) 1.1. 在组件化开发中&#xff0c;我们封装一个组件A&#xff0c;在另外一个组件B中使用 组件A中template的元素&#xff0c;会被挂载到组件B中template的某个位置&#xf…

Java网络编程实战:TCP/UDP Socket通信详解与高并发服务器设计

&#x1f50d; 开发者资源导航 &#x1f50d;&#x1f3f7;️ 博客主页&#xff1a; 个人主页&#x1f4da; 专栏订阅&#xff1a; JavaEE全栈专栏 内容&#xff1a; socket(套接字)TCP和UDP差别UDP编程方法使用简单服务器实现 TCP编程方法Socket和ServerSocket之间的关系使用简…

vue+threeJs 绘制3D圆形

嗨&#xff0c;我是小路。今天主要和大家分享的主题是“vuethreeJs 绘制圆形”。 今天找到一个用three.js绘制图形的项目&#xff0c;主要是用来绘制各种形状。 项目案例示意图 1.THREE.ShapeGeometry 定义&#xff1a;是 Three.js 中用于从 2D 路径形状&#xff08…

Silky-CTF: 0x02靶场

Silky-CTF: 0x02 来自 <Silky-CTF: 0x02 ~ VulnHub> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.128&#xff0c;靶场IP192.168.23.131 3&#xff0c;对靶机进…

Kafka 的优势是什么?

Kafka 作为分布式流处理平台的核心组件&#xff0c;其设计哲学围绕高吞吐、低延迟、高可扩展性展开&#xff0c;在实时数据管道和大数据生态中具有不可替代的地位。 一、超高吞吐量与低延迟 1. 磁盘顺序 I/O 优化 突破磁盘瓶颈&#xff1a;Kafka 将消息持久化到磁盘&#xff…

基于FPGA + JESD204B协议+高速ADC数据采集系统设计

摘 要&#xff1a; 针对激光扫描共聚焦显微镜的大视场、高分辨率需求&#xff0c;为在振镜扫描的时间内获取更多数据量&#xff0c;设计一种基 于 FPGA 的高速数据采集系统。该系统采用 Xilinx 的 A7 系列 FPGA 作为主控芯片&#xff0c;同时选用 TI 公司提供的 LM…

Ubuntu20.04 LTS 升级Ubuntu22.04LTS 依赖错误 系统崩溃重装 Ubuntu22.04 LTS

服务器系统为PowerEdge R740 BIOS Version 2.10.2 DELL EMC 1、关机 开机时连续按键盘F2 2、System Setup选择第一个 System BIOS 3、System BIOS Setting 选择 Boot Setting 4、System BIOS Setting-Boot Setting 选择 BIOS Boot Settings 5、重启 开启时连续按键盘F11 …

测量3D翼片的距离与角度

1&#xff0c;目的。 测量3D翼片的距离与角度。说明&#xff1a; 标注A 红色框选的区域即为翼片&#xff0c;本示例的3D 对象共有3个翼片待测。L1与L2的距离、L1与L2的角度即为所求的翼片距离与角度。 2&#xff0c;原理。 使用线结构光模型&#xff08;标定模式&#xff0…

零基础学习计算机网络编程----socket实现UDP协议

本章将会详细的介绍如何使用 socket 实现 UDP 协议的传送数据。有了前面基础知识的铺垫。对于本章的理解将会变得简单。将会从基础的 Serve 的初始化&#xff0c;进阶到 Client 的初始化&#xff0c;以及 run。最后实现一个简陋的小型的网络聊天室。 目录 1.UdpSever.h 1.1 构造…

谷歌地图2022高清卫星地图手机版v10.38.2 安卓版 - 前端工具导航

谷歌地图2022高清卫星地图手机版是由谷歌公司推出的一款非常好用的手机地图服务软件&#xff0c;用户能够通过精准的导航和定位来查看地图&#xff0c;周边的商店等生活服务都会在地图上显示&#xff0c;用起来超级方便。 谷歌卫星高清地图 下载链接&#xff1a;夸克网盘分享 …