JavaEE从入门到起飞 (三) ~AOP

 晚上好,愿这深深的夜色给你带来安宁,让温馨的夜晚抚平你一天的疲惫,美好的梦想在这个寂静的夜晚悄悄成长。

目录

文章目录

前言

了解面向切面编程(AOP)

什么是面向切面编程(AOP)?

举个例子理解AOP和代理思想

一、主要概念?

1.1 Aspect(切面)

1.2 切入点表达式

切入点表达式的标准格式:

 切入点表达式的通配符:

@Pointcut

1.3 通知类型 

二、日志案例

1.导入初始环境

1.1 引入AOP的依赖

1.2 初始化数据库和实体类

1.3 创建日志的持久层Mapper 

1.4 创建自定义注解

2.核心逻辑

2.1 对要增强的方法添加自定义注解

2.2 一些常用的获取请求信息的方法

2.3 编写切面类进行增强 


前言

了解面向切面编程(AOP)

在现代软件开发中,面向切面编程(Aspect-Oriented Programming,AOP)是一种重要的编程范式,它提供了一种有效的方式来解决横切关注点(cross-cutting concerns)的问题。本文将介绍AOP的概念、其主要组成部分以及如何在Java应用程序中应用AOP技术。

什么是面向切面编程(AOP)?

AOP是一种软件开发技术,它允许开发人员将横切关注点(如日志记录、事务管理、安全性、缓存等)从应用的核心逻辑中分离出来。这些横切关注点可能会散布在应用程序的多个模块中,传统的面向对象编程往往会导致这些关注点与核心业务逻辑混杂在一起,使得代码难以理解、维护困难。AOP的出现旨在通过模块化横切关注点的方式来提高代码的模块性、可维护性和可重用性。

举个例子理解AOP和代理思想

例如:理发店这个工作流程,这样来一个顾客,我们就需要这样的流程,十分繁琐,对于聪明的我们自然是不愿意这样干的

我们可以抽取一个公共的方法,来直接调用即可,但仍然还需要我们调用,不够懒。

因此我们可以引入切面的思想了,我们把匹配的流程,自动执行对应的操作。

主要原理:代码,通过代理对象伪装成真正的对象来为我们服务

剪发的时候代理对象,会给你找剪头发的托尼老师,如果你想染发,代理也可以给你找专门染发的托尼老师。这就是我们AOP的思想了。

将真实bean作为代理对象中的一个成员变量。


一、主要概念?

横向增强内容,是一种无侵入的对原始方法进行增强

三大条件需要声明:

  1. 要增强的方法(切入点表达式进行匹配)
  2. 增加方法
  3. 增强方法和切入点方法的执行顺序 (通知方式 四大通知类型和环绕通知)

在AOP中,有几个核心概念需要理解:

  • 连接点(Join Point): 连接点是程序执行过程中的一个特定点,例如方法调用或抛出异常。在AOP中,连接点是可以被增强(如添加日志、事务管理等)的程序执行点。

  • 切入点(Pointcut): 切入点定义了在程序中哪些连接点上应用通知(Advice)。它通过表达式或者模式匹配来描述要被增强的一组连接点。

  • 切面(Aspect): 切面是将通知(Advice)和切入点(Pointcut)结合起来的一个模块化单元。它描述了在何处以及何时应用通知来实现横切关注点的功能。

  • 通知(Advice): 通知是在切面的某个特定连接点上执行的动作。它定义了增强代码的类型和时机,如在方法调用之前、之后或者环绕方法执行。

  • 目标对象(Target Object): 目标对象是一个或多个切面所增强的原始对象。它包含业务逻辑,通常是不察觉被应用切面的存在。

  • 代理对象(Proxy Object): 代理对象是生成的对象,用作目标对象的替代品。它拦截对目标对象的方法调用,并允许AOP框架应用切面(如日志记录、安全性、事务管理等)。

1.1 Aspect(切面)

使用@Aspect和@Component的类

为什么切面还要声明成bean?

因为如果一个类没有被 Spring 管理,那么 Spring 将无法为它创建代理对象,也就无法实现 AOP 功能。

@Aspect的作用是什么?

因为,Spring容器启动后,会优先读取声明到@Aspect类并且读取所有切面配置的切入点,然后在初始化bean判断bean中的方法是否匹配切入点,匹配失败就创建对象到IOC,匹配成功,就创建原始对象的代理对象,然后会将代理对象注入为该类型的Bean,然后当你注入该Bean的时候实际注入的是代理对象的Bean,执行的就是代理对象与连接点映射的方法。

代理对象内容:根据切面中的增强方法和原始对象生成 。

1.2 切入点表达式

主要思想:匹配所有的切点,然后根据条件筛选出连接点,然后进行增强。

切入点表达式的标准格式:

execution([访问修饰符] [返回值类型] [包名.类名.]方法名(参数列表) [throws 异常名])
  • 访问修饰符:可选项,例如 publicprotected 等,可以省略。
  • 返回值类型:方法的返回类型,例如 voidint 等。
  • 包名.类名:类的完整路径,可以省略。
  • 方法名:目标方法的名称。
  • 参数列表:方法的参数列表。
  • throws 异常名:方法声明的异常,可选,指定方法可能抛出的异常。

 切入点表达式的通配符:

  • *:匹配任意数量的字符(单个独立的任意符号,可以单独使用或者作为前缀或后缀)。
  • ..:匹配任意数量的字符序列(多个连续的任意符号,常用于简化包名和参数的书写)。
  • +:写在类或接口的后面,专用于匹配子类类型。

@Pointcut

是在 Spring AOP 中用来定义切入点的注解。

在 Spring AOP 中,切入点(Pointcut)是一组匹配连接点(Join Point)的规则。连接点是在应用执行过程中能够插入切面的点,例如方法执行时、方法调用时等。而切入点则是为了从连接点中筛选出我们真正关心的一部分。

execution:有一定规律的(可以按照规律模糊匹配)

  • execution 表达式:这种表达式按照一定的规则和格式来匹配方法的执行。它允许根据方法的访问修饰符、返回值类型、包名、类名、方法名、参数列表以及可能抛出的异常来进行精确或模糊的匹配。

    @Pointcut("execution(* com.example.service.*.*(..))")private void serviceMethods() {}@Before("serviceMethods()")public void beforeServiceMethods(JoinPoint joinPoint) {// 在 serviceMethods() 匹配的方法执行前执行此通知}

@annotion:没有规律,指定注解被匹配

  • @annotation 注解表达式:这种表达式是一种特殊的切入点表达式,用于匹配被特定注解标注的方法。它不需要关注方法的签名、包名等,而是专门指定某个或某些特定的注解。

    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")private void transactionalMethods() {}@Around("transactionalMethods()")public Object aroundTransactionalMethods(ProceedingJoinPoint joinPoint) throws Throwable {// 在使用 @Transactional 注解的方法周围执行此通知return joinPoint.proceed();}

1.3 通知类型 

我认为通知类型,就是决定了原始方法的执行位置

  1. @Before: 前置通知

  2. @After: 后置通知

  3. @Around: 环绕通知

    1. 需要有参数来确定原始方法所在的位置,然后调用proceed()方法来调用原始方法。
    2. 需要把原始方法返回值扔出去,就是proceed()的返回值。注意,proceed()方法如果要修改参数的内容可以传参。
  4. @AfterReturning: 返回后通知

    1. 在方法返回后执行,不抛出异常的情况下。
  5. @AfterThrowing: 抛出异常后通知

对于环绕方式(Around)通过调用原始方法的实际,可以完成上述四者所有的功能。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;@Aspect
public class UserServiceAspect {@Before("execution(* com.example.service.UserService.*(..))")public void beforeAdvice() {System.out.println("前置通知: 在UserService方法执行前进行");}@After("execution(* com.example.service.UserService.*(..))")public void afterAdvice() {System.out.println("后置通知: 在UserService方法执行后进行(相当于finally块)");}@Around("execution(* com.example.service.UserService.*(..))")public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕通知: UserService方法执行前");Object result = joinPoint.proceed(); // 执行方法System.out.println("环绕通知: UserService方法执行后");return result;}@AfterReturning(pointcut = "execution(* com.example.service.UserService.*(..))", returning = "result")public void afterReturningAdvice(Object result) {System.out.println("返回后通知: 在UserService方法返回后执行");}@AfterThrowing(pointcut = "execution(* com.example.service.UserService.*(..))", throwing = "ex")public void afterThrowingAdvice(Exception ex) {System.out.println("异常抛出通知: UserService方法中抛出的异常: " + ex.getMessage());}
}

二、日志案例

注意下述是使用SpringBoot的方式实现的Aop。

我们要实现的功能是,要记录controller中增 删 改方法的运行日志保存到日志表中

日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法作用、方法运行时参数、返回值、方法执行时长

1.导入初始环境

1.1 引入AOP的依赖

        <!--aop相关的依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

1.2 初始化数据库和实体类

初始化数据库

-- 操作日志表
create table operate_log(id bigint unsigned primary key auto_increment comment 'ID',class_name varchar(100) comment '操作的类名',method_name varchar(100) comment '操作的方法名',method_desc varchar(100) comment '方法用途',method_params varchar(1000) comment '方法参数',return_value varchar(2000) comment '返回值',operate_user int unsigned comment '操作人ID',operate_time datetime comment '操作时间',cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';

 创建实体类

package com.itheima.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.time.LocalDateTime;@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {private Integer id; //IDprivate String className; //操作类名private String methodName; //操作方法名private String methodDesc; //方法用途private String methodParams; //操作方法参数private String returnValue; //操作方法返回值private Integer operateUser; //操作人IDprivate LocalDateTime operateTime; //操作时间private Long costTime; //操作耗时
}

1.3 创建日志的持久层Mapper 

package com.itheima.mapper;import com.itheima.pojo.OperateLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface OperateLogMapper {//插入日志数据@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name,method_desc, method_params, return_value, cost_time) " +"values (#{operateUser}, #{operateTime}, #{className}, #{methodName},#{methodDesc}, #{methodParams}, #{returnValue}, #{costTime});")public void insert(OperateLog log);
}

因为不是对Controller的所有方法进行增强,因此没有规律,所以需要注解的方法进行匹配切点。 

1.4 创建自定义注解

package com.csy.anno;import java.lang.annotation.*;/*** @author windStop* @version 1.0* @description 记录日志所有Controller的增删改的日志* @date 2024年08月08日19:42:23*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogMyAnno {String value() default "";
}

2.核心逻辑

2.1 对要增强的方法添加自定义注解

package com.csy.controller;import com.csy.anno.LogMyAnno;
import com.csy.entity.Dept;
import com.csy.service.DeptService;
import com.csy.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;
import java.util.Map;/*** @author windStop* @version 1.0* @description 关于部门管理的表现层* @date 2024年08月06日16:35:50*/
@RestController
@RequestMapping("/depts")
public class DeptController {@Autowiredprivate DeptService deptService;/*** 查询所有部门信息* 通过调用deptMapper的selectList方法,查询所有部门的信息* 该方法不需要任何查询条件,返回所有部门的列表** @return 包含所有部门信息的List集合*/@GetMappingpublic Result<List<Dept>> findAll() {return Result.success(deptService.findAll());}/*** 使用POST请求方式插入部门信息** @param map 部门名称是json参数,string类型会把大括号和键值对一起转换,因此需要map* @return 包含操作结果的对象,成功时返回成功信息*/@LogMyAnno("添加部门")@PostMappingpublic Result<Object> insertDept(@RequestBody Map<String,String> map) {deptService.insertDept(map.get("name"));return Result.success();}/*** 删除部门调用服务层的deleteDept方法来删除对应的部门* 不返回任何数据,使用Result对象表示操作结果* @param ids 部门的ID* @return Result<Object>对象,表示操作结果*/@LogMyAnno("删除部门")@DeleteMapping("/{ids}")public Result<Object> deleteDept(@PathVariable List<Integer> ids) {deptService.deleteDept(ids);return Result.success();}/*** 更新部门信息* 通过PutMapping注解指定处理更新部门信息的HTTP Put请求* 该方法接收一个Dept对象,更新数据库中的部门信息,并返回操作结果** @param dept 待更新的部门对象,包含部门的所有信息* @return Result<Object>类型的结果对象,包含操作是否成功的状态信息*         在本方法中,始终返回成功状态,不包含额外的数据* @see DeptService updateDept(Dept) 实际执行更新操作的服务方法*/@LogMyAnno("更新员工")@PutMappingpublic Result<Object> updateDept(@RequestBody Dept dept) {deptService.updateDept(dept);return Result.success();}/*** 通过ID查找部门信息** 此方法通过接收一个部门ID,调用部门服务(DeptService)的findById方法来查找特定的部门信息* 它使用了@GetMapping注解来处理HTTP GET请求,路径中的{id}是动态接收的参数,对应于数据库中的部门ID** @param id 部门的唯一标识符,用于定位特定的部门* @return 返回一个Result对象,其中包含找到的部门信息如果找到,则Result的成功标志为true,数据为找到的部门;如果未找到,则成功标志为false,数据为空*/@GetMapping("/{id}")public Result<Dept> findById(@PathVariable Integer id) {return Result.success(deptService.findById(id));}
}

2.2 一些常用的获取请求信息的方法

        // 获取Request对象RequestAttributes attributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes sra = (ServletRequestAttributes) attributes;HttpServletRequest request = sra.getRequest();// 记录访问的URLString url = request.getRequestURI();logger.info("URL Accessed: {}", url);// 记录请求方式String method = request.getMethod();logger.info("Request Method: {}", method);// 获取客户端IPString clientIP = getIpAddr(request);logger.info("Client IP Address: {}", clientIP);

2.3 编写切面类进行增强 

package com.csy.log;import com.csy.anno.LogMyAnno;
import com.csy.dao.OperateLogMapper;
import com.csy.entity.OperateLog;
import com.csy.utils.ThreadLocalUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Map;/*** @author windStop* @version 1.0* @description 记录增删改查的日志* @date 2024年08月08日19:49:54*/
@Component
@Aspect //切面类,会先扫描该注解在创建bean,会将切入点匹配的方法所对应的类创建代理对象。// 原始对象不加入ioc管理,该类的代理对象完全替代它
public class Logger {@Autowiredprivate OperateLogMapper operateLogMapper;//1.定义切入点表达式@Pointcut("@annotation(com.csy.anno.LogMyAnno)")public void pt(){};//2.增强方法@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws RuntimeException {OperateLog log = new OperateLog();log.setOperateTime(LocalDateTime.now());//操作时间MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();
//        log.setMethodParams(Arrays.toString(signature.getParameterNames()));操作变量名写错了log.setMethodParams(Arrays.toString(pjp.getArgs()));//操作方法参数log.setMethodName(method.getName());//操作方法名log.setMethodDesc(method.getAnnotation(LogMyAnno.class).value());//操作用途log.setClassName(pjp.getTarget().getClass().getName());//操作类名Map<String, Object> tokens = ThreadLocalUtil.get();log.setOperateUser((Integer) tokens.get("id"));//操作人IDlong start = System.currentTimeMillis();Object proceed;//执行目标方法try {proceed = pjp.proceed();log.setReturnValue(proceed.toString());//操作返回值return proceed;}catch (Throwable t){throw new RuntimeException(t);}finally {long end = System.currentTimeMillis();log.setCostTime(end - start);//操作耗时(毫秒值)//保存日志operateLogMapper.insert(log);}}
}

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

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

相关文章

二、Matlab图像处理基础

文章目录 一、Matlab图像处理工具箱二、图像文件的读取2.1 文件信息的读取2.2 图像文件的读取2.3 图像文件的保存2.4 图像文件的显示2.5 像素信息的显示 本章知识点总结 一、Matlab图像处理工具箱 在帮助文档可以搜索到图像处理工具箱的介绍 二、图像文件的读取 2.1 文件信息…

回归评价指标

这里写目录标题 1. 均方误差MSE2. 均方根误差RMSE3. 平均绝对误差MAE4. R^2^5. 调整后R^2^ 1. 均方误差MSE 回归数据和原始数据误差的平方和/原始数据个数平方的原因&#xff1a;不平方正负误差会抵消&#xff0c;对大误差更为敏感&#xff0c;在一些场景下更能凸显出模型预测…

41.【C语言之外】聊聊Cheat Engine官方教程步骤6的思考

0.看前须知 有一定指针概念的基础 推荐阅读前几篇博文&#xff1a; 19.【C语言】指针&#xff08;重难点&#xff09;&#xff08;A&#xff09; 37.【C语言】指针&#xff08;重难点&#xff09;&#xff08;B&#xff09; 38.【C语言】指针&#xff08;重难点&#xff09…

【python】模块包

前言 模块化是python中的重要知识。随着我们接触的工程项目变得越来越大时&#xff0c;就需要把我们的运行代码进行拆解以便我们检查和项目的推进。有些时候&#xff0c;几个程序都需要同一个功能&#xff0c;那python就提供一种方法&#xff0c;把需要重复利用的代码放在同一…

Spring Boot 3.x Web MVC实战:实现流缓存的request

上一节《Spring Boot 3.x Filter实战&#xff1a;记录请求日志》实践最后遇到了request对象的流不可重复读的问题&#xff0c;本小节我们将通过流数据缓存以及流的装饰器模式来解决这个问题。如果觉得对你有帮助&#xff0c;记得点赞收藏&#xff0c;关注小卷&#xff0c;后续更…

Linux部署MySQL8.0

目录 一、部署前准备1.1、查看系统版本和位数&#xff08;32位或64位&#xff09;1.2、下载对应安装包 二、开始部署1、将安装包解压并且移动到目标安装目录2、准备MySQL数据和日志等存储文件夹3、准备MySQL配置文件 my.cnf4、创建mysql单独用户组和用户&#xff0c;将安装目录…

<数据集>灭火器识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;3262张 标注数量(xml文件个数)&#xff1a;3262 标注数量(txt文件个数)&#xff1a;3262 标注类别数&#xff1a;1 标注类别名称&#xff1a;[extinguisher] 使用标注工具&#xff1a;labelImg 标注规则&#xf…

无人机培训机构推广运营理论技术

一、市场定位与品牌建设 在无人机培训行业的激烈竞争中&#xff0c;精准的市场定位是成功的第一步。首先&#xff0c;需明确目标学员群体&#xff0c;如航拍爱好者、农业植保服务者、应急救援人员或专业无人机操作员等。基于目标群体的需求&#xff0c;构建差异化的品牌形象。…

FlexBV电路查看软件

FlexBV - Macbook, iPhone, PC/Laptop & Electronics BoardViewer with PDF Cross Referencing 免费。 支持tvw&#xff0c;cad格式。 支持Windows,Linux,Mac。 而且我发现cad格式是文本的&#xff01;意味着可以自由编辑&#xff01;

git拉取代码出现“remote: The project you were looking for could not be found.”错误分析

git拉取代码出现“remote: The project you were looking for could not be found.”错误分析 如果输入的远程地址正确&#xff0c;那么极大可能是用户未登录或多个用户登录无法正确获取你想要的用户&#xff0c;如下图所示&#xff0c; 由于之前有同事在我电脑登录git账号&a…

leetcode 103.二叉树的锯齿形层序遍历

1.题目要求: 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类推&#xff0c;层与层之间交替进行&#xff09;。2.做题思路:由题我们可以判断&#xff0c;树中每到偶数…

spring过滤器和拦截器的区别

1出身不同。 过滤器来自servlet&#xff0c;拦截器来自spring框架。 2触发时机 不同请求的执行顺序是&#xff1a;请求进入容器 > 进入过滤器 > 进入 Servlet > 进入拦截器 > 执行控制器 过滤器先执行&#xff0c;会在servlet请求之前和相应之后进行处理。 拦…

写一个Vue2和vue3的自定义指令(以复制指定作为示例)

文章目录 一、自定义指令是什么&#xff1f;二、自定义指令有啥用&#xff1f;三、自定义指令怎么用&#xff1f;1.自定义指令的参数2.自定义指令的钩子函数&#xff08;1&#xff09;五个钩子函数的说明&#xff08;2&#xff09;钩子函数的参数(主要参数&#xff1a;el和valu…

【活动预告】研讨会+开源集市,IoTDB “登录” GOTC 2024!

由开源中国与上海浦东软件园联合举办的 GOTC 2024 即将开幕&#xff01;本次大会结合 “GOTC&#xff08;全球开源技术峰会&#xff09;” 与 “GOGC&#xff08;全球开源极客嘉年华&#xff09;”&#xff0c;将集结全球范围内对开源技术充满热情的开发者、社区成员、创业者、…

Oracle是如何保证数据不丢的

上一篇文章给大家梳理了一条更新语句在Oracle数据库中是如何执行的&#xff0c;我们也提到只要更新记录成功写入到在线重做日志文件&#xff0c;Oracle就能保证数据不会丢失。同时也向大家解释了&#xff0c;其实这个时候数据并没有写入到数据文件&#xff0c;因此这个时候仍然…

为什么不用postman做自动化

面试的时候被问到&#xff1a;为什么不用postman做自动化 打开postman&#xff0c;看到用例集管理、API 管理、环境管理这三个功能&#xff0c;用户体验感算得上品牌等级了 为什么不用呢&#xff0c;文心一言给了一些答案 不适合大规模自动化测试&#xff1a;Postman 主要是为…

React 后台管理项目 入门项目 简洁清晰保姆级内容讲解

序章 React Hook的后台管理项目&#xff0c;从0到1搭建&#xff0c;内容非常丰富涵盖项目搭建、路由配置、用户鉴权、首页报表、用户列表、前后端联调等功能&#xff0c;推荐指数&#xff1a;5颗星&#xff01; 视频学习链接: React 通用后台管理-零基础从0到1详细的入门保姆…

数据结构(5.5_3)——并查集的进一步优化

Find操作的优化(压缩路径) 压缩路径——Find操作&#xff0c;先找到根节点&#xff0c;再将查找路径上所有结点都挂到根结点下 代码&#xff1a; //Find "查"操作优化&#xff0c;先找到根节点&#xff0c;再进行"路径压缩" int Find(int S[], int x) {…

50 mysql 的 “where 1 = 1“ 的优化处理

前言 问题是来自于 chinaunix 问题 ”mysql查询后面加 where 1 1 影响效率吗?” mysql 中在 java 代码中我们经常会使用到 ”where 1 1 and username ‘jerry’ ” 之类的条件 然后 我们这里 来看一下 “where 1 1” 的相关处理 where 条件在 select_lex, QUP_shared…

LeetCode面试150——14最长公共前缀

题目难度&#xff1a;简单 默认优化目标&#xff1a;最小化平均时间复杂度。 Python默认为Python3。 目录 1 题目描述 2 题目解析 3 算法原理及代码实现 3.1 横向扫描 3.2 纵向扫描 3.3 分治 3.4 二分查找 参考文献 1 题目描述 编写一个函数来查找字符串数组中的最长…