Spring AOP面向切面编程
Spring AOP面向切面编程
- 面向切面编程
- AOP作用
- AOP功能
- AOP总结
- AOP核心概念
- AOP的实现方式
- Spring 对AOP支持
- 支持@Aspect
- 声明一个切面
- 声明一个切入点
- AspectJ描述符如下
- AspectJ类型匹配的通配符
- 常用的匹配规则
- 声明增强
- 用AOP实现日志拦截
- 一般的实现
- 仅拦截需要的方法
- 先定义一个日志注解Log
- 用@annotation定义切入点
- 在想做日志输出的方法上使用注解Log
- requestId传递
- 关于增强执行顺序
面向切面编程
面向切面编程(
AOP,Aspect Oriented Programming)是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
AOP作用
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP功能
主要功能:日志记录、性能统计、安全控制、事务处理、异常处理等。
AOP总结
面向切面编程是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只是修改这个行为即可。
AOP通过提供另一种思考程序结构的方式来补充了面向对象编程(OOP)。OOP中模块化的基本单元是类(class),而AOP中模块化的基本单元是切面(aspect)。可以这么理解,OOP是解决了纵向的代码复用问题,AOP是解决了横向的代码复用问题。
Spring的关键组件之一是AOP框架。虽然Spring IOC容器不依赖于AOP,意味着如果你不想使用AOP,则可以不使用AOP,但AOP补充了Spring IOC以提供一个非常强大的中间件解决方案。

AOP核心概念
- 切面(aspect): 在AOP中,切面一般使用@Aspect注解来标识。
- 连接点(Join Point): 在Spring AOP,一个连接点总是代表一次方法的执行。
- 增强(Advice): 在连接点执行的动作。
- 切入点(Pointcout): 说明如何匹配到连接点。
- 引介(Introduction): 为现有类型声明额外的方法和属性。
- 目标对象(Target Object): 由一个或者多个切面建议的对象,也被称为
建议对象,由于Spring AOP是通过动态代理来实现的,这个对象永远是一个代理对象。 - AOP代理(AOP proxy): 一个被AOP框架创建的对象,用于实现切面约定(增强方法的执行等)。在
Spring Framework中,一个AOP代理是一个JDK动态代理或者CGLIB代理。 - 织入(Weaving): 连接切面和目标对象或类型创建代理对象的过程。它能在编译时(例如使用
AspectJ编译器)、加载时或者运行时完成。Spring AOP与其他的纯Java AOP框架一样是在运行时进行织入的。

Spring AOP包括以下类型的增强:
前置增强(Before advice):在连接点之前运行,但不能阻止到连接点的流程继续执行(除非抛出异常)返回增强(After returning advice):在连接点正常完成后运行的增强(例如,方法返回没有抛出异常)异常增强(After thorwing advice):如果方法抛出异常退出需要执行的增强后置增强(After (finally) Advice):无论连接点是正常或者异常退出,都会执行该增强环绕增强(Around advice):围绕连接点的增强,例如方法的调用。环绕增强能在方法的调用之前和调用之后自定义行为。它还可以选择方法是继续执行或者去缩短方法的执行通过返回自己的值或者抛出异常。
AOP的实现方式
AOP的两种实现方式:静态织入(以AspectJ为代表)和动态代理(Spring AOP实现)。
AspectJ是一个采用Java实现的AOP框架,它能够对代码进行编译(在编译期进行),让代码具有AspectJ的AOP功能,当然它也可支持动态代理的方式;Spring AOP实现:通过动态代理技术来实现,Spring2.0集成了AspectJ,主要用于PointCut的解析和匹配,底层的技术还是使用的Spring1.x中的动态代理来实现。

Spring AOP实现采用了两种混合的实现方式:JDK动态代理和CGLib动态代理。
- JDK动态代理:Spring AOP的首选方法。每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口。
- CGLIB:如果目标对象没有实现接口,则可以使用
CGLIB代理。

Spring 对AOP支持
Spring可以使用两种方式来实现AOP:基于注解式配置和基于XML配置。
下面介绍基于注解配置的形式
支持@Aspect
如果是Spring Framework,需要使用aspectjweaver.jar包,然后创建我们自己的AppConfig,如下,并加上@EnableAspectJAutoProxy注解开启AOP代理自动配置(Spring Boot默认是开启的,则不需要增加配置),如下:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {}
声明一个切面
@Aspect //告诉Spring 这是一个切面
@Component //交给Spring容器管理
public class MyAspect {}
可以使用@Aspect来定义一个类作为切面,但是这样,该类并不会自动被Spring加载,还是需要加上@Component注解。
声明一个切入点
一个切入点的生命包含两个部分:一个包含名称和任何参数的签名和一个切入点的表达式,这个表达式确定了我们对哪些方法的执行感兴趣。
我们以拦截Controller层中的MyController中的test方法为例子,代码如下:
@RestController
@RequestMapping("/my")
public class MyController {@GetMapping("/test")public void test() {System.out.println("test 方法");}
}
下面定义一个名为controller的切入点,该切入点与上述的test方法相匹配,切入点需要用@Pointcut注解来标注,如下:
//表达式
@Pointcut("execution (public * com.yc.springboot.controller.MyController.test())")
public void controller(){}; //签名
切入点表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
execution(方法修饰符(可选) 返回类型 类路径 方法名 参数 异常模式(可选))
AspectJ描述符如下
| AspectJ描述符 | 描述 |
|---|---|
| arg() | 限制连接点匹配参数为指定类型的执行方法 |
| @args() | 限制连接点匹配参数由指定注解标注的执行方法 |
| execution() | 用于匹配是连接点的执行方法 |
| this() | 限制连接点匹配的AOP代理的bean引用为指定类型的类 |
| target | 限制连接点匹配目标对象为指定类型的类 |
| @target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解 |
| within() | 限制连接点匹配指定的类型 |
| @within() | 限制连接点匹配指定注解所标注的类型 |
| @annotationn | 限定匹配带有指定注解的连接点 |
常用的主要是:execution()
AspectJ类型匹配的通配符
| 通配符 | 含义 |
|---|---|
* | 匹配任何数量字符 |
.. | 匹配任何数量字符的重复 |
+ | 匹配指定类型的子类型;仅能用于后缀放在类型模式后面 |
常用的匹配规则
| 表达式 | 内容 |
|---|---|
execution(public * *(..)) | 匹配所有public方法 |
execution(* set*(..)) | 匹配所有方法名开头为set的方法 |
execution(* com.xyz.service.AccountService.*(..)) | 匹配AccountService下的所有方法 |
execution(* com.xyz.service.*.*(..)) | 匹配service包下的所有方法 |
execution(* com.xyz.service..*.*(..)) | 匹配service包或其子包下的所有方法 |
@annotation(org.springframework.transaction.annotation.Transactional) | 匹配所有打了@Transactional注解的方法 |
bean(*Service) | 匹配命名后缀为Service的类的方法 |
声明增强
增强与切点表达式相关联,并且在与切点匹配的方法之前、之后或者前后执行。
下面直接罗列了各种增强的声明,用于拦截MyController中的各个方法
@Aspect //告诉Spring 这是一个切面
@Component //告诉Spring容器需要管理该对象
public class MyAspect {//通过规则确定哪些方法是需要增强的@Pointcut("execution (public * com.yc.springboot.controller.MyController.*(..))")public void controller() {}//前置增强@Before("controller()")public void before(JoinPoint joinPoint) {System.out.println("before advice");}//返回增强@AfterReturning(pointcut = "controller()",returning = "retVal")public void afterReturning(Object retVal) {System.out.println("after returning advice, 返回结果 retVal:" + retVal);}//异常增强@AfterThrowing(pointcut = "controller()",throwing = "ex")public void afterThrowing(Exception ex) {System.out.println("after throwing advice, 异常 ex:" + ex.getMessage());}//后置增强@After("controller()")public void after(JoinPoint joinPoint) {System.out.println("after advice");}//环绕增强@Around("controller()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("before advice");//相当于是before adviceObject reVal = null;try {reVal = joinPoint.proceed();} catch (Exception e) {//相当于afterthrowing adviceSystem.out.println("afterthrowing advice");}//相当于是after adviceSystem.out.println("after advice");return reVal;}
}
需要注意的是:
- 在返回增强中,我们需要给
@AfterReturing设置returning的值,且需要与方法的参数名一致,用于表示业务方法的返回值 - 在异常增强中,需要给
@AfterThrowing设置throwing的值,且需要与方法的参数名一致,用于表示业务方法产生的异常 - 在环绕增强中,参数为
ProceedingJoinPoint类型,它是JoinPoint的子接口,我们需要在这个方法中手动调用其proceed方法来触发业务方法 - 在所有的增强方法中都可以申明第一个参数为
JoinPoint(注意的是,环绕增强是使用ProceedingJoinPoint来进行申明,它实现了JoinPoint接口) JoinPoint接口提供了几个有用的方法- getArgs():返回这个方法的参数
- getThis():返回这个代理对象
- getTarget():返回目标对象(被代理的对象)
- getSignature():返回被增强方法的描述
- toString():打印被增强方法的有用描述
下面为Mycontroller测试类:
@RestController
@RequestMapping("/my")
public class MyController {@GetMapping("/testBefore")public void testBefore() {System.out.println("testBefore 业务方法");}@GetMapping("/testAfterReturning")public String testAfterReturning() {System.out.println("testAfterReturning 业务方法");return "我是一个返回值";}@GetMapping("/testAfterThrowing")public void testAfterThrowing() {System.out.println("testAfterThrowing 业务方法");int a = 0;int b = 1 / a;}@GetMapping("/testAfter")public void testAfter() {System.out.println("testAfter 业务方法");}@GetMapping("/around")public void around() {System.out.println("around 业务方法");}
}
用AOP实现日志拦截
一般的实现
打印日志是AOP的一个常见应用场景,我们可以对Controller层向外提供的接口做统一的日志拦截,用日志记录请求参数、返回参数、请求时长以及异常信息,方便我们线上排查问题,下面是核心类LogAspect的实现
/*** 日志的切面*/
@Aspect
@Component
public class LogAspect {@Resourceprivate IdWorker idWorker;@Pointcut("execution (public * com.yc.core.controller.*.*(..))")public void log(){}/*** 使用环绕增强实现日志打印* @param joinPoint* @return* @throws Throwable*/@Around("log()")public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {//获得执行方法的类和名称String className = joinPoint.getTarget().getClass().getName();//获得方法名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String methodName = signature.getMethod().getName();//获得参数Object[] args = joinPoint.getArgs();long requestId = idWorker.nextId();//打印参数LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",params:" + JSONObject.toJSONString(args));long startTime = System.currentTimeMillis();//执行业务方法Object result = null;try {result = joinPoint.proceed();} catch (Exception e) {LogHelper.writeErrLog(className, methodName, "requestId:" + requestId + ",异常啦:" + LogAspect.getStackTrace(e));}long endTime = System.currentTimeMillis();//打印结果LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",耗时:" + (endTime - startTime) + "ms,result:" + JSONObject.toJSONString(result));//返回return result;}/*** 获取异常的堆栈信息* @param throwable* @return*/public static String getStackTrace(Throwable throwable){StringWriter sw = new StringWriter();PrintWriter pw = new PrintWriter(sw);try{throwable.printStackTrace(pw);return sw.toString();} finally{pw.close();}}
}
- 在
proceed()方法之前,相当于前置增强,收集类名、方法名、参数,记录开始时间,生成requestId - 在
proceed()方法之后,相当于后置增强,并能获取到返回值,计算耗时 - 在前置增强时,生成
requestId,用于串联多条日志 - 使用
try、catch包裹proceed()方法,在catch中记录异常日志 - 提供了
getStackTrace方法获取异常的堆栈信息,便于排查报错详细情况
仅拦截需要的方法
但是上面的日志是针对所有controller层中的方法进行了日志拦截,如果我们有些方法不想进行日志输出,比如文件上传的接口、大量数据返回的接口,这个时候定义切入点的时候可以使用@annotation描述符来匹配加了特定注解的方法,步骤如下:
先定义一个日志注解Log
/*** 自定义日志注解*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
用@annotation定义切入点
@Pointcut("@annotation(com.yc.core.annotation.Log)")
public void logAnnotation(){}
在想做日志输出的方法上使用注解Log
@Log
@PostMapping(value = "testannotation")
public AOPTestVO testannotation(@RequestBody AOPTestDTO aopTestDTO) {AOPTestVO aopTestVO = new AOPTestVO();aopTestVO.setCode(1);aopTestVO.setMsg("哈哈哈");return aopTestVO;
}
这样,我们就可以自定义哪些方法需要日志输出了。
requestId传递
后来有同事提到,如果这是针对Controller层的拦截,但是Service层也有自定义的日志输出,怎么在Service层获取到上述的requestId呢?
其实就是我们拦截之后,是否可以针对方法的参数进行修改呢?其实注意是看
result = joinPoint.proceed();
我们发现ProceedingJoinPoint还有另外一个带有参数的proceed方法,定义如下:
public Object proceed(Object[] args) throws Throwable;
我们可以利用这个方法,在环绕增强中去增加requestId,这样后面的增强方法或业务方法中就能获取到这个requestId了。
首先,我们先定义一个基类AOPBaseDTO,只有一个属性requestId
@Data
@ApiModel("aop参数基类")
public class AOPBaseDTO {@ApiModelProperty(value = "请求id", hidden = true)private long requestId;
}
然后我们让Controller层接口的参数AOPTestDTO继承上述AOPBaseDTO,如下:
@Data
@ApiModel("aop测试类")
public class AOPTestDTO extends AOPBaseDTO{@ApiModelProperty(value = "姓名")private String name;@ApiModelProperty(value = "年龄")private int age;
}
最后在环绕的增强中添加上requestId,如下:
/*** 使用环绕增强实现日志打印* @param joinPoint* @return* @throws Throwable*/
@Around("logAnnotation()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {//获得执行方法的类和名称String className = joinPoint.getTarget().getClass().getName();//获得方法名MethodSignature signature = (MethodSignature) joinPoint.getSignature();String methodName = signature.getMethod().getName();//获得参数Object[] args = joinPoint.getArgs();long requestId = idWorker.nextId();for(int i = 0; i < args.length; i++) {if (args[i] instanceof AOPBaseDTO) {//增加requestId((AOPBaseDTO) args[i]).setRequestId(requestId);}}//打印参数LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",params:" + JSONObject.toJSONString(args));long startTime = System.currentTimeMillis();//执行业务方法Object result = null;try {result = joinPoint.proceed(args);} catch (Exception e) {LogHelper.writeErrLog(className, methodName, "requestId:" + requestId + ",异常啦:" + LogAspect.getStackTrace(e));}long endTime = System.currentTimeMillis();//打印结果LogHelper.writeInfoLog(className, methodName, "requestId:" + requestId + ",耗时:" + (endTime - startTime) + "ms,result:" + JSONObject.toJSONString(result));//返回return result;
}
我们运行起代码,访问一下,下面是运行结果:

可以看到,我们的业务方法中已经能获取到requestId,如果Service层需要,可以通过传递AOPTestDTO,从中获取。
关于增强执行顺序
- 针对不同类型的增强,顺序固定的,比如
before在after前面 - 针对同一切面的相同类型的增强,根据定义先后顺序依次执行
- 针对不同切面的相同增强,可以通过使我们的切面实现
Ordered接口,重写getOrder方法,返回值最小,优先级越高。注意的是before和after的相反的,before的优先级越高越早执行,after的优先级越高,越晚执行。
相关文章:
Spring AOP面向切面编程
Spring AOP面向切面编程 面向切面编程AOP作用AOP功能AOP总结 AOP核心概念AOP的实现方式Spring 对AOP支持支持Aspect声明一个切面声明一个切入点AspectJ描述符如下AspectJ类型匹配的通配符常用的匹配规则 声明增强 用AOP实现日志拦截一般的实现仅拦截需要的方法先定义一个日志注…...
Visual Studio 中增加的AI功能
前言: 人工智能的发展,在现在,编程技术的IDE里面也融合了AI的基本操做。本例,以微软的Visual Studio中的人工智能的功能介绍例子。 本例的环境: Visual Studio 17.12 1 AI 智能变量检测: 上图展示了一…...
15. 接雨水
接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(…...
从索尼爱立信手机打印短信的简单方法
昨天,我买了一部新手机来代替我的旧索尼爱立信Xperia,但手机上有很多珍贵的短信,是我男朋友发来的,我不想失去它们。然后我尝试打印它们,但我无法从我的索尼爱立信手机中取出它们。您有什么从索尼爱立信手机打印短信的…...
Java-list均分分割到多个子列表
在Java中,如果你有一个List并且想要将其均分到多个子列表中,可以使用以下方法。假设你有一 个List<T>,并且想要将其分成n个子列表。 import java.util.ArrayList; import java.util.List;public class ListSplitter {public static <T> List<List<T>…...
kettle合并表数据
总体执行图:以两个数据表作为输入,根据关键栏位进行合并后,以excel表输出。 两表数据输入 需要确定查询的表名 2. 根据关键栏位进行排序。在记录集连接之前需要进行排序操作 3. 记录连接与合并 此方式表示select EQP_ID, ID FROM T_EQP_C…...
蓝耘平台使用InstantMesh生成高质量的三维网格模型!3D内容创作!小白入门必看!!!
目录 引言 InstantMesh应用介绍 蓝耘平台与InstantMesh结合使用 如何部署(超简单) 第一步登录蓝耘平台 第二步点击应用商城 编辑 第三步选择InstantMesh 第四步点击部署 第五步点击快速启动应用 第六步即可体验该产品 总结 注册链接 引言…...
关于IDE的相关知识之二【插件推荐】
成长路上不孤单😊😊😊😊😊😊 【14后😊///计算机爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于ide插件推荐的相关内容!…...
oceanbase集群访问异常问题处理
1.报错现象 2.问题排查 检查obproxy状态发现为不可用状态 重启obproxy 依次重启Obproxy集群 观察任务状态 重启完成 Obproxy状态正常 3.验证登录 登录成功...
Linux(centos)安装 MySQL 8 数据库(图文详细教程)
前言 前几天写了个window系统下安装Mysql的博客,收到很多小伙伴私信需要Linux下安装Mysql的教程,今天这边和大家分享一下,话不多说,看教程。 一、删除以前安装的MySQL服务 一般安装程序第一步都需要清除之前的安装痕迹ÿ…...
C++之map和set的模拟实现
目录 引言 红黑树迭代器实现 红黑树元素的插入 map模拟实现 set模拟实现 之前我们已经学习了map和set的基本使用,但是因为map和set的底层都是用红黑树进行封装实现的,上期我们已经学习了红黑树的模拟实现,所以本期我们在红黑树模拟实现…...
判断一个单链表是否是回文结构 要求O(N)时间复杂度 O(1)空间复杂度
没做出来 看了解析 但是思路想到了 就是只能调整链表顺序,正确答案是 把链表变成两条单链表,分别从两侧走向中间拿两个指针 分别指向两头 ,往中间走 中途有不一样的就返回false, private static boolean handle(Node head){int size size…...
Kafka 快速实战及基本原理详解解析-01
一、Kafka 介绍 1. MQ 的作用 消息队列(Message Queue,简称 MQ)是一种用于跨进程通信的技术,核心功能是通过异步消息的方式实现系统之间的解耦。它在现代分布式系统中有着广泛的应用,主要作用体现在以下三个方面&…...
wujie无界微前端框架初使用
先说一下项目需求:将单独的四套系统的登录操作统一放在一个入口页面进行登录,所有系统都使用的是vue3,(不要问我为啥会这样设计,产品说的客户要求) 1.主系统下载wujie 我全套都是vue3,所以直接…...
C++ 设计模式:职责链模式(Chain of Responsibility)
链接:C 设计模式 链接:C 设计模式 - 组合模式 链接:C 设计模式 - 迭代器模式 职责链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它允许多个对象都有机会处理请求,从而避免请求…...
Yocto项目 - 详解PACKAGECONFIG机制
引言 Yocto项目是一个强大的嵌入式Linux开发工具,广泛应用于创建定制的嵌入式Linux发行版。在Yocto中,配置和定制化构建系统、软件包、以及生成适用于特定硬件的平台镜像是非常重要的。PACKAGECONFIG是Yocto项目中用于灵活启用或禁用软件包特性的强大工…...
Linux下部署ElasticSearch集群
Elasticsearch7.17.8集群的搭建 节点host名称节点ip节点部署内容k8s-m192.168.40.142主节点 数据节点k8s-w1192.168.40.141主节点 数据节点k8s-w2192.168.40.140数据节点 一、准备安装环境 1.下载安装包 官网 www.elastic.co 下载所有版本地址 点击跳转 下载elasticsearch-7.…...
超高分辨率 图像 分割处理
文章大纲 制造业半导体领域高分辨率图像半导体数据集开源的高分辨率晶圆图像数据集1. WM-811K数据集2. Kaggle上的WM-811K Clean Subset数据集医疗 病理领域高分辨率图像1. Camelyon+2. CAMELYON173. CPIA Dataset4. UCF-WSI-Dataset航拍 遥感中的高分辨率 图像航拍遥感领域高分…...
【含文档+PPT+源码】基于springboot的农贸菜市场租位管理系统的设计与实现
开题报告 本文旨在探讨基于SpringBoot框架构建的农贸菜市场租位管理系统的设计与实现。系统结合了现代化信息技术与农贸市场管理需求,为用户提供了注册登录、查看系统公告、分类搜索店铺、查看店铺详情、填写租赁信息、在线租赁、我的订单管理以及用户信息和密码修…...
信息科技伦理与道德1:绪论
1 问题描述 1.1 信息科技的进步给人类生活带来的是什么呢? 功能?智能?陪伴?乐趣?幸福? 基于GPT-3的对话Demo DeepFake 深伪技术:通过神经网络技术进行大样本学习,将个人的声音、面…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...
DAY 45 超大力王爱学Python
来自超大力王的友情提示:在用tensordoard的时候一定一定要用绝对位置,例如:tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾: tensorboard的发展历史和原理tens…...
PydanticAI快速入门示例
参考链接:https://ai.pydantic.dev/#why-use-pydanticai 示例代码 from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIModel from pydantic_ai.providers.openai import OpenAIProvider# 配置使用阿里云通义千问模型 model OpenAIMode…...
