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

【Spring】AOP底层原理(动态代理)-》 AOP概念及术语 -》 AOP实现

在这里插入图片描述

个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~
个人主页:.29.的博客
学习社区:进去逛一逛~

在这里插入图片描述

AOP - 面向切面编程

  • 一、简述AOP
  • 二、AOP底层原理
  • 三、实现动态代理(案例)
  • 四、AOP术语
  • 五、AOP的注解方式实现



一、简述AOP


AOP —— 面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP的作用:

  • 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。

  • 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。



二、AOP底层原理


AOP面向切面编程,在底层使用动态代理实现,其中分为两种情况:

  • 有接口时,使用JDK动态代理
  • 无接口时,使用CGLIB动态代理

JDK动态代理:创建接口实现类代理对象,增强类的方法;
CGLIB动态代理:创建子类的代理对象,增强类的方法;



三、实现动态代理(案例)


使用的相关Maven依赖:
<dependencies><!--spring context依赖--><!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.6.RELEASE</version></dependency><!--spring aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.2.6.RELEASE</version></dependency><!--spring aspects依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.6.RELEASE</version></dependency><!--junit5测试--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.1</version></dependency></dependencies>

① 声明计算器接口Calculator,包含加减乘除的抽象方法

/*** @author .29.* @create 2023-02-04 15:01*/
public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}

② 创建接口实现类,实现加减乘除等方法

import org.springframework.stereotype.Component;/*** @author .29.* @create 2023-02-04 15:02*///简单功能的实现类
@Component
public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("方法内部 result = " + result);return result;}
}

③ 编写被代理对象的工厂类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;/*** @author .29.* @create 2023-02-04 22:05*/
public class ProxyFactory {//目标对象private Object target;public ProxyFactory(Object target){this.target = target;}//返回代理对象public Object getProxy(){//动态代理返回对象//调用Proxy类中的newProxyInstance(),获取动态代理对象/** newProxyInstance()中需要传入三个参数* ① ClassLoader:加载动态生成代理类的 类加载器* ② Class[] interfaces: 目标对象实现的所有接口* ③ InvocationHandler: 设置代理对象实现目标对象方法的过程* */ClassLoader classLoader = target.getClass().getClassLoader();Class<?>[] interfaces = target.getClass().getInterfaces();InvocationHandler invocationHandler = new InvocationHandler(){//参数一:代理对象//参数二:需要重写目标对象的方法//参数三:method方法(参数二)中的参数@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//方法调用前输出的日志System.out.println("[动态代理][日志]"+method.getName()+",参数:"+ Arrays.toString(args));//调用目标方法Object result = method.invoke(target, args);//方法调用后输出的日志System.out.println("[动态代理][日志]"+method.getName()+",结果:"+ result);return result;}};//调用Proxy类中的newProxyInstance(),获取动态代理对象return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);}
}

④ 测试

/*** @author .29.* @create 2023-02-04 22:24*/
public class TestCalculator {public static void main(String[] args) {//创建代理对象ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());//通过代理对象获取目标对象Calculator proxy = (Calculator) proxyFactory.getProxy();//调用目标对象方法proxy.add(1,1);System.out.println();proxy.div(1,1);System.out.println();proxy.mul(1,1);System.out.println();proxy.sub(1,1);System.out.println();}
}

在这里插入图片描述



四、AOP术语


1.横切关注点
分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。

从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。

这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。

2.通知
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

  • 前置通知:使用@Before注解标识,在被代理的目标方法执行
  • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝
  • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命
  • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论
  • 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

各种通知的执行顺序:

  • Spring版本5.3.x以前:
    • 前置通知
    • 目标操作
    • 后置通知
    • 返回通知或异常通知
  • Spring版本5.3.x以后:
    • 前置通知
    • 目标操作
    • 返回通知或异常通知
    • 后置通知

3.切面
即:封装通知方法的类。

4.目标
即:被代理的目标对象。

5.代理
向目标对象应用通知之后创建的代理对象。

6.连接点
这也是一个纯逻辑概念,不是语法定义的。

把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方

7.切入点
定位连接点的方式。

每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。

如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。

Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法

切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。



五、AOP的注解方式实现


须知:
切入点表达式语法:
execution(权限修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(方法参数))

在这里插入图片描述


1.导入Maven依赖

<dependencies><!--spring context依赖--><!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.6.RELEASE</version></dependency><!--spring aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.2.6.RELEASE</version></dependency><!--spring aspects依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.6.RELEASE</version></dependency><!--junit5测试--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.1</version></dependency></dependencies>

2.准备被代理的 接口+实现类

/*** @author .29.* @create 2023-02-04 15:01*/public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}
import org.springframework.stereotype.Component;/*** @author .29.* @create 2023-02-04 15:02*///简单功能的实现类
@Component
public class CalculatorImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("方法内部 result = " + result);return result;}
}

※ 3.创建并配置切面类

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;/*** @author .29.* @create 2023-02-04 22:47*/
//切面类
//@Aspect代表这是一个切面类
//@Component代表将此类交给Spring处理,能够放入IOC容器中去
@Aspect
@Component
public class LogAspect {//设置切入点 和 通知类型//切入点表达式:execution(权限修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(方法参数))//重用切入点表达式:使用@Pointcut注解,设置需要重复使用的切入点表达式@Pointcut(value = "execution(public int com.haojin.spring.aop.annoaop.CalculatorImpl.*(..))")public void pointCut(){}//之后,在使用通知时,将切入点表达式用 方法名 替换,即pointCut()(同一个切面)//若在不同的切面,需要加上重用切入点表达式方法的全类名:com.haojin.spring.aop.annoaop.LogAspect.pointCut()(不同的切面)//通知类型:// @Before("切入点表达式配置切入点") 前置@Before(value = "pointCut()")   //重用切入点表达式,使用方法名替换public void beforeMethod(JoinPoint joinPoint){String name = joinPoint.getSignature().getName();//获取增强方法的名字Object[] args = joinPoint.getArgs();             //获取增强方法的参数数组System.out.println("Logger-->前置通知,增强方法为:"+name+",参数:"+ Arrays.toString(args));}// @AfterReturning() 返回//返回通知 可以获取到返回值,在切入表达式中增加返回值属性:returning = ""@AfterReturning(value = "pointCut()",returning = "result")//增强方法中需要添加 返回值参数result,参数名与切入点表达式保持一致(result)public void afterReturningMethod(JoinPoint joinPoint,Object result){//可以存在参数JoinPoint,以此来获取信息String name = joinPoint.getSignature().getName();//获取增强方法的名字System.out.println("Logger-->返回通知,增强方法为:"+name+",返回结果:"+result);}// @AfterThrowing() 异常//目标方法出现异常时,此通知会执行,在切入表达式中增加属性:@AfterThrowing(value = "pointCut()",throwing = "exception")//增加 Throwable类型参数,参数名必须与切入点表达式保持一致(exception)public void aAfterThrowingMethod(JoinPoint joinPoint,Throwable exception){//可以存在参数JoinPoint,以此来获取信息String name = joinPoint.getSignature().getName();//获取增强方法的名字System.out.println("Logger-->异常通知,增强方法为:"+name+"异常信息:"+exception);}// @After() 后置@After(value = "execution(* com.haojin.spring.aop.annoaop.CalculatorImpl.*(..))")public void afterMethod(JoinPoint joinPoint){//可以存在参数JoinPoint,以此来获取信息String name = joinPoint.getSignature().getName();//获取增强方法的名字System.out.println("Logger-->后置通知,增强方法为:"+name);}// @Around() 环绕通知,可以通过try-catch-finally,使得增强方法在所有阶段执行(增强方法可设置返回值)@Around("execution(public int com.haojin.spring.aop.annoaop.CalculatorImpl.*(..))")//可设置 ProceedingJoinPoint属性参数,以此调用增强方法(JoinPoint的子接口,JoinPoint没有调用目标方法的功能)public Object aroundMethod(ProceedingJoinPoint joinPoint){String name = joinPoint.getSignature().getName();//通过ProceedingJoinPoint参数获取增强方法名Object[] args = joinPoint.getArgs();             //获取增强方法参数数组String argStr = Arrays.toString(args);           //参数数组转字符串Object result = null;try{System.out.println("环绕通知 == 增强方法执行前执行");//通过ProceedingJoinPoint类型参数调用增强方法result = joinPoint.proceed();System.out.println("环绕通知 = 增强方法返回值之后执行");}catch(Throwable throwable){//捕捉Throwable类型异常throwable.printStackTrace();System.out.println("环绕通知 = 增强方法异常时执行");}finally {System.out.println("环绕方法 = 增强方法执行完毕执行");}return result;}

切面优先级:

切面的优先级控制切面的 内外嵌套 顺序
外层切面:优先级高
内层切面:优先级低


可使用@Order()注解设置优先级
@Order(较小的数) 优先级较高
@Order(较大的数) 优先级较低


4.配置Spring配置文件(这里命名为 bean.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--基于注解的AOP的实现:1、将目标对象和切面交给IOC容器管理(注解+扫描)2、开启AspectJ的自动代理,为目标对象自动生成代理3、将切面类通过注解@Aspect标识--><context:component-scan base-package="com.haojin.spring.aop.annoaop"></context:component-scan><!--开启AspectJ的自动代理--><aop:aspectj-autoproxy /></beans>

5.测试

import com.haojin.spring.aop.annoaop.Calculator;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author .29.* @create 2023-02-06 9:34*/
public class TestAop {//以实现类中的加法功能为例@Testpublic void testAdd(){ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");Calculator bean = context.getBean(Calculator.class);bean.add(3,13);}
}

在这里插入图片描述

相关文章:

【Spring】AOP底层原理(动态代理)-》 AOP概念及术语 -》 AOP实现

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ AOP - 面向切面编程一、简述AOP二、AOP底层原理…...

Java8 新特性 之 lambda 表达 和 函数式接口

—— lambda 表达式 概念 lambda 表达式是一个匿名函数&#xff0c;可以把 lambda 表达式理解为是一段可以传递的代码。更简洁、更灵活&#xff0c;使 Java 的语言表达能力得到了提升lambda 表达式是作为接口的实现类的对象&#xff08;万事万物皆对象&#xff09; 使用语法…...

Netty服务端和客户端开发实例

一、Netty服务端开发在开始使用 Netty 开发 TimeServer 之前&#xff0c;先回顾一下使用 NIO 进行服务端开发的步骤。(1)创建ServerSocketChannel&#xff0c;配置它为非阻塞模式;(2)绑定监听&#xff0c;配置TCP 参数&#xff0c;例如 backlog 大小;(3)创建一个独立的I/O线程&…...

linux基本指令和权限

目录 一.shell命令以及运行原理 二.Linux常用指令 1. ls 指令 2. pwd命令 3.cd指令 4. touch指令 5.mkdir指令&#xff08;重要&#xff09; 6.rmdir指令 && rm 指令&#xff08;重要&#xff09; 7.man指令&#xff08;重要&#xff09; 8.cp指令&#xff08;重要&…...

滚蛋吧,正则表达式!

大家好&#xff0c;我是良许。 不知道大家有没有被正则表达式支配过的恐惧&#xff1f;看着一行火星文一样的表达式&#xff0c;虽然每一个字符都认识&#xff0c;但放在一起直接就让人蒙圈了~ 你是不是也有这样的操作&#xff0c;比如你需要使用「电子邮箱正则表达式」&…...

序列号和反序列化--java--Serializable接口--json序列化普通使用

序列化和反序列化序列化和反序列化作用为什么需要用途Serializable使用serialVersionUID不设置的后果什么时候修改Externalizable序列化的顺序json序列化序列化和反序列化 序列化&#xff1a;把对象转换为字节序列的过程称为对象的序列化。 反序列化:把字节序列恢复为对象的过…...

Java异步任务编排

多线程创建的五种方式&#xff1a; 继承Thread类实现runnable接口。实现Callable接口 FutureTask(可以拿到返回结果&#xff0c;阻塞式等待。)线程池创建。 ExcutorService service Excutors.newFixedThreadPool(10); service.excute(new Runnable01());另外一种创建线程池…...

Hive与HBase的区别及应用场景

当数据量达到一定量级的时候&#xff0c;存储和统计计算查询都会遇到问题&#xff0c;今天了解一下Hive和Hbase的区别和应用场景。 一、定义 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供简单的sql查询功能&am…...

C++之单例模式

目录 1. 请设计一个类&#xff0c;只能在堆上创建对象 2. 请设计一个类&#xff0c;只能在栈上创建对象 3.请设计一个类&#xff0c;不能被拷贝 C98 C11 4. 请设计一个类&#xff0c;不能被继承 C98 C11 5. 请设计一个类&#xff0c;只能创建一个对象(单例模式) 设计…...

Redis十大类型——Set与Zset常见操作

Redis十大类型——Set与Zset常见操作Set命令操作简列基本操作展示删除移动剪切集合运算Zset基本操作简列添加展示反转按分数取值获取分数值删除分数操作下标操作如果我们对Java有所了解&#xff0c;相信大家很容易就明白Set&#xff0c;在Redis中也一样&#xff0c;Set的value值…...

车载雷达实战之Firmware内存优化

内存&#xff08;Memory&#xff09;是计算机中最重要的部件之一&#xff0c;计算机运时的程序以及数据都依赖它进行存储。内存主要分为随机存储器&#xff08;RAM&#xff09;,只读存储器&#xff08;ROM&#xff09;以及高速缓存&#xff08;Cache&#xff09;。仅仅雷达的原…...

【剑指Offer】JZ14--剪绳子

剪绳子详解1.问题描述2.解题思路3.具体实现1.问题描述 2.解题思路 首先想到的思路&#xff1a;因为是求乘积的最大值&#xff0c;所以如果截取剩下的是1&#xff0c;那还是它本身就没有意义。从此出发&#xff0c;考虑绳子长度是2、3、4、5…通过穷举法来找规律。 值–》拆分–…...

raspberry pi播放音视频

文章目录目的QMediaPlayerGStreamerwhat is GStreamer体系框架优势omxplayerwhat is omxplayercommand Linekey bindings运行过程中错误ALSA目的 实现在树莓派下外接扬声器&#xff0c; 播放某段音频&#xff0c; 进行回音测试。 QMediaPlayer 首先我的安装是5.11版本。 优先…...

【电子学会】2022年12月图形化二级 -- 老鹰捉小鸡

老鹰捉小鸡 小鸡正在农场上玩耍&#xff0c;突然从远处飞来一只老鹰&#xff0c;小鸡要快速回到鸡舍中&#xff0c;躲避老鹰的抓捕。 1. 准备工作 &#xff08;1&#xff09;删除默认白色背景&#xff0c;添加背景Farm&#xff1b; &#xff08;2&#xff09;删除默认角色小…...

C++的双端队列

双端队列介绍1.双端队列知识需知2.大试牛刀1.双端队列知识需知 由于队列是一种先进先出&#xff08;FIFO&#xff09;的数据结构&#xff0c;因此无法直接从队列的底部删除元素。如果希望从队列的底部删除元素&#xff0c;可以考虑使用双端队列&#xff08;deque&#xff09;。…...

【独家】华为OD机试 - 拼接 URL(C 语言解题)

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...

为什么使用Junit单元测试?Junit的详解

Hi I’m Shendi 为什么使用Junit单元测试&#xff1f;Junit的详解 Junit简介 Junit是一个Java语言的单元测试框架。 单元测试是一个对单一实体&#xff08;类或方法&#xff09;的测试 JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架&#xff08;regression test…...

怎么学好嵌入式Linux系统和驱动

嵌入式专业是一门实践性非常强的学科&#xff0c;只有多动手&#xff0c;多实践&#xff0c;多编程&#xff0c;多调试&#xff0c;多看书&#xff0c;多思考才能真正掌握好嵌入式开发技术。 现在很多同学也意识到了学校培养模式和社会需求脱节问题&#xff0c;有一部分同学也先…...

Spring Aware总结

概述 Spring中Aware到底是什么意思&#xff1f; 我们在看Spring源码的时候&#xff0c;经常可以看到xxxAwarexxx的身影&#xff0c;通常我会很疑惑&#xff0c;Aware到底是什么意思呢&#xff1f; 比如图片中这些包含Aware关键字的类或者接口。 我对下面3个类或接口进行了解…...

【RocketMQ】源码详解:Broker端消息刷盘流程

消息刷盘 同步入口&#xff1a;org.apache.rocketmq.store.CommitLog.GroupCommitService 异步入口&#xff1a;org.apache.rocketmq.store.CommitLog.FlushRealTimeService 刷盘有同步和异步两种&#xff0c;在实例化Commitlog的时候&#xff0c;会根据配置创建不同的服务 p…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

srs linux

下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...