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

【Spring】Spring AOP

在这里插入图片描述

文章目录

  • 前言
  • 1. 什么是 AOP
  • 2. 什么是 Spring AOP
  • 3. Spring AOP 的使用
    • 引入 AOP 依赖
    • 编写 AOP 程序
  • 4. Spring AOP 详解
    • 4.1 Spring AOP 的概念
      • 4.1.1 切点
      • 4.1.2 连接点
      • 4.1.3 通知
      • 4.1.4 切面
    • 4.2 通知类型
    • 4.3 切点
    • 4.4 切面优先级 @Order注解
    • 4.5 切点表达式
      • 4.5.1 execution 切点表达式
      • 4.5.2 @annotation
        • 4.5.2.1 自定义注解
        • 4.5.2.2 切面类
        • 4.5.2.3 添加自定义注解

前言

前面我们学习了 SpringBoot 统一功能处理,这篇文章我将为大家分享 Spring 框架的第二大核心——AOP(第一大核心是 IOC)

1. 什么是 AOP

AOP(Aspect Oriented Programming)是一种编程范型,意为面向切面编程,什么是⾯向切面编程呢?切面就是指某⼀类特定问题,所以AOP也可以理解为面向特定⽅法编程,它通过预编译和运行期动态代理的方式实现程序功能的统一维护。AOP可以看作是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,同时也是函数式编程的一种衍生范型。

AOP的目标是实现对业务逻辑的各个部分进行隔离,从而降低业务逻辑各部分之间的耦合度,提高程序的可重用性,并提升开发效率。在AOP中,目标类代表核心代码和业务逻辑,而额外功能(也即AOP的功能)则包括日志处理、事务处理、异常处理、性能分析等。通过将目标类与额外功能结合,可以生成代理类。

AOP的原理基于Java的动态代理机制。通过预编译方式和运行期动态代理,AOP能够实现程序功能的统一维护,使得开发者能够更加专注于业务逻辑的实现,而无需过多关注其他非核心功能。

AOP 是面向某一特定问题的编程?那前面的统一功能处理什么呢?其实前面学习的 统一功能处理是 AOP 的具体实现和应用。AOP是⼀种思想,拦截器是AOP思想的⼀种实现。Spring框架实现了这种思想,提供了拦截器技术的相关接口。

2. 什么是 Spring AOP

知道了什么是 AOP,那么什么是 Spring AOP 呢?AOP 是一种思想,而 Spring AOP、AspectJ、GGLIB等叫做 AOP 的实现。

那么我们前面学习的拦截器、统一数据返回格式、统一异常处理这些 AOP 的实现不够吗?其实是不够的,拦截器作⽤的维度是URL(⼀次请求和响应),@ControllerAdvice 应⽤场景主要是全局异常处理(配合⾃定义异常效果更佳),数据绑定,数据预处理。AOP作⽤的维度更加细致(可以根据包、类、⽅法名、参数等进⾏拦截),能够实现更加复杂的业务逻辑。

假设一个项目中开发了很多业务功能,但是呢?由于一些业务的执行效率比较低,耗时较长,所以我们就需要对接口进行优化。首先需要做的就是找到耗时较长的业务方法,那么我们如何知道每个业务方法执行的时间呢?一个简单的方法就是可以通过获取到业务方法刚执行时候的时间 start,再记录这个业务方法刚结束时候的时间 end,用 end - statr,就得到了该业务方法的执行时间,那么,是否意味着我们需要在每个方法中都加上这段代码呢?

public void function1() {long startTime = System.currentTimeMillis();test();long endTime = System.currentTimeMillis();log.info("function1执行耗时" + (endTime - startTime));
}

如果每个方法都加上这样的逻辑的话,如果业务中的方法很多的话,那么这也是一个很大的工作量,所以这时候就可以用到我们的 AOP 了,AOP可以做到在不改动这些原始⽅法的基础上,针对特定的⽅法进行功能的增强。

3. Spring AOP 的使用

引入 AOP 依赖

AOP 属于第三方库,要想使用的话,就需要在 pom.xml 文件中引入对应的 AOP 依赖:

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

编写 AOP 程序

首先我们需要在类上加上 @Aspect 注解,表明这个类是一个 AOP 类:

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Slf4j
@Aspect
@Component
public class TimeAspect {@Around("execution(* com.example.springbootbook2.controller.*.*(..))")public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {long begin = System.currentTimeMillis();//执行原始方法Object result = pjp.proceed();long end = System.currentTimeMillis();log.info(pjp.getSignature() + "执行耗时:{}ms",(end - begin));return result;}
}
  • @Aspect:标识这是⼀个切面类
  • @Around:环绕通知,在目标⽅法的前后都会被执⾏。后⾯的表达式表示对哪些方法进行增强
  • pjp.proceed():让原始⽅法执行

因为 @Around 注解的作用,这个代码被分为了三个部分:

在这里插入图片描述

看看执行效果:

在这里插入图片描述

可以看到:使用 AOP 可以在不更改原来业务代码的基础上,做出一些额外的工作。

AOP 面向切面编程的优势:

  • 代码无侵入:不修改原始的业务方法,就可以对原始的业务方法进行功能的增强或是功能的改变
  • 减少重复代码
  • 提高开发效率
  • 维护方便

既然 AOP 面向切面编程有这么多优势,拿那么接下来我们来详细的学习一下 Spring AOP。

4. Spring AOP 详解

4.1 Spring AOP 的概念

Spring AOP 需要了解的概念主要有:

  1. 切点
  2. 连接点
  3. 通知
  4. 切面

4.1.1 切点

切点(PointCur)也成为切入点,作用是提供一组规则,告诉程序哪些方法来进行功能增强。也就类似前面拦截器中的配置拦截路径。

也就是这个:

在这里插入图片描述
execution(* com.example.springbootbook2.controller.*.*(..))叫做切点表达式,这里后面为大家详细介绍。

4.1.2 连接点

满足切点表达式规则的方法,就是连接点,也就是可以被 AOP 控制的方法,参考上面的例子也就是 com.example.springboot2.controller 包下的所有类中的所有方法都叫做连接点。在我们上面的例子中主要体现在 pjp 参数中:

在这里插入图片描述
通过这个参数的 proceed() 方法可以执行目标方法。

4.1.3 通知

通知就是具体要做的工作,指那些重复的逻辑,也就是共性功能(最终体现为一个方法):

在这里插入图片描述

在AOP面向切面编程中,我们把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容。

4.1.4 切面

切面(Aspect)就是 切点(PointCut) + 通知(Advice)

通过切面就能够描述当前 AOP 程序需要针对于哪些⽅法,在什么时候执⾏什么样的操作。切面既包含了通知逻辑的定义,也包括了连接点的定义。

在这里插入图片描述
一个切面类可以存在多个切面。

4.2 通知类型

Spring AOP 中的通知类型有以下几种:

  1. @Around:环绕通知,此注解标注的通知方法在目标方法前,都被执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  3. @After:后置通过,此注解标注的通知方法在目标方法后被执行,⽆论是否有异常都会执行
  4. AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会被执行
  5. @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

接下来我们通过一个例子来了解这几种通知类型:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Slf4j
@Aspect
@Component
public class AspectDemo {@Before("execution(* com.example.springaop.controller.*.*(..))")public void doBefore() {log.info("执行 Before 方法");}@After("execution(* com.example.springaop.controller.*.*(..))")public void doAfter() {log.info("执行 After 方法");}@AfterReturning("execution(* com.example.springaop.controller.*.*(..))")public void doAfterReturning() {log.info("执行 AfterReturning 方法");}@AfterThrowing("execution(* com.example.springaop.controller.*.*(..))")public void doAfterThrowing() {log.info("执行 AfterThrowing 方法");}@Around("execution(* com.example.springaop.controller.*.*(..))")public Object doAround(ProceedingJoinPoint pjp) throws Throwable {log.info("Around 方法开始执行");Object result = pjp.proceed();log.info("Around 方法执行后");return result;}
}
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@RestController
@RequestMapping("test")
public class TestController {@RequestMapping("/t1")public String t1() {log.info("目标方法执行");return "hello";}
}

在这里插入图片描述
这里少了一个日志就是 AfterThrowing 方法打印的日志,这是因为我们这个方法没有出现错误,所以我们重新定义一个内部会剖出异常的方法:

@RequestMapping("/t2")
public Integer t2() {log.info("目标方法2执行");Integer result = 10/0;return result;
}

在这里插入图片描述
可以看到这几个通知类型的执行顺序:

目标方法中不出现异常:Around通知类型的目标方法执行前的逻辑——>Before通知类型——>目标方法——>AfterReturning通知类型——>After通知类型——>Around通知类型的目标方法执行之后的逻辑

目标方法中出现异常:Around通知类型的目标方法执行前的逻辑——>Before通知类型——>目标方法——>AfterThrowing通知类型——>After通知类型

注意:

  • @Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通过不需要考虑目标方法的执行
  • @Around 环绕通知方法的返回值,必须指定为 Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的
  • ⼀个切面类可以有多个切点

4.3 切点

通过前面的例子,我们可以发现,同样的切点表达式写了很多,那么是否有一种方法可以节省相同切点表达式的书写呢?当然是可以的,我们程序员可是以“懒”著称的。

我们可以使用 @PointCut 注解将相同的切点表达式提取出来,需要用到时引用该切点表达式即可。

@Pointcut("切点表达式")
修饰限定词 返回值 方法名(){}
public class AspectDemo {@Pointcut("execution(* com.example.springaop.controller.*.*(..))")private void pt(){}@Before("pt()")public void doBefore() {log.info("执行 Before 方法");}@After("pt()")public void doAfter() {log.info("执行 After 方法");}@AfterReturning("pt()")public void doAfterReturning() {log.info("执行 AfterReturning 方法");}@AfterThrowing("pt()")public void doAfterThrowing() {log.info("执行 AfterThrowing 方法");}@Around("pt()")public Object doAround(ProceedingJoinPoint pjp) throws Throwable {log.info("Around 方法开始执行");Object result = pjp.proceed();log.info("Around 方法执行后");return result;}
}

在这里插入图片描述

我们这里 @PointCut 注解的方法的修饰限定词是 private,该切点表达式只能在当前类中使用,如果我们想要在当前项目的其他切面类中使用这个提取出来的切点表达式的话,需要将方法的修饰限定词改为 public,并且引用这个切点表达式的方法需要使用 全限定类名.方法名()

@Pointcut("execution(* com.example.springaop.controller.*.*(..))")
public void pt(){}
@Slf4j
@Component
@Aspect
public class AspectDemo2 {@Before("com.example.springaop.aspect.AspectDemo.pt()")public void deBefore() {log.info("执行 AspectDemo2 中的 Before 方法");}
}

在这里插入图片描述

4.4 切面优先级 @Order注解

当存在多个切面的时候,并且这些切面类的多个切入点都匹配到了同一个目标方法,当目标方法运行的时候,这些切面类中的通知方法都会执行,这些通知方法的执行顺序会遵守两个原则:

  1. 前面通知类型的先后顺序
  2. 切面类的类名排序

我们创建出三个切面类,来看多个切面类匹配到了同一个目标方法之后的通知方法的执行顺序:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
通过这个运行结果可以知道,当存在多个切面类时,默认按照切面类名字母排序:

  • @Before 通知:字母排名靠前的先执行
  • @After 通知,字母排名靠前的后执行

就可以形象的看成这个模型:

在这里插入图片描述
但是根据切面类的类名字母排序的话,很不方便,所以为了切面类执行顺序更好的控制,就出现了一个注解 @Order() 用来控制多个切面类的执行顺序。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以看到,通过使用 @Order 注解可以控制多个切面类的执行顺序。

4.5 切点表达式

前面我们使用切点表达式来描述切点,接下来我们来详细介绍一下切点表达式的语法。

切点表达式常见的有两种表达方式:

  1. execution(…):根据方法的签名来匹配
  2. @annotation(…):根据注解匹配

4.5.1 execution 切点表达式

execution() 是最常用的切点表达式,用来匹配方法,它的语法为:

execution(<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)

其中访问修饰符和异常可以省略。

在这里插入图片描述
切点表达式支持通配符表达:

  1. *:匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)
  • 包名使用 * 表示任意包(一层包使用一个*)
  • 类名使用 * 表示任意类
  • 返回值使用 * 表示任意返回值类型
  • ⽅法名使用 * 表示任意⽅法
  • 参数使用 * 表示⼀个任意类型的参数
  1. … :匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数
  • 使用 … 配置包名,表示此包以及此包下的所有子包
  • 可以使用 … 配置参数,任意个任意类型的参数

示例:

TestController 下的 public修饰,返回类型为 String 方法名为 t1,⽆参⽅法

execution(public String com.example.demo.controller.TestController.t1())

省略访问修饰符:

execution(String com.example.demo.controller.TestController.t1())

匹配所有返回类型:

execution(* com.example.demo.controller.TestController.t1())

匹配 TestController 下的所有无参方法:

execution(* com.example.demo.controller.TestController.*())

匹配 TestController 下的所有方法:

execution(* com.example.demo.controller.TestController.*(..))

匹配 controller 包下所有的类的所有方法:

execution(* com.example.demo.controller.*.*(..))

匹配所有包下⾯的 TestController:

execution(* com..TestController.*(..))

匹配 com.example.demo 包下,子孙包下的所有类的所有⽅法:

execution(* com.example.demo..*(..))

4.5.2 @annotation

execution 表达式适用于更符合规则的,如果我们要匹配更多无规则的方法的话,使用 execution 表达式就会很吃力。

在这里插入图片描述

假设我需要捕获到 t2 和 t3 方法,使用 execution 表达式该如何写呢?因为这两个方法的返回值不同,使用 execution 表达式无法同时表示出这两个方法。所以这时候就需要使用 @annotation 注解来捕获到更多无规则的方法。

4.5.2.1 自定义注解

如何使用 @annotaion 呢?首先我们需要自定义出注解。

在新建 Java class 的时候选择 Annotation:

在这里插入图片描述

然后里面的内容我们就实现一个最简单的注解,参考 @Component 注解来构造一个自定义的注解:

在这里插入图片描述

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

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

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

  1. RetentionPolicy.SOURCE:表示注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使⽤。比如 @SuppressWarnings ,以及lombok提供的注解 @Data ,@Slf4j
  2. RetentionPolicy.CLASS:编译时注解。表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时⽆法获取。通常⽤于⼀些框架和⼯具的注解
  3. RetentionPolicy.RUNTIME:运行时注解。表示注解存在于源代码,字节码和运行时中。这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息。通常⽤于⼀些需要在运行时处理的注解,如Spring的 @Controller @ResponseBody

我们一般就使用 RUNTIME 来指明注解的存在时间。

package com.example.springaop.aspect;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
4.5.2.2 切面类

使用 @annotation 切点表达式定义切点,只对 @MyAspect ⽣效:

@annotation 中需要填入我们自定义注解的全限定名。

@Slf4j
@Component
@Aspect
public class MyAspectDemo {@Before("@annotation(com.example.springaop.aspect.MyAspect)")public void before() {log.info("执行 MyAspectDemo 的 Before 方法");}@After("@annotation(com.example.springaop.aspect.MyAspect)")public void after() {log.info("执行 MyAspect 的 After 方法");}
}
4.5.2.3 添加自定义注解
@Slf4j
@RestController
@RequestMapping("test")
public class TestController {@RequestMapping("/t1")public String t1() {log.info("目标方法执行");return "hello";}@MyAspect@RequestMapping("/t2")public String t2() {log.info("我爱Java,我要成为Java高手");return "hello world";}@MyAspect@RequestMapping("/t3")public Integer t3() {log.info("目标方法2执行");Integer result = 10/0;return result;}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到通过使用 @annotation 可以更加灵活的捕获到无规则的方法。

相关文章:

【Spring】Spring AOP

文章目录 前言1. 什么是 AOP2. 什么是 Spring AOP3. Spring AOP 的使用引入 AOP 依赖编写 AOP 程序 4. Spring AOP 详解4.1 Spring AOP 的概念4.1.1 切点4.1.2 连接点4.1.3 通知4.1.4 切面 4.2 通知类型4.3 切点4.4 切面优先级 Order注解4.5 切点表达式4.5.1 execution 切点表达…...

软件开发架构

【 一 】软件开发架构图 【 1】ATM和选课系统 三层的开发架构 前段展示台 后端逻辑层 数据处理层 【二】软件开发架构的步骤流程 需求分析&#xff1a;在软件开发架构设计之前&#xff0c;需要对应用系统进行需求分析&#xff0c;明确用户需求、功能模块、业务流程等内容。…...

计图大模型推理库部署指南,CPU跑大模型,具有高性能、配置要求低、中文支持好、可移植等特点

Excerpt 计图大模型推理库,具有高性能、配置要求低、中文支持好、可移植等特点 计图大模型推理库,具有高性能、配置要求低、中文支持好、可移植等特点 计图大模型推理库 - 笔记本没有显卡也能跑大模型 本大模型推理库JittorLLMs有以下几个特点: 成本低:相比同类框架,本库…...

CSS||Emmet语法

1、简介 ​ Emmet语法的前身是Zen coding,它使用缩写,来提高html/css的编写速度, Vscode内部已经集成该语法。 ​ 快速生成HTML结构语法 ​ 快速生成CSS样式语法 2、快速生成HTML结构语法 生成标签 直接输入标签名 按tab键即可 比如 div 然后tab 键&#xff0c; 就可以生成 <…...

Android中的anr定位指导与建议

1.背景 8月份安卓出现了一次直播间卡死(ANR)问题&#xff0c;且由于排查难度较大&#xff0c;持续了较长时间。本文针对如何快速定位安卓端出现ANR问题进行总结和探讨. 这里大致补充一下当时的情况,当时看到情景的是从某一个特定的场景下进入直播间后整个直播间界面立刻就卡住…...

YOLOV7剪枝流程

YOLOV7剪枝流程 1、训练 1&#xff09;划分数据集进行训练前的准备&#xff0c;按正常的划分流程即可 2&#xff09;修改train.py文件 第一次处在参数列表里添加剪枝的参数&#xff0c;正常训练时设置为False&#xff0c;剪枝后微调时设置为True parser.add_argument(--pr…...

【React】组件性能优化、高阶组件

文章目录 React性能优化SCUReact更新机制keys的优化render函数被调用shouldComponentUpdatePureComponentshallowEqual方法高阶组件memo 获取DOM方式refs如何使用refref的类型 受控和非受控组件认识受控组件非受控组件 React的高阶组件认识高阶函数高阶组件的定义应用一 – pro…...

前端开发中基于Web Speech API(speechSynthesis接口)实现文字转语音功能

文章目录 一、Web Speech 的概念及用法二、Web Speech 的 API 接口1、SpeechSynthesis属性方法 2、SpeechSynthesisUtterance属性方法 三、Web Speech 的 用法用法演示一用法演示二htmljs 四、扩展 一、Web Speech 的概念及用法 在开发业务系统时&#xff0c;有时候可能需要使…...

C++核心编程之通过类和对象的思想对文件进行操作

目录 ​​​​​​​一、文件操作 1. 文件类型分类&#xff1a; 2. 操作文件的三大类 二、文本文件 1.写文件 2.读文件 三、二进制文件 1.写二进制文件 2.读二进制文件 一、文件操作 程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放 通过文件可以将…...

Verilog语法——4.Verilog工程模板、相应规范再强调

参考资料 【明德扬_verilog零基础入门语法HDL仿真快速掌握-手把手教你写FPGA/ASIC代码设计流程中的应用】 4. Verilog工程模板、相应规范 4.1 Verilog工程模板 4.1.1 设计模块模板 module module_name(clk,rst_n,//其他信号&#xff0c;举例doutdout };//参数定义parameter …...

CNN:Convolutional Neural Network(下)

目录 1 CNN 学到的是什么 1.1 Convolution 中的参数 1.2 FFN 中的参数 1.3 Output 2 Deep Dream 3 Deep Style 4 More Application 4.1 AlphaGo 4.2 Speech 4.3 Text 原视频&#xff1a;李宏毅 2020&#xff1a;Convolutional Neural Network 本博客属于学…...

FPGA时序分析与时序约束(四)——时序例外约束

目录 一、时序例外约束 1.1 为什么需要时序例外约束 1.2 时序例外约束分类 二、多周期约束 2.1 多周期约束语法 2.2 同频同相时钟的多周期约束 2.3 同频异相时钟的多周期约束 2.4 慢时钟域到快时钟域的多周期约束 2.5 快时钟域到慢时钟域的多周期约束 三、虚假路径约…...

无需任何三方库,在 Next.js 项目在线预览 PDF 文件

前言&#xff1a; 之前在使用Vue和其它框架的时候&#xff0c;预览 PDF 都是使用的 PDFObject 这个库&#xff0c;步骤是&#xff1a;下载依赖&#xff0c;然后手动封装一个 PDF 预览组件&#xff0c;这个组件接收本地或在线的pdf地址&#xff0c;然后在页面中使用组件的车时候…...

排序问题——晴问题库

排序问题——晴问题库 排序问题是线性数据的常考问题&#xff0c;在解晴问题库时总结归纳以下关于排序的解题方法和思路。 感谢晴神 排序问题是数据结构中十分重点的一部分。 可以分为五个大部分&#xff1a; 插入排序选择排序交换排序基数归并 再具体往下分&#xff1a; 插…...

【LabVIEW FPGA入门】FPGA中的数学运算

数值控件选板上的大部分数学函数都支持整数或定点数据类型&#xff0c;但是需要请注意&#xff0c;避免使用乘法、除法、倒数、平方根等函数&#xff0c;此类函数比较占用FPGA资源&#xff0c;且如果使用的是定点数据或单精度浮点数据仅适用于FPGA终端。 1.整数运算 支持的数…...

华为设备VRRP配置

核心代码&#xff1a; 需要对所有虚拟路由器设置&#xff08;要进入到对应的端口&#xff09; vrrp vrid 38 virtual-ip 192.168.10.254 vrrp vrid 38 priority 120 vrrp vrid 38 track int g0/0/1 reduced 30①mac由vrid生成 ②指定虚拟ip ③虚拟ip作为内部主机的网关&#x…...

2024年艺术发展与文化产业国际会议(ICADCI 2024)

2024年艺术发展与文化产业国际会议(ICADCI 2024) 2024 International Conference on Art Development and Cultural Industry(ICADCI 2024) 数据库&#xff1a;EI,CPCI,CNKI,Google Scholar等 一、【会议简介】 2024年艺术发展与文化产业国际会议(ICADCI 2024)将于丽江这座美丽…...

华为手表开发:WATCH 3 Pro(10)获取心率_java 华为手表获取心跳

华为手表开发:WATCH 3 Pro(10)获取心率_java 华为手表获取心跳 Excerpt 文章浏览阅读1.2k次。鸿蒙开发,获取手表心跳,按钮点击后触发的方法,我们将跳转页面的代码写在这个位置就可以实现点击按钮进行跳转页面的动作。在HTML文件“index.hml”,添加按钮,这里按钮用到是标…...

使用企业订货软件的担忧与考虑|网上APP订货系统

使用企业订货软件的担忧与考虑|网上APP订货系统 网上订货系统担心出现的问题 1&#xff0c;如果在订货系统中定错(多)货物了该怎么办 其实这也是很多人在网购或者是现实中经常会犯的一个错误&#xff0c;但是网上订货平台为大家提供了很多的解决方案&#xff0c;其中对于订单的…...

Java-集合-Collection类

1 需求 2 接口 Interface Collection<E> public interface Collection<E> extends Iterable<E> 2.3 Method Detail int size()boolean isEmpty()boolean contains(Object o)Iterator<E> iterator()Object[] toArray()<T> T[] toArray(T[] a)…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

工程地质软件市场:发展现状、趋势与策略建议

一、引言 在工程建设领域&#xff0c;准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具&#xff0c;正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

Unity UGUI Button事件流程

场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...

c# 局部函数 定义、功能与示例

C# 局部函数&#xff1a;定义、功能与示例 1. 定义与功能 局部函数&#xff08;Local Function&#xff09;是嵌套在另一个方法内部的私有方法&#xff0c;仅在包含它的方法内可见。 • 作用&#xff1a;封装仅用于当前方法的逻辑&#xff0c;避免污染类作用域&#xff0c;提升…...