【JavaEE进阶】Spring AOP使用篇

目录

1.AOP概述

2.SpringAOP快速入门

2.1 引入AOP依赖

2.2 编写AOP程序

3. Spring AOP详解

3.1 Spring AOP 核心概念

3.1.1切点(Pointcut)

3.1.2 连接点 (Join Point)

3.1.3 通知(Advice)

3.1.4 切面(Aspect)

3.2 通知类型

3.3@PointCut

3.4 切面优先级

3.5 切点表达式

3.5.1 execution 表达式

3.5.2 @annotation

3.5.2.1 自定义注解 @MyAspect

3.5.2.2 切面类

3.5.2.3 添加自定义注解

4. Spring AOP的实现方式


1.AOP概述

AOP是Spring框架的一大核心(第一大核心是loC)

什么是AOP?

Aspect Oriented Programming(面向切面编程)

什么是面向切面编程呢?切面就是指某一类特定问题, 所以 AOP 也可以理解为面向特定方法编程.

什么是面向特定方法编程呢? 比如以前学习的"登录校验", 就是一类特定问题.  登录校验拦截器, 就
是对 "登录校验" 这类问题的统一处理. 所以,拦截器也是 AOP 的一种应用. AOP 是一种思想,拦截器是 AOP 思想的一种实现. Spring 框架实现了这种思想,  提供了拦截器技术的相关接口.

同样的, 统一数据返回格式和统一异常处理,也是AOP思想的一种实现.

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

什么是 Spring AOP ?

AOP是一种思想,它的实现方法有很多,有Spring AOP, 也有AspectJ, CGLIB等.

Spring AOP是其中的一种实现方式.

学会了统一功能之后,是不是就学会了Spring AOP呢,当然不是.

截器作用的维度是URL(一次请求和响应), @ControllerAdvice 应用场景主要是全局异常处理
(配合自定义异常效果更佳), 数据绑定 , 数据预处理.  AOP 作用的维度更加细致(可以根据包、类、方法名、参数等进行拦截),  能够实现更加复杂的业务逻辑.

举个例子:

我们现在有一个项目, 项目中开发了很多的业务功能

现在有一些业务的执行效率比较低, 耗时较长, 我们需要对接口进行优化.

第一步就需要定位出执行耗时比较长的业务方法, 在针对该业务方法来进行优化

如何定位呢? 我们就需要统计当前项目中每一个业务方法的执行耗时.

如何统计呢? 可以在业务方法运行前和运行后,  记录下方法的开始时间和结束时间, 两者之差就是这个方法的耗时. 

这种方法是可以解决问题的,但一个项目中会包含很多业务模块,每个业务模块又有很多接口,一个接口又包含很多方法, 如果我们要在每个业务方法中都记录方法的耗时,对于程序员而言, 会增加很多的工作量.

AOP 就可以做到在不改动这些原始方法的基础上, 针对特定的方法进行功能的增强.

AOP 的作用: 在程序执行期间不修改源代码的基础上对已有方法的增强(无侵入性: 解耦)

接下来我们来看看 SpringAOP 如何来实现.

2.SpringAOP快速入门

学习完什么是 AOP 后, 我们先通过下面的程序体验下 AOP 的开发, 并掌握 Spring 中 AOP 的开发步骤.

需求: 统计图书系统中各个接口方法的执行时间.

2.1 引入AOP依赖

在 pom.xml 文件中添加配置

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

2.2 编写AOP程序

记录 Controller 中每个方法的执行时间

先看看传统的方法:

/*** 根据ID查询图书信息** @param bookId* @return*/@RequestMapping("/queryBookById")public BookInfo queryBookById(Integer bookId) {log.info("根据ID查询图书信息, id:{}", bookId);long start = System.currentTimeMillis();BookInfo bookInfo = bookService.queryBookById(bookId);long end = System.currentTimeMillis();log.info("[BookController] queryBookById 耗时: " + (end-start) + " ms");return bookInfo;}

使用这种方式, 当要测试多个接口的时候, 就需要对每个接口的代码进行修改 , 工作量比较复杂, 很影响我们的时间和效率.

下面来看看使用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;@Component
@Slf4j
@Aspect
public class TimeRecordAspect {/*** 记录耗时*/@Around("execution(* com.example.demo.controller.*.*(..))")public Object TimeRecord(ProceedingJoinPoint joinPoint) throws Throwable {//记录开始事件long start = System.currentTimeMillis();//执行目标方法Object proceed = joinPoint.proceed();//记录结束时间long end = System.currentTimeMillis();//日志打印耗时log.info(joinPoint.getSignature() + " 耗时时间: " + (end- start) + " ms");return proceed;}
}

运行程序, 观察日志

对程序进行简单的讲解:

1. @Aspect: 标识这是一个切面类

2. @Around: 环绕通知, 在目标方法的前后都会被执行. 后⾯的表达式表示对哪些方法进行增强.

3. ProceedingJoinPoint.proceed(): 让原始方法执行

整个代码划分为三部分

我们通过AOP入门程序完成了业务接口执行耗时的统计.

通过上面的程序,我们也可以感受到AOP面向切面编程的一些优势:

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

3. Spring AOP详解

下面我门再来详细学习AOP, 主要是一下几个部分

Spring AOP 中涉及的核心概念

Spring AOP 的通知类型

多个 AOP 的执行顺序

3.1 Spring AOP 核心概念

3.1.1切点(Pointcut)

切点(Pointcut), 也称之为 "切入点"

Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述), 告诉程序对哪些方法来进行功能增强.

上面的表达式  execution(* com.example.demo.controller.*.*(..)) 就是切点表达式.

3.1.2 连接点 (Join Point)

满足切点表达式规则的方法, 就是连接点, 也就是可以被 AOP 控制的方法

以如门程序举例, 所有 com.example.demo.controller 路径下的方法, 都是连接点

package com.example.demo.controller;@RequestMapping("/book")
@RestController
public class BookController {@RequestMapping("/addBook")public Result addBook(BookInfo bookInfo) {//...代码省略 }@RequestMapping("/queryBookById")public BookInfo queryBookById(Integer bookId) {//...代码省略 }@RequestMapping("/updateBook")public Result updateBook(BookInfo bookInfo) {//...代码省略 }
}

上述 BookController 中的方法都是连接点

切点和连接点的关系

连接点是满足切点表达式的元素, 切点可以看作是保存了众多连接点的一个集合.

比如:

切点表达式: 全体教师

连接点就是: 张三, 李四等各个教室

3.1.3 通知(Advice)

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

比如上述程序中记录业务方法的耗时时间, 就是通知

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

3.1.4 切面(Aspect)

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

通过切面就能描述当前AOP程序需要针对于哪些方法, 在什么时候执行什么样的操作.

切面既包含了通知逻辑的定义, 也包括了连接点的定义.

切面所在的类, 我们一般称为切面类(被@Aspect注解标识的类)

3.2 通知类型

上面我们讲了什么是通知, 接下来学习通知的类型. @Around 就是其中的一种通知类型, 表示环绕通知.

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

@Around: 环绕通知, 次注解标注的通知方法在目标方法前, 后都被执行

@Before: 前置通知, 次注解表述的通知方法在目标方法前被执行

@After: 后置通知, 次注解标注的通知方法在目标方法后被执行, 无论是否有异常都会执行

@AfterReturning: 返回后通知, 次注解标注的通知方法在目标方法返回后被执行, 有异常不会执行

@AfterThrowing: 次注解标注的通知方法发生异常后执行

接下来我门通过代码来加深对这几个通知的理解:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Slf4j
@Aspect
public class AspectDemo {//前置通知@Before("execution(* com.example.aop.controller.*.*(..))")public void doBefore() {log.info("AspectDemo do before...");}//后置通知@After("execution(* com.example.aop.controller.*.*(..))")public void doAfter() {log.info("AspectDemo do after...");}//环绕通知@Around("execution(* com.example.aop.controller.*.*(..))")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("AspectDemo do around before...");Object result = null;try {result = joinPoint.proceed();}catch (Exception e) {log.error("do around 执行目标函数, 内部发生异常");}log.info("AspectDemo do around after...");return result;}//返回后通知@AfterReturning("execution(* com.example.aop.controller.*.*(..))")public void doAfterReturning() {log.info("AspectDemo do AfterReturning...");}//抛出异常后通知@AfterThrowing("execution(* com.example.aop.controller.*.*(..))")public void doAfterThrowing() {log.info("AspectDemo do AfterThrowing...");}
}

写一些测试程序:

import com.example.aop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RequestMapping("/test")
@RestController
@Slf4j
public class TestController {@MyAspect@RequestMapping("/t1")public String t1() {log.info("执行t1方法....");return "t1";}@RequestMapping("/t2")public String t2() {log.info("执行t2方法....");int a = 10/0;return "t2";}
}

运行程序, 观察日志:

1.正常运行的情况

观察日志

程序正常运行的情况下, @AfterThrowing 标识的通知方法不会执行

从上图也可以看出来, @Around 标识的通知方法包含两个部分, 一个 "前置逻辑" , 一个 "后置逻辑" .其中 "前置逻辑" 会先于 @Before 标识的通知方法执行. "后置逻辑" 会晚于 @After 标识的通知方法执行

2. 异常时的情况

观察日志

程序发生异常的情况下:

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

@Around 环绕通知中原始方法调用时有异常, 通知中的环绕后的代码也不会在执行了(因为原始方法调用出异常了)

注意事项:

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

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

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

3.3@PointCut

上面代码存在一个问题, 就是存在大量重复的切点表达式 execution(* com.example.demo.controller.*.*(..)) , Spring 提供了 @Pointcut 注解, 把公共的切点表达式提取出来, 需要用到时引入该切点表达式即可.

上述代码就可以修改为:

@Component
@Slf4j
@Aspect
public class AspectDemo {@Pointcut("execution(* com.example.aop.controller.*.*(..))")public void pt(){};//前置通知@Before("pt()")public void doBefore() {//...代码省略}//后置通知@After("pt()")public void doAfter() {//...代码省略}//添加环绕通知@Around("pt()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {//...代码省略}//返回后通知@AfterReturning("pt()")public void doAfterReturning() {//...代码省略}//抛出异常后通知@AfterThrowing("pt()")public void doAfterThrowing() {//...代码省略}
}

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

@Component
@Slf4j
@Aspect
public class AspectDemo2 {@Before("com.example.aop.aspect.AspectDemo.pt()")public void doBefore() {log.info("AspectDemo2 do before...");}@After("com.example.aop.aspect.AspectDemo.pt()")public void doAfter() {log.info("AspectDemo2 do after...");}
}

3.4 切面优先级

当我们在一个项目中,定义了多个切面类时,并且这些切面类的多个切入点都匹配到了同一个目标方法. 当目标方法运行的时候,这些切面类中的通知方法都会执行,那么这几个通知方法的执行顺序是什么样的呢?

我们还是通过程序来验证:

定义多个切⾯类:

为了防止干扰, 我们把AspectDemo这个切面先去掉(把@Component注解去掉就可以)

为了简单化, 只写了@Before 和 @After 两个通知

@Component
@Slf4j
@Aspect
public class AspectDemo2 {@Before("com.example.aop.aspect.AspectDemo.pt()")public void doBefore() {log.info("AspectDemo2 do before...");}@After("com.example.aop.aspect.AspectDemo.pt()")public void doAfter() {log.info("AspectDemo2 do after...");}
}
@Component
@Slf4j
@Aspect
public class AspectDemo3 {@Before("com.example.aop.aspect.AspectDemo.pt()")public void doBefore() {log.info("AspectDemo3 do before...");}@After("com.example.aop.aspect.AspectDemo.pt()")public void doAfter() {log.info("AspectDemo3 do after...");}
}
@Component
@Slf4j
@Aspect
public class AspectDemo4 {@Before("com.example.aop.aspect.AspectDemo.pt()")public void doBefore() {log.info("AspectDemo4 do before...");}@After("com.example.aop.aspect.AspectDemo.pt()")public void doAfter() {log.info("AspectDemo4 do after...");}
}

运行程序, 访问接口:

 观察日志:

通过上述程序的运行结果, 可以看出:

存在多个切面类时, 默认按照切面类的名字母排序:

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

但这种方式不方便管理,我们的类名更多还是具备一定含义的.

Spring给我们提供了一个新的注解,来控制这些切面通知的执行顺序: @Order

使用方式如下:

重新运行程序, 观察日志:

通过上述程序的运行结果, 得出结论:

@Order 注解标识的切面类, 执行顺序如下:

@Before 通知: 数字越小先执行

@After 通知: 数字越大先执行

@Order 控制切面的优先级, 先执行优先级高的切面, 在执行优先级较低的切面, 最终执行目标方法.

3.5 切点表达式

上面的代码中,我们一直在使用切点表达式来描述切点. 下面我们来介绍一下切点表达式的语法.

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

1. execution(......): 根据方法的签名来匹配

2. @annotation(......): 根据注解匹配

3.5.1 execution 表达式

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

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

切点表达式支持通配符表达:

1.: 匹配任意字符, 只匹配一个元素(返回类型, 包, 类名, 方法或者方法参数)

a. 包名使用 表示任意包(一层包使用一个 * )

b. 类名使用 表示任意类

c. 返回值使用 表示任意返回值类型

d. 方法名使用 表示任意方法

e. 参数使用 表示一个任意类型的参数

2. .. : 匹配多个连续的任意符号, 可以通配任意层级的包, 或任意类型, 任意个数的参数

a. 使用 .. 配置包名, 标识次包以及此包下的所有子包

b. 可以使用 .. 配置参数, 任意个任意类型的参数

切点表达式示例

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..*(..))

匹配特定方法名且有特定参数的方法:

execution(* myMethod(String, int))

 匹配特定方法名且有特定参数, 并且抛出特定异常的方法

execution(* myMethod(String, int) throws IOExeception)

3.5.2 @annotation

execution 表达式更适合有规则的, 如果我门要匹配多个无规则的方法呢, 比如: TestController中的 t1() 和 UserController 中的 u1() 这两个方法.

这个时候我们使用 execution 这种切点表达式来描述就不是很方便了.

我们可以借助自定义注解的方法以及另一种切点表达式 @annotation 来描述这一类的切点

实现步骤:

1. 编写自定义注解

2. 使用 @annotation 表达式来描述切点

3. 在连接点的方法上添加自定义注解

准备测试代码

@RequestMapping("/test")
@RestController
@Slf4j
public class TestController {@RequestMapping("/t1")public String t1() {log.info("执行t1方法....");return "t1";}@RequestMapping("/t2")public String t2() {log.info("执行t2方法....");int a = 10/0;return "t2";}
}
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController{@RequestMapping("/u1")public String u1() {log.info("执行u1方法...");return "u1";}@RequestMapping("/u2")public String u2() {log.info("执行u2方法...");return "u2";}
}
3.5.2.1 自定义注解 @MyAspect

创建一个注解类(和创建Class文件一样的流程, 选择Annotation就可以了)

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 {}

代码简单说明, 了解即可, 不做过多的解释

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

常用取值: 

ElementType.TYPE: 用于描述类, 接口(包括注解类型)或 enum 声明

ElementType.METHOD: 描述方法

ElementType.PARAMETER: 描述参数

ElementType.TYPE_USE: 可以标注任意类型

2. @Retention 指 Annotation 被保留的时间长短, 标明注解的声明周期

@Retention的取值有三种

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

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

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

3.5.2.2 切面类

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

切面类代码如下:

@Component
@Slf4j
@Aspect
public class MyAspectDemo {//前置通知@Before("@annotation(com.example.aop.config.MyAspect)")public void before() {log.info("MyAspect -> before...");}//后置通知@After("@annotation(com.example.aop.config.MyAspect)")public void after() {log.info("MyAspect -> after...");}
}
3.5.2.3 添加自定义注解

在 TestController 中 t1() 和 UserController 中的 u1() 这两个方法上添加自定义注解 @MyAspect, 其他方法不加

运行程序, 测试接口:

观察日志:

继续测试 t2, 观察日志:

切面通知未执行

4. Spring AOP的实现方式

1.基于注解 @Aspect

2. 基于自定义注解(参考上面自定义注解 @annotation 部分的内容)

3. 基于Spring API (通过xml配置的方法, 自从SpringBoot广泛使用之后, 这种方法几乎看不到了, 稍作了解即可)

4. 基于代理来实现(更加久远的一种方式, 写法笨重, 不建议使用)

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

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

相关文章

基于经典滑膜控制的永磁同步电机调速系统MATLAB仿真

滑膜控制器 取PMSM状态变量为&#xff1a; ωref为目标转速&#xff0c;ωm为电机输出转速。将此式求导得&#xff1a; 定义系统滑模面函数为&#xff1a; 对滑模面函数求导 在电机实际控制时&#xff0c;滑模控制方法存在高频抖振问题&#xff0c;则需要选取合适的指数趋近率…

web前端——css(一篇教会网页制作)

目录 一、基本语法 1.行内样式表 2.内嵌样式表 3.外部样式表 二、选择器 1.标签选择器 2.类选择器 3.id 选择器 4.通配选择器 三、常见修饰 1.文本 2.背景 3.列表 4.伪类 5.透明度 6.块级、行级、行级块标签 7.div 和 span 四、盒子模型&#xff08;重点&…

【PostgreSQL】守护数据安全:事务与数据完整性管理

目录 事务管理&#xff1a;确保操作的原子性 事务的概念与重要性 事务的启动与提交 事务的回滚&#xff08;ROLLBACK&#xff09;&#xff08; 数据一致性与隔离级别 隔离级别的解释 设置隔离级别 错误处理与事务的高级策略 异常处理&#xff08;SAVEPOINT & EXCE…

25届最近5年重庆邮电大学自动化考研院校分析

重庆邮电大学 目录 一、学校学院专业简介 二、考试科目指定教材 三、近5年考研分数情况 四、近5年招生录取情况 五、最新一年分数段图表 六、历年真题PDF 七、初试大纲复试大纲 八、学费&奖学金&就业方向 一、学校学院专业简介 二、考试科目指定教材 1、考试…

[数据集][目标检测]电缆钢丝绳线缆缺陷检测数据集VOC+YOLO格式1800张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1800 标注数量(xml文件个数)&#xff1a;1800 标注数量(txt文件个数)&#xff1a;1800 标注…

单例模式(下)

文章目录 文章介绍步骤安排及单例讲解step1&#xff1a;注册单例类型&#xff08;main.cpp&#xff09;step2&#xff1a;定义类和私有构造函数&#xff08;keyboardinputmanager.h&#xff09;step3:&#xff08;keyboardinputmanager.cpp&#xff09;step4&#xff1a;在qml中…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 特殊加密算法(200分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

Rust 跨平台-Android 和鸿蒙 OS

1. 安装 rustup rustup 是 Rust 的安装和版本管理工具 $ curl --proto https --tlsv1.2 https://sh.rustup.rs -sSf | sh 该命令会安装 rusup 和最新的稳定版本的 Rust&#xff1b;包括&#xff1a; rustc Rust 编译器&#xff0c;用于将 Rust 代码编译成可执行文件或库。 ca…

技术速递|Visual Studio Code 的 .NET MAUI 扩展现已正式发布

作者&#xff1a;Maddy Montaquila 排版&#xff1a;Alan Wang 今天&#xff0c;我们非常高兴地宣布 .NET MAUI VS Code 扩展插件结束了预览阶段&#xff0c;并将包含一些期待已久的新功能 - 包括 XAML IntelliSense 和 Hot Reload&#xff01; 什么是 .NET MAUI 扩展插件&…

GuLi商城-商品服务-API-三级分类-删除-页面效果

一步步学习Vue太慢了&#xff0c;准备跳过前端的学习&#xff0c;直接使用前端完整的项目 下载依赖npm install&#xff0c;会报错&#xff0c;排查了好久 我安装的是Node14&#xff0c;所以必须要安装4.14 Vscode终端输入&#xff1a;npm install node-sass4.14 输入&#x…

【博士每天一篇文献-综述】A survey on few-shot class-incremental learning

阅读时间&#xff1a;2023-12-19 1 介绍 年份&#xff1a;2024 作者&#xff1a;田松松&#xff0c;中国科学院半导体研究所&#xff1b;李璐思&#xff0c;老道明大学助理教授&#xff1b;李伟军&#xff0c;中国科学院半导体研究所AnnLab&#xff1b; 期刊&#xff1a; Neu…

LearnOpenGL - Android OpenGL ES 3.0 使用 FBO 进行离屏渲染

系列文章目录 LearnOpenGL 笔记 - 入门 01 OpenGLLearnOpenGL 笔记 - 入门 02 创建窗口LearnOpenGL 笔记 - 入门 03 你好&#xff0c;窗口LearnOpenGL 笔记 - 入门 04 你好&#xff0c;三角形OpenGL - 如何理解 VAO 与 VBO 之间的关系LearnOpenGL - Android OpenGL ES 3.0 绘制…

《Windows API每日一练》6.4 程序测试

前面我们讨论了鼠标的一些基础知识&#xff0c;本节我们将通过一些实例来讲解鼠标消息的不同处理方式。 本节必须掌握的知识点&#xff1a; 第36练&#xff1a;鼠标击中测试1 第37练&#xff1a;鼠标击中测试2—增加键盘接口 第38练&#xff1a;鼠标击中测试3—子窗口 第39练&…

vite-ts-cesium项目集成mars3d修改相关的包和配置参考

如果vite技术栈下使用原生cesium&#xff0c;请参考下面文件的包和配置修改&#xff0c;想用原生创建的viewer结合我们mars3d的功能的话。 1. package.json文件 "dependencies": {"cesium": "^1.103.0","mars3d": "^3.7.18&quo…

深度学习 - Transformer 组成详解

整体结构 1. 嵌入层&#xff08;Embedding Layer&#xff09; 生活中的例子&#xff1a;字典查找 想象你在读一本书&#xff0c;你不认识某个单词&#xff0c;于是你查阅字典。字典为每个单词提供了一个解释&#xff0c;帮助你理解这个单词的意思。嵌入层就像这个字典&#xf…

Micrometer+ZipKin分布式链路追踪

目录 背景MicrometerMicrometer与ZipKin之间的关系专业术语分布式链路追踪原理 ZipKin安装下载 MicrometerZipKin 案例演示相关文献 背景 一个系统页面上的按钮点击到结果反馈&#xff0c;在微服务框架里&#xff0c;是由N个服务组成返回结果&#xff0c;中间可能经过a->b-…

【Electron】Electron入门实现

Electron 学习笔记 Electron 是一个开源框架&#xff0c;允许开发者使用网页技术&#xff08;HTML、CSS 和 JavaScript&#xff09;来构建跨平台的桌面应用程序。它由 GitHub 开发并维护&#xff0c;最初是为了支持开发 Atom 编辑器。Electron 结合了 Chromium&#xff08;用于…

密码学及其应用 —— 对称加密技术

1. 对称加密、流加密和块加密 1.1 对称加密 对称加密&#xff08;也称为密钥加密&#xff09;是一种加密方式&#xff0c;其中加密和解密使用相同的密钥。这种加密方法基于二进制层面的操作&#xff0c;如XOR&#xff08;异或&#xff09;、SHIFT&#xff08;位移&#xff09;…

Redis Stream Redisson Stream

目录 一、Redis Stream1.1 场景1&#xff1a;多个客户端可以同时接收到消息1.1.1 XADD - 向stream添加Entry&#xff08;发消息 &#xff09;1.1.2 XREAD - 从stream中读取Entry&#xff08;收消息&#xff09;1.1.3 XRANGE - 从stream指定区间读取Entry&#xff08;收消息&…

【DevExpress】WPF DevExpressMVVM 24.1版本开发指南

DevExpressMVVM WPF 环境安装 前言重要Bug&#xff08;必看&#xff09;环境安装控件目录Theme 主题LoginWindow 登陆窗口INavigationService 导航服务DockLayout Dock类型的画面布局TreeView 树状列表注意引用类型的时候ImageSource是PresentationCore程序集的博主找了好久&am…