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

十二、面向切面编程AOP


IoC使软件组件松耦合。AOP让你能够捕捉系统中经常使用的功能,把它转化成组件。

AOP(Aspect Oriented Programming):面向切面编程,面向方面编程。(AOP是一种编程技术)
AOP是对OOP的补充延伸。

AOP底层使用的就是动态代理来实现的。

Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。

1 AOP介绍

一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务

这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。

如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:

     第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。

     第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。
使用AOP可以很轻松的解决以上问题。
AOP的思想:

在这里插入图片描述

用一句话总结AOP:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。
AOP的优点:
● 第一:代码复用性增强。
● 第二:代码易维护。
● 第三:使开发者更关注业务逻辑。

2 AOP的七大术语

public class UserService{public void do1(){System.out.println("do 1");}public void do2(){System.out.println("do 2");}public void do3(){System.out.println("do 3");}public void do4(){System.out.println("do 4");}public void do5(){System.out.println("do 5");}// 核心业务方法public void service(){try{// Joinpoint连接点do1(); // Pointcut切点// Joinpoint连接点do2(); // Pointcut切点// Joinpoint连接点do3(); // Pointcut切点// Joinpoint连接点do5(); // Pointcut切点// Joinpoint连接点} catch (Exception e){// Joinpoint连接点} finally {// Joinpoint连接点}}
}
// 1. 连接点(Joinpoint)描述的是位置
// 2. 切点(Pointcut)本质上就是方法(真正织入切面的那个方法叫做切点)
// 3. 通知(Advice),通知又叫做增强。就是具体增强的那个代码
//           例如: 具体的事务代码, 日志代码, 安全代码
//           具体的这个代码是通知
//           通知描述的是代码
// 4. 切面: 切点 + 通知
  • 连接点 Joinpoint
    • 在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。
  • 切点 Pointcut
    • 在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)
  • 通知 Advice
    • 通知又叫增强,就是具体你要织入的代码。
    • 通知包括:
      • 前置通知
      • 后置通知
      • 环绕通知
      • 异常通知
      • 最终通知
  • 切面 Aspect
    • 切点 + 通知就是切面。
  • 织入 Weaving
    • 把通知应用到目标对象上的过程。
  • 代理对象 Proxy
    • 一个目标对象被织入通知后产生的新对象。
  • 目标对象 Target
    • 被织入通知的对象。
      在这里插入图片描述

3 切点表达式

切点表达式用来定义通知(Advice)往哪些方法上切入。
切入点表达式语法格式:

execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

访问控制权限修饰符:

  • 可选项。
  • 没写,就是4个权限都包括。
  • 写public就表示只包括公开的方法。

返回值类型:

  • 必填项。
  • * 表示返回值类型任意。

全限定类名:

  • 可选项。
  • 两个点“..”代表当前包以及子包下的所有类。
  • 省略时表示所有的类。

方法名:

  • 必填项。
  • *表示所有方法。
  • set*表示所有的set方法。

形式参数列表:

  • 必填项
  • () 表示没有参数的方法
  • (..) 参数类型和个数随意的方法
  • (*) 只有一个参数的方法
  • (*, String) 第一个参数类型随意,第二个参数是String的。

异常:

  • 可选项。
  • 省略时表示任意异常类型。

理解以下的切点表达式:

service包下所有的类中以delete开始的所有方法

execution(public * com.powernode.mall.service.*.delete*(..))

mall包下所有的类的所有的方法

execution(* com.powernode.mall..*(..))

所有类的所有方法

execution(* *(..))

4 使用Spring的AOP

Spring对AOP的实现包括以下3种方式:

  • 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
  • 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
  • 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。

使用Spring+AspectJ的AOP需要引入依赖

<dependencies><!--spring context依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.2</version></dependency><!--spring aspects依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.2</version></dependency>
</dependencies>

Spring配置文件中添加context命名空间和aop命名空间

<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"></beans>

1 基于AspectJ的AOP注解式开发

定义目标类以及目标方法

package com.powernode.spring6.service;import org.springframework.stereotype.Service;/*** 目标类*/
@Service("userService")
public class UserService {// 目标方法public void login(){System.out.println("正在登录");/*if (1 == 1){throw new RuntimeException("运行时异常");}*/}
}

定义切面类

通知类型

通知类型包括:

  • 前置通知:@Before 目标方法执行之前的通知
  • 后置通知:@AfterReturning 目标方法执行之后的通知
  • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
  • 异常通知:@AfterThrowing 发生异常之后执行的通知
  • 最终通知:@After 放在finally语句块中的通知
package com.powernode.spring6.service;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;/*** 切面*/
@Component("logAspect")
// 使用@Aspect注解标注切面类
@Aspect
public class LogAspect {// 切面 = 通知 + 切点// 通知就是增强,具体的要编写的增强代码// @Before标注的方法就是一个前置通知// 括号里写切点表达式@Before("execution(* com.powernode.spring6.service.UserService.*(..))")public void beforeAdvice(){System.out.println("前置通知");}// 后置通知@AfterReturning("execution(* com.powernode.spring6.service.UserService.*(..))")public void afterReturningAdvice(){System.out.println("后置通知");}// 环绕通知(环绕是最大的通知,在前置通知之前,在后置通知之后)@Around("execution(* com.powernode.spring6.service.UserService.*(..))")public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {// 前面代码System.out.println("前环绕");// 执行目标joinPoint.proceed();// 后面代码System.out.println("后环绕");}// 异常通知@AfterThrowing("execution(* com.powernode.spring6.service.UserService.*(..))")public void afterThrowingAdvice(){System.out.println("异常通知");}// 最终通知(finally语句块中的通知)@After("execution(* com.powernode.spring6.service.UserService.*(..))")public void afterAdvice(){System.out.println("最终通知");}
}

在spring配置文件中添加组建扫描

<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--组件扫描--><context:component-scan base-package="com.powernode.spring6.service"/><!--开启aspectj的自动代理--><!--spring容器在扫描类的时候,查看该类上是否有@Aspect注解 ,有,则给这个类生成代理--><!--proxy-target-class="true" 表示强制使用CGLIB动态代理proxy-target-class="false" 默认值,表示接口使用JDK动态代理,反之使用CGLIB动态代理--><aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

<aop:aspectj-autoproxy proxy-target-class=“true”/> 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。
proxy-target-class=“true” 表示采用cglib动态代理。
proxy-target-class=“false” 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。

测试

package com.powernode.spring6.test;import com.powernode.spring6.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringAOPTest {@Testpublic void testBefore(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");UserService userService = applicationContext.getBean("userService", UserService.class);userService.login();}
}

在这里插入图片描述
放开目标方法中的异常:
在这里插入图片描述
当发生异常之后,最终通知也会执行,因为最终通知@After会出现在finally语句块中。
出现异常之后,后置通知和环绕通知的结束部分不会执行。

切面的先后顺序

多个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。

/*** 切面*/
@Component("logAspect")
// 使用@Aspect注解标注切面类
@Aspect
@Order(0)
public class LogAspect {
}
定义通用切点表达式
/*** 切面*/
@Component("logAspect")
// 使用@Aspect注解标注切面类
@Aspect
@Order(0)
public class LogAspect {// 定义通用的切点表达式@Pointcut("execution(* com.powernode.spring6.service.UserService.*(..))")public void Point(){// 这个方法只是一个标记,方法名随意,方法体也不需要写代码}// 前置通知@Before("Point()")public void beforeAdvice(){System.out.println("前置通知");}

非本类中使用

package com.powernode.spring6.service;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** 安全切面*/
@Aspect
@Component
@Order(1)
public class SecurityAspect {@Before("com.powernode.spring6.service.LogAspect.Point()")public void beforeAdvice(){System.out.println("安全前置通知");}
}
全注解式开发AOP

就是编写一个类,在这个类上面使用大量注解来代替spring的配置文件

package com.powernode.spring6.service;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;// 代替spring.xml文件
@Configuration
// 组件扫描
@ComponentScan({"com.powernode.spring6.service"})
// 启用aspectj的自动代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Spring6Config {
}

测试

@Test
public void testNoXMl(){ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);UserService userService = applicationContext.getBean("userService", UserService.class);userService.login();
}

2 基于XML配置方式的AOP

编写目标类

package com.powernode.spring6.service.xml;
// 目标对象
public class OrderService {// 目标方法public void logout(){System.out.println("正在退出");}
}

编写切面类,并且编写通知

package com.powernode.spring6.service.xml;import org.aspectj.lang.ProceedingJoinPoint;// 切面
public class TimerAspect {public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {// 前环绕long begin = System.currentTimeMillis();// 目标joinPoint.proceed();// 后环绕long end = System.currentTimeMillis();System.out.println("耗时"+(end - begin)+"毫秒");}
}

编写spring配置文件

<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--纳入spring ioc--><bean id="orderService" class="com.powernode.spring6.service.xml.OrderService"/><bean id="timerAspect" class="com.powernode.spring6.service.xml.TimerAspect"/><!--aop配置--><aop:config><!--切点表达式--><aop:pointcut id="mypointcut" expression="execution(* com.powernode.spring6.service.xml..*(..))"/><!--切面:通知 + 切点--><aop:aspect ref="timerAspect"><aop:around method="aroundAdvice" pointcut-ref="mypointcut"/></aop:aspect></aop:config></beans>

测试程序

package com.powernode.spring6.test;import com.powernode.spring6.service.xml.OrderService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringAOPXMLTest {@Testpublic void testXml(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("springXml.xml");OrderService orderService = applicationContext.getBean("orderService", OrderService.class);orderService.logout();}
}

5 AOP的实际案例:事务处理

控制事务的代码就是和业务逻辑没有关系的“交叉业务”。采用AOP思想。可以把控制事务的代码作为环绕通知,切入到目标类的方法当中。

有两个业务类

package com.powernode.spring6.service;import org.springframework.stereotype.Service;
// 业务类
// 目标对象
@Service
public class AccountService {// 目标方法// 转账的业务方法public void transfer(){System.out.println("银行账户正在完成转账操作");}// 取款的业务方法public void withdraw(){System.out.println("正在取款");}
}
package com.powernode.spring6.service;import org.springframework.stereotype.Service;
// 业务类
// 目标对象
@Service
public class OrderService {// 目标方法// 生成订单的业务方法public void generate(){System.out.println("正在生成订单");}// 取消订单的业务方法public void cancel(){System.out.println("订单已取消");// 模拟异常String s = null;s.toString();}
}

给以上两个业务类的4个方法添加事务控制代码,使用AOP来完成:

package com.powernode.spring6.service;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
// 事务切面类
@Component
@Aspect
public class TransactionAspect {@Around("execution(* com.powernode.spring6.service..*(..))")public void aroundAdvice(ProceedingJoinPoint joinPoint){try {// 前环绕System.out.println("开启事务");// 执行目标joinPoint.proceed();// 后环绕System.out.println("提交事务");} catch (Throwable e) {System.out.println("回滚事务");e.printStackTrace();}}
}

编写spring配置文件

<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--组件扫描--><context:component-scan base-package="com.powernode.spring6.service"/><!--启动自动代理--><aop:aspectj-autoproxy/>
</beans>

编写测试程序:

package com.powernode.spring6.test;import com.powernode.spring6.service.AccountService;
import com.powernode.spring6.service.OrderService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class AOPRealAppTest {@Testpublic void testTransaction(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");AccountService accountService = applicationContext.getBean("accountService", AccountService.class);OrderService orderService = applicationContext.getBean("orderService", OrderService.class);accountService.transfer();accountService.withdraw();orderService.generate();orderService.cancel();}
}

在这里插入图片描述

6 AOP的实际案例:安全日志

需求:凡事在系统中进行修改操作的,删除操作的,新增操作的,都要记录下来。因为这几个操作是属于危险行为。例如有业务类和业务方法:

package com.powernode.spring6.biz;import org.springframework.stereotype.Service;@Service
public class UserService {public void savaUser(){System.out.println("新增用户");}public void deleteUser(){System.out.println("删除用户");}public void modifyUser(){System.out.println("修改用户");}public void getUser(){System.out.println("查询用户");}
}
package com.powernode.spring6.biz;import org.springframework.stereotype.Service;@Service
public class VipService {public void savaVip(){System.out.println("新增会员");}public void deleteVip(){System.out.println("删除会员");}public void modifyVip(){System.out.println("修改会员");}public void getVip(){System.out.println("查询会员");}
}
package com.powernode.spring6.biz;import org.aspectj.lang.JoinPoint;
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.text.SimpleDateFormat;
import java.util.Date;
@Component
@Aspect
public class SecurityLogAspect {@Pointcut("execution(* com.powernode.spring6.biz..sava*(..))")public void savaPointcut(){}@Pointcut("execution(* com.powernode.spring6.biz..delete*(..))")public void deletePointcut(){}@Pointcut("execution(* com.powernode.spring6.biz..modify*(..))")public void modifyPointcut(){}@Before("savaPointcut() || deletePointcut() || modifyPointcut()")public void beforeAdvice(JoinPoint joinPoint){// 系统实际SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");String nowTime = sdf.format(new Date());// 输出日志信息System.out.println(nowTime + " 张三操作了: " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());}
}
@Test
public void testSecurityLog(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");UserService userService = applicationContext.getBean("userService", UserService.class);VipService vipService = applicationContext.getBean("vipService", VipService.class);userService.savaUser();userService.deleteUser();userService.modifyUser();userService.getUser();vipService.savaVip();vipService.deleteVip();vipService.modifyVip();vipService.getVip();
}

相关文章:

十二、面向切面编程AOP

IoC使软件组件松耦合。AOP让你能够捕捉系统中经常使用的功能&#xff0c;把它转化成组件。 AOP&#xff08;Aspect Oriented Programming&#xff09;&#xff1a;面向切面编程&#xff0c;面向方面编程。&#xff08;AOP是一种编程技术&#xff09; AOP是对OOP的补充延伸。 …...

Mybatis 处理 CLOB/BLOB 类型数据

Mybatis 处理 CLOB/BLOB 类型数据 BLOB 和 CLOB 都是大型字段类型。 BLOB通过二进制存储&#xff0c;而CLOB可以直接存储文本。 通常&#xff0c;图片、文件、音乐等信息存储在 BLOB 字段中。首先&#xff0c;文件是转换为二进制&#xff0c;然后存储在。文章或较长的文本存…...

【NLP经典论文阅读】Efficient Estimation of Word Representations in Vector Space(附代码)

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…...

Spring bean生命周期分为几个阶段?

bean 的生命周期从调用 beanFactory 的 getBean 开始&#xff0c;到这个 bean 被销毁&#xff0c;可以总结为以下七个阶段&#xff1a;处理名称&#xff0c;检查缓存→处理父子容器→处理 dependsOn→选择 scope 策略→创建 bean→类型转换处理→销毁 bean划分的阶段和名称并不…...

【基础算法】单链表的OJ练习(4) # 分割链表 # 回文链表 #

文章目录前言分割链表回文链表写在最后前言 本章的OJ练习相对前面的难度加大了&#xff0c;但是换汤不换药&#xff0c;还是围绕单链表的性质来出题的。我相信&#xff0c;能够过了前面的OJ练习&#xff0c;本章的OJ也是轻轻松松。 对于OJ练习(3)&#xff1a;-> 传送门 <…...

SpringBoot整合定时任务和邮件发送(邮箱 信息轰炸 整蛊)

SpringBoot整合定时任务和邮件发送&#xff08;邮箱 信息轰炸 整蛊&#xff09; 目录SpringBoot整合定时任务和邮件发送&#xff08;邮箱 信息轰炸 整蛊&#xff09;1.概述2.最佳实践2.1创建项目引入依赖(mail)2.2 修改yml配置文件2.3 启动类添加EnableScheduling注解2.4 执行的…...

Arduino添加ESP32开发板

【2023年3月4日】 最近要在新电脑上安装Arduino&#xff0c;需要进行一些配置&#xff0c;正好记录一下&#xff01; Arduino2.0.1 下的开发板添加操作。 ESP32开发板GitHub链接&#xff1a; GitHub - espressif/arduino-esp32: Arduino core for the ESP32Arduino core for…...

Mysql通配符的使用

LIKE操作符 通配符&#xff1a;用来匹配值的一部分的特殊字符。 搜索模式&#xff1a;由字面值&#xff0c;通配符或两者组合构成的搜索条件。 百分号(%)通配符 搜索模式使用例如下 SELECT prod_id, prod_name FROM products WHERE prod_name Like jet%; 这条子句表示&…...

RocketMQ-02

1. 案例介绍 1.1 业务分析 模拟电商网站购物场景中的【下单】和【支付】业务 ###1&#xff09;下单 用户请求订单系统下单订单系统通过RPC调用订单服务下单订单服务调用优惠券服务&#xff0c;扣减优惠券订单服务调用调用库存服务&#xff0c;校验并扣减库存订单服务调用用户…...

深度学习卷积神经网络CNN之 VGGNet模型主vgg16和vgg19网络模型详解说明(理论篇)

1.VGG背景 2. VGGNet模型结构 3. 特点&#xff08;创新、优缺点及新知识点&#xff09; 一、VGG背景 VGGNet是2014年ILSVRC&#xff08;ImageNet Large Scale Visual Recognition Challenge大规模视觉识别挑战赛&#xff09;竞赛的第二名&#xff0c;解决ImageNet中的1000类图…...

三:BLE协议架构简介

低功耗蓝牙体系整体架构说明1. PHY(物理层)2. LL(链路层)3. HCI(主机与控制器通信接口)4. L2CAP(逻辑链路控制及适配协议)5. ATT(属性协议)6. GATT(通用属性规范)7. GAP(通用访问规范)8. SM(安全管理)整体架构说明 架构层说明PHY1. 物理层2. 控制射频的发送和接收LL1. 链路层2.…...

小型双轮差速底盘双灰度循迹功能的实现

1. 功能说明 在机器人车体上安装2个 灰度传感器 &#xff0c;实现机器人按照下图所指定的路线进行导航运动&#xff0c;来模拟仓库物流机器人按指定路线行进的工作过程。 2. 使用样机 本实验使用的样机为R023e样机。 3. 功能实现 3.1 电子硬件 在这个示例中&#xff0c;我们采…...

电子签名?玩具罢了!

需要的前置知识&#xff1a;简单的canvas绘制线路过程 let canvas document.getElementById(id); //id为canvas标签元素的id&#xff0c;或通过其它方法获取标签 let ctx canvas.getContext(2d); //规定为2d绘制图片&#xff0c;即确定为2d画笔 ctx.strokeStyle "whit…...

【Spring Boot读取配置文件的方式】

Spring Boot 支持多种读取配置文件的方式&#xff0c;常用的方式有以下三种&#xff1a; application.properties&#xff1a; Spring Boot 默认会读取该文件作为应用的配置文件。可以在 src/main/resources 目录下创建该文件&#xff0c;并在其中配置应用的属性。 applicat…...

java学习路线规划

java学习路线规划 一、写在前面 兄弟&#xff0c;我整理了一下关于自己之前学习java的一些方向&#xff0c;给你归纳在这里&#xff0c;有空就来看看&#xff0c;希望对你有帮助。 二、java基础篇 1、认识java ​ 了解java历史&#xff0c;大概看看发展史&#xff0c;安装…...

格密码学习笔记(二):连续极小、覆盖半径和平滑参数

文章目录最短距离和连续极小值距离函数和覆盖半径格的平滑参数致谢最短距离和连续极小值 除了行列式&#xff0c;格的另一个基本量是格上最短非零向量的长度&#xff0c;即格中最短距离&#xff0c;其定义为 λ1min⁡x,y∈L,x≠y∥x−y∥min⁡z∈L,z≠0∥z∥.\begin{aligned} …...

ios 通过搜索设备MAC地址绑定

最近做了一个物联网项目,涉及到了设备绑定配网这块,需要了解一下iOS BLE与设备绑定的相关知识点,第一次接触蓝牙相关的项目,所以开始熟悉蓝牙的相关信息。没有去深入研究BabyTooth库&#xff0c;只是感觉CoreBluetooth已经让我更好的理解整个流程这个物联网项目的设备绑定流程是…...

Python实现人脸识别,进行视频跟踪打码,羞羞的画面统统打上马赛克

哈喽兄弟们&#xff0c;我是轻松~ 今天我们来实现用Python自动对视频打马赛克前言准备工作代码实战效果展示最后前言 事情是这样的&#xff0c;昨天去表弟家&#xff0c;用了下他的电脑&#xff0c;不小心点到了他硬盘里隐藏的秘密&#xff0c;本来我只需要用几分钟电脑的&…...

vcf bed起始位置是0还是1

VCF 起始位置为1, POS - position: The reference position, with the 1st base having position 1. Positions are sorted numerically, in increasing order, within each reference sequence CHROM. It is permitted to have multiple records with the same POS. Telome…...

Hexo+live2d | 如何把live2d老婆放进自己的博客

参考&#xff1a;Hexo添加Live2D看板娘最新教程live2d-widgetlive2d-widget-models网页/博客Hexo添加live2d游戏角色看板娘,简易添加&#xff0c;碧蓝航线等live2d新型游戏角色模型&#xff08;moc3&#xff09;live2d-moc3jsdelivr方法1可以直接去看参考文章的第一部分的第一篇…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

Chrome 浏览器前端与客户端双向通信实战

Chrome 前端&#xff08;即页面 JS / Web UI&#xff09;与客户端&#xff08;C 后端&#xff09;的交互机制&#xff0c;是 Chromium 架构中非常核心的一环。下面我将按常见场景&#xff0c;从通道、流程、技术栈几个角度做一套完整的分析&#xff0c;特别适合你这种在分析和改…...

SpringAI实战:ChatModel智能对话全解

一、引言&#xff1a;Spring AI 与 Chat Model 的核心价值 &#x1f680; 在 Java 生态中集成大模型能力&#xff0c;Spring AI 提供了高效的解决方案 &#x1f916;。其中 Chat Model 作为核心交互组件&#xff0c;通过标准化接口简化了与大语言模型&#xff08;LLM&#xff0…...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL

ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...