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

[spring] spring AOP - 面向切面编程の学习

[spring] spring AOP - 面向切面编程の学习

几年前开始还在被 spring 的八股文时,AOP 就是一个比较热也比较大的点,为了面试确实背过不少,不过 AOP 实现本身做的不多,一方面也是因为 AOP 一旦配置好了基本上就不需要改什么,我那个时候也是 entry 岗,还没进阶到碰 AOP 的程度

这次正好趁着重学 spring 的机会,补一下 AOP 相关的概念

从结构来说,AOP 可以很好的解决两个问题:

  1. 代码纠缠

    指的是业务逻辑纠缠在一起

  2. 代码分散

    更多的是代码散落的到处都是

以常见的输出日志为例,在以不用第三方库为前提的条件下,controller、service 这两个常见模块都会有 try-catch 的逻辑。那也就代表:

  1. logger 的逻辑和 service/controller 紧紧地纠缠在了一起
  2. logger 的代码散落在 service/controller 的代码中

使用 aspect 就可以很好的解决这个问题,它可以

  • 将这些横切关注点的逻辑进行抽离封装

    这样可以解决了上面 代码纠缠 和 代码分散 的问题,并且让代码修改和维护变得简单不少

    另外,修改 aspect 代码并不需要接触到主要的 java 项目,因此主项目并不需要被重新编译——这需要 aspect 实现遵从规范,保持松散的耦合度;不修改公共类的返回类型;以及进行模块化打包,让 aspcet 部分代码打包在不同的 jar 中

    这样也可以清理业务逻辑,更好的运行 single responsibility principle

  • 可以用在所有地方

    这个后面会提到,它可以使用 wildcard match,所以只要 pattern 写的对,那么就可以用在任何想用的地方

  • 同样的实现(class/aspect)基于配置实现

    这也是上面提到的配置问题

它的具体业务逻辑可以包括:应用代理设计模式、日志输出、安全检查、交易、审计日志、处理异常、管理 API 等

当然,spring aop 也不是没有缺点的:

  • 如果 AOP 太多,那么会导致 aop 的逻辑难以理解和追踪

  • aop 是在 runtime 时完成的 weaving,基于动态代理完成

    因此,太多的 aop 会导致运行速度显著下降

    另一个可以完成效能提升的时间方法是,在做 pointcut 的 matching 时,只包含当前项目的代码

术语

  • 切面(Aspect)- 对关注的逻辑/业务所进行的抽离/抽象

    比较常见的就是 logging 的业务,这里的抽象指的就是对 logger 功能的抽象

    当然,它其实是包含了 具体要执行的业务逻辑 和 需要匹配的业务路径

  • 连接点(Join Point)

    即可以被 aspect 切入的点,以 spring aop 来说,最小且唯一的 Join Point 是方法(method)

  • 切点(Pointcut)

    以 sping aop 来说,因为可以使用 wildcard 进行 match,所以它上可以 match 到

    • 方法名, 如 Service._(..)

    • 包名 / 类名, 如 com.example.service..*

    • 参数类型, 如 (..)(String, ..)

    • 返回类型, 如 void*

    并且可以使用 AND/OR 进行操作关联

    可以理解成,Pointcut 的实现是寻找到对应的 join point;即对 join point 的匹配,也就是 aspect 中需要匹配的业务路径——where

  • 通知(Advice):在匹配到 join point 所要执行的增强逻辑

    也就是 aspect 中提到的 具体要执行的业务逻辑——what

  • 目标对象 (Target):代理的目标对象

    即原始的业务逻辑,也就是未被增强的部分

    依旧以 logging 为例,原本需要打 log 的方法中的业务逻辑——大多数情况下是 CRUD 操作,就是 aspect 的 target

  • 引介(introduction):一种特殊的增强

    大体找了下,意思是:

    允许你给原本没有实现某接口的类,动态添加新的方法或属性

    但是这个方法使用方法比较 edge,本篇笔记中不会提到

  • 织入(Weaving):织入是将 advice 添加到 target 的 Join Point 的过程

    spring aop 实现 weaving 是在 runtime 中通过 proxy 实现的动态插入

AOP 执行过程
匹配
增强逻辑
调用实际方法
连接点 Join Point
切点 Pointcut
通知 Advice
目标对象 Target
切面 Aspect

补充一下,目前比较主流的 AOP 框架有 spring aop 和 AspectJ,spring aop 用了一点 AspectJ 的命名风(语法),但是具体的实现是独立的

spring aop 比 AspectJ 更轻量级,相对来说 AspectJ 的功能比 spring aop 更强大

目前来说,比较常见的业务使用 spring aop 就够了

advice 类型

下面是几个常见的 advice 类型:

  • @Before

    在方法执行前调用

  • @AfterReturning

    在方法成功执行后调用

  • @AfterThrowing

    在方法失败抛出异常后调用

  • @After

    在方法执行后调用

    @AfterReturning@AfterThrowing之 之间的关系有点像 try-catch-after

  • @Around

    在代码执行前后都会调用

下面的案例代码会在项目里面运行一下做个介绍,加入 spring aop 的方法可以在 pom 文件里面添加下面这个 dependency:

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

⚠️:现在的新 spring boot 的项目,aop 都是自动开启的。但是如果跑的是老项目,导入 dependency 还是没有开启,需要手动添加 @EnableAspectJAutoProxy,让 spring 开启对 aop 的识别

@Before

代码还是比较简单的,pointcut 的部分下面会提到,这里具体就不讲了:

package com.example.aopdemo.aspect;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Component
public class MyDemoLoggingAspect {@Before("execution(public void addAccount())")public void beforeAddAccountAdvice() {System.out.println("\n=====>> Executing @Before advice on AddAccount()");}
}

main 代码中依旧使用 commandLineRunner 去执行:

package com.example.aopdemo;import com.example.aopdemo.dao.AccountDAO;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;@SpringBootApplication
public class AopdemoApplication {public static void main(String[] args) {SpringApplication.run(AopdemoApplication.class, args);}@Beanpublic CommandLineRunner commandLineRunner(AccountDAO accountDAO) {return runner -> {demoTheBeforeAdvice(accountDAO);};}private void demoTheBeforeAdvice(AccountDAO accountDAO) {accountDAO.addAccount();}}

DAOImpl 只是保证有这个方法调用,并不是真的负责执行 CRUD 操作,执行结果如下:

在这里插入图片描述

👀:这里的部分内容是在 pointcut 之后写的,所以会涉及一些暂时还没提到的语法。不过对 match 稍微理解一点的应该能猜到,@Before 里的写法做的就是方法的 matching

@Before 通过 JoinPoint 获取参数

回顾一下:

即可以被 aspect 切入的点,以 spring aop 来说,最小且唯一的 Join Point 是方法(method)

这里的 JoinPoint 就是当前 advice 中,连接到的方法

@Before("com.example.aopdemo.aspect.AopExpressions.forDaoPackageNoGetterSetter()")public void beforeAddAccountAdvice(JoinPoint joinPoint) {System.out.println("=====>> Executing @Before advice on executionforDaoPackage() && !(getter() || setter())");MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();System.out.println("Method: " + methodSignature);Object[] args = joinPoint.getArgs();for (Object arg : args) {System.out.println(arg);}}

效果如下:

在这里插入图片描述

@AfterReturning

像上面提到过的, @AfterReturning 只有在方法成功运行没有抛出异常的情况下,才会执行。因此它大概率是需要能够接触到返回结果的,因此可以用 returning 去获取返回结果

⚠️:returning 中的名字,需要与 parameter 中的名字一致

@AfterReturning(pointcut = "execution(* com.example.aopdemo.dao.AccountDAO.findAccounts(..))", returning = "result")public void afterReturningAddAccountAdvice(JoinPoint joinPoint, List<Account> result) {String method = joinPoint.getSignature().toShortString();System.out.println("=====>> Executing @AfterReturning advice on method: " + method + " with result:");System.out.println("=====>> Result is: " + result + "\n");}@AfterReturning(pointcut = "com.example.aopdemo.aspect.AopExpressions.forDaoPackageNoGetterSetter()")public void afterReturningAddAccountAdvice(JoinPoint joinPoint) {String method = joinPoint.getSignature().toShortString();System.out.println("=====>> Executing @AfterReturning advice on method: " + method);MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();System.out.println("Method: " + methodSignature);Object[] args = joinPoint.getArgs();for (Object arg : args) {System.out.println(arg);}}

运行结果如下:

在这里插入图片描述

修改返回结果

⚠️:这个操作,在大部分的实现里面是属于不太好的操作。正常需要对返回结果实现修改的操作,都应该放在 controller/service 中,而不是 advice 中

这里只是表示 aspect 中可以这么做,而不是推荐这么做

修改的代码为:

@AfterReturning(pointcut = "execution(* com.example.aopdemo.dao.AccountDAO.findAccounts(..))", returning = "result")public void afterReturningAddAccountAdvice(JoinPoint joinPoint, List<Account> result) {String method = joinPoint.getSignature().toShortString();System.out.println("=====>> Executing @AfterReturning advice on method: " + method + " with result:");System.out.println("=====>> Result is: " + result + "\n");convertAccountNamesToUpperCase(result);System.out.println("=====>> Result is: " + result + "\n");}private void convertAccountNamesToUpperCase(List<Account> result) {for (Account account : result) {account.setName(account.getName().toUpperCase());}}

效果其实已经贴在上面了

这种情况下,如果要修改 aspect,就需要对原本的 main app 也进行 recompile

@AfterThrowing

这个算是用的比较多的了,以我们现在的系统为例,任何的 exception 发生后都会触发各种各样的 email notification……

它的规则和 @AfterReturning 类似,只不过获取的不是返回结果,而是异常,代码实现如下:

    @AfterThrowing(pointcut = "execution(* com.example.aopdemo.dao.AccountDAO.findAccounts(..))", throwing = "ex")public void afterThrowingAddAccountAdvice(JoinPoint joinPoint, Throwable ex) {String method = joinPoint.getSignature().toShortString();System.out.println("=====>> Executing @AfterThrowing advice on method: " + method + " with exception:");System.out.println("=====>> Exception is: " + ex + "\n");}

一个 catch 并 swallow,另一个 catch 并且抛出的结果对比:

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

@After

@After 是获取不到返回的数据或者是抛出的异常的,实现大体如下:

    @After("execution(* com.example.aopdemo.dao.AccountDAO.findAccounts(..))")public void afterFinallyAddAccountAdvice(JoinPoint joinPoint) {String method = joinPoint.getSignature().toShortString();System.out.println("=====>> Executing @After (finally) advice on method: " + method);}

在这里插入图片描述

这里比较适合处理一些清理上下文、释放资源——手动分配的资源,如数据库这种 spring 自行管理的,还是让 spring 自己操作比较好、tracing 日志之类的操作,因为这些操作,不管业务逻辑成功还是失败,都是要执行的

@Around

@Around 在方法调用前后都会被触发,所以是最强大的 advice,因为这个特性,它可以执行很多的操作,如:

  • 全局统一处理异常——非消化异常,而是将其包装成统一的格式进行返回
  • 性能监控可以在调用前后进行计时,统计代码效率。当耗时太久时便可触发警报
  • logging 中参数对比管理
  • 用户校验
  • 熔断/限流

实现代码如下:

@Around("execution(* com.example.aopdemo.service.*.getTraffic(..))")public Object getTraffic(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("=====>> Around: Executing @Around advice on getTraffic()");long begin = System.currentTimeMillis();Object result = proceedingJoinPoint.proceed();long end = System.currentTimeMillis();long duration = end - begin;System.out.println("=====>> Around: Duration: " + duration / 1000.0 + " seconds");return result;}

效果如下:

在这里插入图片描述

@Around 处理异常

上面提到过 @Around 也可以处理异常,下面是实现方法:

    @Around("execution(* com.example.aopdemo.service.*.getTraffic*(..))")public Object getTraffic(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("=====>> Around: Executing @Around advice on getTraffic()");Object result = null;long begin = System.nanoTime();try {result = proceedingJoinPoint.proceed();} catch (Exception ex) {System.out.println("=====>> Around: Exception: " + ex);result = "Major Accident! But no worries, your private AOP helicopter is on the way!";}long end = System.nanoTime();long duration = end - begin;System.out.println("=====>> Around: Duration: " + duration + " nanoseconds.");return result;}

效果如下:

在这里插入图片描述

其实这里比较推荐的是将异常统一包装后,再用 throw ex; 进行抛出。具体的业务逻辑还是在具体的地方实现比较好。如果统一在 advice 中处理,那么很有可能在某一个时间段,就会发生有些异常被 advice 消化了,而没有正确的在业务逻辑中被处理,那对于 debug 来说,也是非常大的挑战

最后补充一下 sequence diagram:

调用者 代理对象 (AOP) 目标方法 调用方法() @Around(前置逻辑) proceed() @Before(方法执行前) 执行业务逻辑 返回结果 @AfterReturning(方法成功后) @Around(后置逻辑) @After(最终通知) 返回结果 @Before(方法执行前) 抛出异常 @AfterThrowing(捕获异常) @Around(后置逻辑) @After(最终通知) 抛出异常 alt [如果方法成功返回] [如果方法抛出异常] 调用者 代理对象 (AOP) 目标方法

pointcut

这里主要提一下 pointcut 怎么做 match

pointcut 表达式

方法名 match -> exact match

这是前面出现过的写法:

@Before("execution(public void updateAccount())")

这里的话只会 match 到 public void updateAccount() 这一个方法,而且返回类型类型必须是 void,而且是个无参方法。如果调用了其他的方法,或者是返回类型/参数不一样,那么就都不会触发这个 advice

main 部分如下:

package com.example.aopdemo;import com.example.aopdemo.dao.AccountDAO;
import com.example.aopdemo.dao.MembershipDAO;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;@SpringBootApplication
public class AopdemoApplication {public static void main(String[] args) {SpringApplication.run(AopdemoApplication.class, args);}@Beanpublic CommandLineRunner commandLineRunner(AccountDAO accountDAO, MembershipDAO membershipDAO) {return runner -> {demoTheBeforeAdvice(accountDAO, membershipDAO);};}private void demoTheBeforeAdvice(AccountDAO accountDAO, MembershipDAO membershipDAO) {accountDAO.addAccount();membershipDAO.addAccount();}}

效果截图:

在这里插入图片描述

类 match -> exact match

这里会提供一个完整的路径(fully qualified classname)去做一个完整的 match:

    @Before("execution(public void com.example.aopdemo.dao.AccountDAO.addAccount())")public void beforeAddAccountAdvice() {System.out.println("\n=====>> Executing @Before advice on AddAccount()");}

换言之,这个 advice 只会在制定类下的 AccountDAO 调用 addAccount 才会触发。方法名的 match 上面提到了

这里 main 没有修改,效果如下:

在这里插入图片描述

可以看到 MembershipDAOImpl 中的方法没有触发 advice

match 所有 add 开始的方法

这里就可以用 wildcard 了:

@Before("execution(public void add*())")public void beforeAddAccountAdvice() {System.out.println("\n=====>> Executing @Before advice on Add*()");}

效果如下:

在这里插入图片描述

⚠️:这里还是有返回类型和参数的限制

返回类型 match

这里的 advice 还是使用 void,但是 addMembership 的返回类型换成了 boolean,可以看到 addMembership 没有出发 advice:

在这里插入图片描述

如果换成了 wildcard match,那么 addMembership 也能触发了:

在这里插入图片描述

参数 match

这里主要有这么几种:

  • () 无参

  • (SomeClass) exact match 类型

    如:

    @Before("execution(* add*(com.example.adpdemo.Account))")

    这里就需要保证,传进去的参数必须是 com.example.adpdemo.Account

    效果如下:

    在这里插入图片描述

  • (*) 匹配 1 个任意格式的参数

  • (..) 匹配 0-n 个任意格式的参数

    这个写法中,第一个参数的要求依旧是 com.example.adpdemo.Account,只是对于后面的参数没有限制,因此 addMembership 依旧不会被触发:

    在这里插入图片描述

    将第一个参数 com.example.adpdemo.Account 移除后,当前 advice 对 argument 没有任何的匹配,这样所有的方法都能触发 advice 了:

    在这里插入图片描述

package 匹配

这个和之前的写法比较类似:

@Before("execution(* com.example.adpdemo.dao.*.*(..))")

需要注意的是,如果不对方法进行匹配,就需要额外再加一个 *,上面这个匹配具体的理解方法是:任意参数 com.example.adpdemo.dao.包下的方法.任意方法(..)

在这里插入图片描述

❗:如果想匹配所有的 sub package 下的方法,写法为:

@Before("execution(* com.example.adpdemo.dao..*.*(..))")

pointcut 装饰器

之前的写法都在反复的 cv execution 这一段,其实 spring aop 提供了 pointcut 装饰器,用以提供 expression 的复用性,实现如下:

package com.example.aopdemo.aspect;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect
@Component
public class MyDemoLoggingAspect {@Pointcut("execution(* com.example.aopdemo.dao.*.*(..))")private void forDaoPackage() {}@Before("forDaoPackage()")public void beforeAddAccountAdvice() {System.out.println("\n=====>> Executing @Before advice on execution(* add*(com.example.aopdemo.Account, ..))");}@Before("forDaoPackage()")public void performApiAnalytics() {System.out.println("\n=====>> Performing API analytics");}
}

最终效果和反复 cv execution 的结果是一样的:

在这里插入图片描述

组合 pointcut 表达式

即添加 and, or 和 not 的操作:

    @Pointcut("execution(* com.example.aopdemo.dao.*.get*(..))")private void getter() {}@Pointcut("execution(* com.example.aopdemo.dao.*.set*(..))")private void setter() {}@Pointcut("forDaoPackage() && !(getter() || setter())")private void forDaoPackageNoGetterSetter() {}

结果如下:

在这里插入图片描述

aspect order

在运行过程中,流程可以确定的是从 before -> after returning/throwing -> after。但是在同一个周期内,不同 advice 运行的默认顺序是无法规定的。如果想要控制同一个生命周期内,多个 advice 的运行顺序,那么就可以通过 @Order 去实现

@Order 中接受参数的值从 integer 的最小值到最大值,并不需要连续,spring aop 会优先运行数值更小的 advice。当 advice 的 order 一样时,顺序则随机

实现大体如下:

在这里插入图片描述

规模太小了,所以没办法触发多个随机运行的结果

其他项目的运行结果

从之前 thymeleaf 的项目里重新用 Logger 实现了一下 aspect,一个比较简单的实现如下:

package com.demo.springboot.thymeleafdemo.aspect;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.logging.Logger;@Component
@Aspect
public class DemoLoggingAspect {private Logger logger = Logger.getLogger(getClass().getName());@Pointcut("execution(* com.demo.springboot.thymeleafdemo.controller.*.*(..))")private void controllerMethods() {}@Pointcut("execution(* com.demo.springboot.thymeleafdemo.service.*.*(..))")private void serviceMethods() {}@Pointcut("execution(* com.demo.springboot.thymeleafdemo.dao.*.*(..))")private void daoMethods() {}@Pointcut("controllerMethods() || daoMethods() || serviceMethods()")private void appFlow() {}@Before("appFlow()")public void logBefore(JoinPoint joinPoint) {logger.info("========>> @Before " + joinPoint.getSignature().toShortString());logger.info("========>> Arguments: " + Arrays.toString(joinPoint.getArgs()));}@AfterReturning(pointcut = "appFlow()", returning = "result")public void logAfter(JoinPoint joinPoint, Object result) {logger.info("========>> @AfterReturning " + joinPoint.getSignature().toShortString());logger.info("========>> Result: " + result);}
}

效果如下:

在这里插入图片描述

总结

  • ✅ 推荐使用 AOP 处理:日志、权限校验、上下文清理、异常封装、性能监控

  • ✅ 配合 MDC + ThreadLocal 实现 traceId 链路追踪更实用

    这里没提到,有空再研究下

  • ❌ 不推荐用 AOP 处理:复杂业务流程控制、数据库事务强控制、请求参数转换

    这部分的内容大体都会通过框架管理,使用 aspect 可能会 break 框架的管理

  • 🚫 Advice 中避免直接修改返回值、吞掉异常

相关文章:

[spring] spring AOP - 面向切面编程の学习

[spring] spring AOP - 面向切面编程の学习 几年前开始还在被 spring 的八股文时&#xff0c;AOP 就是一个比较热也比较大的点&#xff0c;为了面试确实背过不少&#xff0c;不过 AOP 实现本身做的不多&#xff0c;一方面也是因为 AOP 一旦配置好了基本上就不需要改什么&#…...

JavaScript 中的 Reflect 详解

Reflect 是 ES6引入的一个内置对象&#xff0c;它提供了一系列静态方法来操作对象&#xff0c;这些方法与 Proxy 处理器方法一一对应。Reflect 的设计目的是为了更优雅地操作对象&#xff0c;并统一某些操作的行为。 1. Reflect 的基本特点 1. 不是构造函数&#xff1a;不能使…...

【操作系统】linux常用命令

UP作为一个Linux系统练习两年半的个人练习生&#xff0c;今天分门别类地给大家整理一下常用的Linux命令&#xff0c;祝大家在Linux练习之路一帆风顺。 文件和目录操作 文件查看与编辑 文件查找 文件权限与所有权 进程管理 系统信息与监控 网络管理与诊断...

002 vue组件化编程

文章目录 一般方式全局组件局部组件 组件&#xff08;Component&#xff09;是Vue.js最强大的功能之一 组件也是一个Vue实例&#xff0c;也包括&#xff1a;data、methods、生命周期函数等 组件渲染需要html模板&#xff0c;所以增加了template属性&#xff0c;值就是HTML模板 …...

常见的 JavaScript 框架和库

在现代前端开发中&#xff0c;JavaScript框架和库成为了构建高效、可维护应用程序的关键工具。本文将介绍四个常见的JavaScript框架和库&#xff1a;React、Vue.js、Angular 和 Node.js&#xff0c;并探讨它们的特点、使用场景及适用场合。 1. React — 构建用户界面的JavaScri…...

005_循环结构

循环结构 循环结构的作用和应用场景for循环while循环for和while的使用规范do - while 死循环循环嵌套break、continueRandom生成随机数 循环结构的作用和应用场景 减少代码的重复编写、灵活的控制程序的执行 for循环 for (1初始化语句; 2循环条件; 3迭代语句){4循环体语句(重…...

1110+款专业网站应用程序UI界面设计矢量图标figma格式素材 Icon System | 1,100+ Icons Easily Customize

1110款专业网站应用程序UI界面设计矢量图标figma格式素材 Icon System | 1,100 Icons Easily Customize 产品特点 — 24 x 24 px 网格大小 — 2px 线条描边 — 所有形状都是基于矢量的 — 平滑和圆角 — 易于更改颜色 类别 &#x1f6a8; 警报和反馈 ⬆️ 箭头 &…...

leetcode 368. 最大整除子集 中等

给你一个由 无重复 正整数组成的集合 nums &#xff0c;请你找出并返回其中最大的整除子集 answer &#xff0c;子集中每一元素对 (answer[i], answer[j]) 都应当满足&#xff1a; answer[i] % answer[j] 0 &#xff0c;或answer[j] % answer[i] 0 如果存在多个有效解子集&…...

项目总结之常问的一些问题

1.项目功能介绍&#xff0c;重难点 重难点&#xff1a; mock工具使用&#xff08;涉及到的三方接口过多&#xff0c;由于网络等原因无法调通&#xff0c;所以测试的时候&#xff0c;采用mock工具来模拟返回接口真正调用后响应数据&#xff09; 2.项目负责哪部分&#xff1f;…...

51单片机实现精确定时

一、51单片机代码 #include <reg52.h>sbit LED P3^3;extern bit b10Ms; extern bit b100Ms; extern bit b1S; extern bit b10S;void Timer0_Init() {TMOD | 0x01; // 设置定时器0为模式1TH0 (65536 - 1000) / 256; // 高8位赋初值TL0 (65536 - 1000) % 256; // 低8位…...

记一次不太顺利的Docker重装

#记录工作 一、前言 默认情况下&#xff0c;Windows系统上Docker Desktop 安装在 &#xff1a;C:\Program Files\Docker\Docker&#xff1b; 目前正常下载能下载到最新版本是到v4.39.0&#xff0c;实际已经能找到v4.40.0版本来进行修复安装&#xff1b; 建议尽量不要改变Doc…...

【计网】TCP 协议详解 与 常见面试题

三次握手、四次挥手的常见面试题 不用死记&#xff0c;只需要清楚三次握手&#xff0c;四次挥手的流程&#xff0c;回答的时候心里要记住&#xff0c;假设网络是不可靠的 问题(1)&#xff1a;为什么关闭连接时需要四次挥手&#xff0c;而建立连接却只要三次握手&#xff1f; 关…...

Docker介绍

Docker介绍 Docker 本身并不是容器&#xff0c;而是一个使用容器的工具。容器是 Linux 内核提供的技术&#xff0c;Docker 只是将这种技术的使用简便化了。Docker 的主要目标是 “Build,Ship and Run Any APP,Anywhere”&#xff08;“一次封装&#xff0c;到处运行”&#xf…...

大模型推理--Qwen2.5-Omni在A100上的初体验

过去的一周Qwen2.5-Omni产生了很高的热度&#xff0c;吸引了很多人的目光。它的多模态确实很吸引人&#xff0c;放出来的demo体验还算尚可&#xff08;语音对话的延迟还是太大&#xff09;&#xff0c;所以就在A100 PCIe上实地部署了一下&#xff0c;初步对其速度进行了测试&am…...

二分查找例题

本篇基于b站灵茶山艾府。 34. 在排序数组中查找元素的第一个和最后一个位置 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你…...

新增一种线性回归的增量学习框架,已更新31个模型!Matlab回归预测大合集又更新啦!

目录 效果图基本介绍程序设计参考资料 效果图 基本介绍 一种线性回归的增量学习框架&#xff0c;程序研究的主要内容是线性回归模型的增量学习实现及其在房价预测中的应用&#xff0c;旨在通过分块处理数据逐步更新模型&#xff0c;以适应动态数据环境并减少计算资源消耗。 详…...

P1025 [NOIP 2001 提高组] 数的划分(DFS)

题目描述 将整数 n 分成 k 份&#xff0c;且每份不能为空&#xff0c;任意两个方案不相同&#xff08;不考虑顺序&#xff09;。 例如&#xff1a;n7&#xff0c;k3&#xff0c;下面三种分法被认为是相同的。 1,1,5; 1,5,1; 5,1,1. 问有多少种不同的分法。 输入格式 n,k …...

SQL Server存储过程和触发器的使用

存储过程 &#xff08;1&#xff09;创建存储过程&#xff0c;使用Employees表中的员工人数来初始化一个局部变量&#xff0c;并调用这个存储过程。 1. Create PROCEDURE test number1 int output --输出参数&#xff0c;可以从程序中返回信息 2. As 3. begin 4. D…...

Elastic 的 OpenTelemetry 分发版(EDOT)现已正式发布:开源、可用于生产环境的 OTel

作者&#xff1a;来自 Elastic Miguel Luna 及 Bahubali Shetti Elastic 自豪地宣布正式发布 Elastic OpenTelemetry 分发版&#xff08;Elastic Distributions of OpenTelemetry - EDOT&#xff09;&#xff0c;其中包含 Elastic 自定义版本的 OpenTelemetry Collector 以及多…...

springMVC-Json交互处理

什么是JSON JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式&#xff0c;目前使用特别广泛。 采用完全独立于编程语言的文本格式来存储和表示数据。 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写&#xff0c;同时也…...

MySQL中的索引

explain关键字&#xff0c; MySQL索引特性 索引的概念 MySQL 索引是一种用于提高数据库查询效率的数据结构 数据库表中存储的数据都是以记录为单位的&#xff0c;如果在查询数据时直接一条条遍历表中的数据记录&#xff0c;那么查询的时间复杂度将会是 O ( N )。索引的价值在…...

AI小白:JavaPython开发环境双轨制搭建指南

文章目录 1 Python深度学习环境配置1.1 Anaconda生态体系建设1.2 JupyterLab高效工作流魔法命令与可视化调试扩展插件配置指南 2 Java深度学习方案&#xff1a;DL4J实战2.1 企业级部署架构设计2.2 集成传统Java系统Spring Boot微服务封装模型性能优化技巧 1 Python深度学习环境…...

《比特城的机密邮件:加密、签名与防篡改的守护之战》

点击下面图片带您领略全新的嵌入式学习路线 &#x1f525;爆款热榜 88万阅读 1.6万收藏 第一章&#xff1a;风暴前的密令 比特城的议会大厅内&#xff0c;首席长老艾德文握着一卷足有半人高的羊皮纸&#xff0c;眉头紧锁。纸上是即将颁布的《新纪元法典》——这份文件不仅内…...

Redis之布隆过滤器

面试场景切入 针对于电话号码问题的痛点 布隆过滤器是什么&#xff1f; 由一个初值都为0的bit数组和多个哈希函数构成&#xff0c;用来快速判断集合中是否存在某个元素。 设计思想 本质就是判断具体数据是否存在于一个大的集合中。布隆过滤器是一种类似Set的数据结构&#…...

这是一份简单优雅的Prompt Engineering教程

Prompt Engineering&#xff08;提示工程&#xff09;是通过精心设计输入文本&#xff08;prompt&#xff09;来引导大型语言模型&#xff08;LLM&#xff09;生成更准确、相关且符合预期的输出的技术。其核心在于通过调整提问的措辞、结构、上下文和附加信息&#xff0c;优化模…...

Java基础 4.6

1.成员方法练习 //编写类A&#xff1a;判断一个数是奇数还是偶数&#xff0c;返回boolean //根据行、列、字符打印对应行数和列数的字符&#xff0c;比如&#xff1a;行4 列4 字符# 则打印相应的效果 public class MethodExercise01 {public static void main(String[] args) …...

DApp实战篇:前端技术栈一览

前言 在前面一系列内容中&#xff0c;我们由浅入深地了解了DApp的组成&#xff0c;从本小节开始我将带领大家如何完成一个完整的DApp。 本小节则先从前端开始。 前端技术栈 在前端开发者速入&#xff1a;DApp中的前端要干些什么&#xff1f;文中我说过&#xff0c;即便是在…...

C++中如何比较两个字符串的大小--compare()函数实现

一、现在有一个问题描述&#xff1a;有两个字符串&#xff0c;要按照字典顺序比较它们的大小&#xff08;注意所有的小写字母都大于所有的大写字母 &#xff09;。 二、代码 #include <bits/stdc.h> using namespace std;int main() {string str1 "apple";…...

c++中的auto关键字

在 C 中&#xff0c;auto 是一个类型推断关键字&#xff08;C11 引入&#xff09;&#xff0c;允许编译器根据变量的初始化表达式自动推导其类型。它极大地简化了代码编写&#xff0c;尤其在涉及复杂类型或模板的场景中。以下是 auto 的详细说明&#xff1a; 1. 基本用法 1.1 …...

zk源码—1.数据节点与Watcher机制及权限二

大纲 1.ZooKeeper的数据模型、节点类型与应用 (1)数据模型之树形结构 (2)节点类型与特性(持久 临时 顺序 ) (3)节点的状态结构(各种zxid 各种version) (4)节点的版本(version cversion aversion) (5)使用ZooKeeper实现锁(悲观锁 乐观锁) 2.发布订阅模式&#xff1…...

交换机和集线器的区别

集线器&#xff08;Hub&#xff09;—— 大喇叭广播站​​ ​​工作原理​​&#xff1a; 集线器像村里的“大喇叭”&#xff0c;收到任何消息都会​​广播给所有人​​。 比如A对B说“你好”&#xff0c;全村人&#xff08;C、D、E&#xff09;都能听到&#xff0c;但只有B会回…...

微服务系统记录

记录下曾经工作涉及到微服务的相关知识。 1. 架构设计与服务划分 关键内容 领域驱动设计&#xff08;DDD&#xff09;&#xff1a; 利用领域模型和限界上下文&#xff08;Bounded Context&#xff09;拆分业务&#xff0c;明确服务边界。通过事件风暴&#xff08;Event Storm…...

同花顺客户端公司财报抓取分析

目标客户端下载地址:https://ft.51ifind.com/index.php?c=index&a=download PC版本 主要难点在登陆,获取token中的 jgbsessid (每次重新登录这个字段都会立即失效,且有效期应该是15天的) 抓取jgbsessid 主要通过安装mitmproxy 使用 mitmdump + 下边的脚本实现监听接口…...

二叉树与红黑树核心知识点及面试重点

二叉树与红黑树核心知识点及面试重点 一、二叉树 (Binary Tree) 1. 基础概念 定义&#xff1a;每个节点最多有两个子节点&#xff08;左子节点和右子节点&#xff09; 术语&#xff1a; 根节点&#xff1a;最顶层的节点 叶子节点&#xff1a;没有子节点的节点 深度&#xf…...

Rocket-JWT鉴权

目录 一、概述 二、相关依赖 三、环境准备 3.1 创建项目 3.2 读取私钥信息 3.3 token数据负载 3.4 生成token 四、Web鉴权 4.1 验证载体 4.2 接收请求 五、总结 Welcome to Code Blocks blog 本篇文章主要介绍了 [Rocket-JWT鉴权] ❤博主广交技术好友&#xff0c;喜…...

2025 年网络安全终极指南

我们生活在一个科技已成为日常生活不可分割的一部分的时代。对数字世界的依赖性日益增强的也带来了更大的网络风险。 网络安全并不是IT专家的专属特权&#xff0c;而是所有用户的共同责任。通过简单的行动&#xff0c;我们可以保护我们的数据、隐私和财务&#xff0c;降低成为…...

横扫SQL面试——PV、UV问题

&#x1f4ca; 横扫SQL面试&#xff1a;UV/PV问题 &#x1f31f; 什么是UV/PV&#xff1f; 在数据领域&#xff0c;UV&#xff08;Unique Visitor&#xff0c;独立访客&#xff09; 和 PV&#xff08;Page View&#xff0c;页面访问量&#xff09; 是最基础也最重要的指标&…...

ctf-show-杂项签到题

下载文件&#xff0c;解压需要密码&#xff0c;用010打开没看出什么 然后用Advanced Archive Password Recovery暴力破解&#xff0c;发现没用 怀疑是伪解密&#xff0c;解压出来发现加密受损用随波逐流修复加密文件 打开修复的加密文件直接得flag flag&#xff1a;flag{79d…...

对解释器模式的理解

对解释器模式的理解 一、场景1、题目【[来源](https://kamacoder.com/problempage.php?pid1096)】1.1 题目描述1.2 输入描述1.3 输出描述1.4 输入示例1.5 输出示例 二、不采用解释器模式1、代码2、“缺点” 三、采用解释器模式1、代码2、“优点” 四、思考1、解释器模式的意义…...

高频面试题(含笔试高频算法整理)基本总结回顾64

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…...

python入门之从安装python及vscode开始

本篇将解决三个问题&#xff1a; 1. 如何下载及安装官方python&#xff1f; 2. 如何下载及安装vscode&#xff1f; 3. 如何配置vscode的python环境&#xff1f; 一、python下载及安装 1.搜索python&#xff0c;找到官网并打开 2.找到download&#xff0c;按需选择版本下载 …...

OpenGL学习笔记(模型材质、光照贴图)

目录 光照与材质光照贴图漫反射贴图采样镜面光贴图 GitHub主页&#xff1a;https://github.com/sdpyy OpenGL学习仓库:https://github.com/sdpyy1/CppLearn/tree/main/OpenGLtree/main/OpenGL):https://github.com/sdpyy1/CppLearn/tree/main/OpenGL 光照与材质 在现实世界里&…...

视觉_transform

visual_transform 图像分块 (Patch Embedding) 假设输入图像为 x ∈ R ∗ H ∗ ∗ W ∗ ∗ C ∗ x∈R^{*H**W**C*} x∈R∗H∗∗W∗∗C∗ C 是图像的通道数&#xff08;例如&#xff0c;RGB图像的 C3&#xff09; 将图像分割成N个大小为P*CP的patch&#xff0c;每个patch的大…...

Redis的安装及通用命令

二. Redis 的安装及通用命令 1. Ubuntu 安装 Redis (1) 切换到 root 用户: su root(2) 搜索 Redis 软件包 apt search redis(3) 安装 Redis apt install redis(4) 查看 Redis netstat -anp | grep redis(5) 切换到 Redis 目录下 cd /etc/redis/(6) 修改 Redis 配置文件:…...

Python 实现的运筹优化系统代码详解(0-1规划背包问题)

一、引言 在数学建模与实际决策场景的交织领域中&#xff0c;诸多复杂问题亟待高效且精准的解决方案。0-1 规划作为一种特殊且极为重要的优化方法&#xff0c;宛如一把万能钥匙&#xff0c;能够巧妙开启众多棘手问题的解决之门。它专注于处理决策变量仅能取 0 或 1 这两种极端状…...

护网蓝初面试题

《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…...

音视频学习(三十二):VP8和VP9

VP8 简介 全称&#xff1a;Video Processing 8发布者&#xff1a;原 On2 Technologies&#xff08;2010 被 Google 收购&#xff09;定位&#xff1a;开源视频压缩标准&#xff0c;主要竞争对手是 H.264应用&#xff1a; WebRTC 视频通信HTML5 <video> 标签&#xff08…...

美国mlb与韩国mlb的关系·棒球9号位

MLB&#xff08;Major League Baseball&#xff0c;美国职业棒球大联盟&#xff09;作为全球最高水平的职业棒球联赛&#xff0c;与韩国市场流行的“MLB”时尚品牌之间存在着授权合作关系&#xff0c;但两者在业务范畴和品牌定位上存在显著差异。 一、品牌授权背景&#xff1a;…...

免费在线PUA测试工具:识别情感操控,守护情感健康

免费在线PUA测试工具&#xff1a;识别情感操控&#xff0c;守护情感健康 你是否曾经在感情中感到困惑、不安&#xff0c;甚至怀疑自己&#xff1f;今天为大家推荐一个专业的PUA测试工具&#xff0c;帮助你识别是否正在经历情感操控。 测试工具链接&#xff1a;PUA测试工具 什么…...

nginx中的try_files指令

try_files 是 Nginx 中一个非常有用的指令&#xff0c;用于按顺序检查文件是否存在&#xff0c;并返回第一个找到的文件。如果所有指定的文件都不存在&#xff0c;则执行回退逻辑&#xff0c;如重定向到一个指定的 URI 或返回一个错误代码。 作用 文件查找&#xff1a;按顺序检…...