代码重构怕踩坑?基于 ASM+Maven 插件实现分析 Java 方法调用链(附完整代码)

代码重构怕踩坑?基于 ASM+Maven 插件实现分析 Java 方法调用链(附完整代码)

本文基于 ASM 字节码解析开发的 Maven 调用链分析插件,实现无侵入式的类方法调用链分析,无需运行业务代码,直接解析编译后的 .class 文件,快速定位指定类的方法调用关系,结合其「包过滤、循环调用检测、树形可视化、字节码级无侵入」等特性,适用于以下场景:

  • 老旧代码重构:解析调用链、标记循环调用,明确修改边界
  • 大型项目依赖梳理:梳理模块间方法依赖,定位关键调用节点
  • 死代码识别:检测无调用的方法,辅助代码瘦身
  • 故障排查:快速追溯异常方法的上下游调用链
  • 接口变更评估:识别方法的调用方,评估变更影响范围
  • 代码合规审计:检查核心类是否调用违规方法 / 第三方包
  • 性能瓶颈定位:识别调用层级过深(如超过 5 层)、循环调用等性能问题

该教程全程实操导向,所有代码均可直接复制运行,每个步骤都附带详细的字节码解析逻辑和插件开发细节说明。哪怕你是 Maven 插件开发新手,也能跟着步骤快速落地这款工具,让代码重构的依赖梳理环节效率翻倍、少踩坑!

项目源码地址call-chain-analyzer-maven-plugin(可直接克隆源码运行、调试或二次开发)

一、前置准备

1.1 开发环境

  • JDK 21+(ASM 对 JDK 8 兼容性最佳,也是企业级项目主流版本)
  • Maven 3.8+(插件开发基础依赖)
  • IDE(IntelliJ IDEA/Eclipse,推荐 IDEA 自带 Maven 插件调试功能)

1.2 ASM 核心认知

ASM 是轻量级字节码操作框架,核心通过「读取 - 遍历 - 解析」三步识别方法调用:

  • ClassReader:读取 .class 文件的字节码流;
  • ClassVisitor:遍历类结构(类名、方法、字段);
  • MethodVisitor:遍历方法内的指令,识别 invokevirtual/invokespecial/invokestatic/invokeinterface 等方法调用指令。

二、插件核心原理

插件整体执行逻辑:

  1. 遍历指定目录下的所有 .class 文件;
  2. 对每个 .class,通过 ASM 解析类内所有方法及方法调用指令;
  3. 构建「调用方 → 被调用方」的映射关系,存储调用链;
  4. 对调用链做过滤、循环检测、可视化输出。

扫描编译目录下的 .class 文件

ASM 解析 .class 内所有方法及方法调用指令

构建 调用方-被调用方 的映射关系

调用链筛选目标类的调用关系

可视化输出

三、Maven 插件开发

3.1 初始化 Maven 插件项目

方式一:用 Archetype 创建插件项目
    • 名称:填写项目名(如示例中的call-chain-analyzer-maven-plugin
    • JDK:选择项目使用的 JDK 版本(如示例中的 Oracle OpenJDK 21)
    • Archetype:选择 org.apache.maven.archetypes:maven-archetype-plugin(Maven 官方提供的插件项目模板)

点击右下角的 “创建” 按钮,Maven 会自动完成插件项目初始化,项目结构如下:

image-20260113164221066

在左侧菜单中点击 Maven Archetype,填写项目基本信息:

image-20260113163841280

打开 IDEA(或其他 IDE),点击 “新建项目”:

image-20260113164457979
方式二:创建普通 Java 项目
    • 名称:填写项目名(如示例中的call-chain-analyzer-maven-plugin
    • JDK:选择项目使用的 JDK 版本(如示例中的 Oracle OpenJDK 21)
    • 构建系统:勾选 “Maven”
  1. 执行 mvn clean compile 验证依赖是否正常(无报错即成功)。

在 pom.xml 中添加 Maven 插件的核心配置:

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><!-- 1. 项目基本信息 --><groupId>你的包名(如com.example)</groupId><artifactId>插件名(如call-chain-analyzer-maven-plugin)</artifactId><version>1.0-SNAPSHOT</version><packaging>maven-plugin</packaging><!-- 关键:打包类型为maven-plugin --><!-- 2. 依赖:Maven插件核心API --><dependencies><dependency><groupId>org.apache.maven</groupId><artifactId>maven-plugin-api</artifactId><version>3.9.11</version><scope>provided</scope></dependency><dependency><groupId>org.apache.maven</groupId><artifactId>maven-core</artifactId><version>3.9.11</version><scope>provided</scope></dependency><dependency><groupId>org.apache.maven.plugin-tools</groupId><artifactId>maven-plugin-annotations</artifactId><version>3.15.1</version><scope>provided</scope></dependency></dependencies><!-- 3. 插件配置:编译注解 --><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-plugin-plugin</artifactId><version>3.15.1</version><executions><execution><id>default-descriptor</id><phase>process-classes</phase></execution></executions><configuration><encoding>UTF-8</encoding></configuration></plugin></plugins></build></project>
🐖 <packaging>maven-plugin</packaging> 用于声明项目为 Maven 插件,是插件项目的核心标识,缺失则无法被 Maven 识别

点击右下角的 “创建” 按钮,生成一个空的 Java 项目。

image-20260113165811698

在左侧菜单中点击 “Java”,填写项目基本信息:

image-20260113165223050

打开 IDEA → 选择 “新建项目”:

image-20260113164457979

3.2 定义数据模型(MethodCallInfo)

创建 pojo/MethodCallInfo.java,用于存储单次调用 “调用方” 和 “被调用方” 的信息,是整个插件的数据载体:

/** * 方法调用信息模型:存储类方法单次调用的「调用方-被调用方」关系 */@Data@AllArgsConstructorpublicclassMethodCallInfo{// 调用方类全限定名(如 com.example.demo.UserService)privateString callerClass;// 调用方方法名(如 getUser)privateString callerMethod;// 调用方方法描述符privateString callerMethodDesc;// 被调用方类全限定名privateString calleeClass;// 被调用方方法名privateString calleeMethod;// 被调用方方法描述符privateString calleeMethodDesc;// 格式化输出调用方方法publicStringgetCallerMethodFull(){return callerClass +"#"+ callerMethod + callerMethodDesc;}// 格式化输出被调用方方法publicStringgetCalleeMethodFull(){return calleeClass +"#"+ calleeMethod + calleeMethodDesc;}}

添加构建配置,显式声明 Lombok 为注解处理器:

<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>${maven-compiler-plugin.version}</version><configuration><source>21</source><target>21</target><!-- 显式声明 Lombok 为注解处理器 --><annotationProcessorPaths><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.38</version></path></annotationProcessorPaths></configuration></plugin>

在 pom.xml 中添加 Lombok 依赖:

<!-- Lombok 依赖 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.38</version><scope>provided</scope></dependency>

3.3 实现 ASM 解析逻辑

类级别访问器(ClassCallChainVisitor)

创建 ClassCallChainVisitor 类,继承 ASM 的ClassVisitor,负责类层面的遍历与过滤,为符合条件的方法创建方法级访问器:

/** * 类级别的 ASM 解析器:遍历类结构,处理每个方法 */publicclassClassCallVisitorextendsClassVisitor{privatefinalSet<MethodCallInfo> methodCallSet;privateString currentClass;privatefinalList<String> scanPackages;privatefinalList<String> excludeScanPackages;publicClassCallVisitor(int api,Set<MethodCallInfo> methodCallSet,List<String> scanPackages,List<String> excludeScanPackages){super(api);this.methodCallSet = methodCallSet;this.scanPackages = scanPackages;this.excludeScanPackages = excludeScanPackages;}publicClassCallChainVisitor(ClassVisitor classVisitor,CallChainContext context){super(Opcodes.ASM9, classVisitor);this.context = context;}@Overridepublicvoidvisit(int version,int access,String name,String signature,String superName,String[] interfaces){// 将 ASM 格式类名(com/example/OrderService)转为全限定名(com.example.OrderService)this.currentClass = name.replace("/",".");super.visit(version, access, name, signature, superName, interfaces);}@OverridepublicMethodVisitorvisitMethod(int access,String name,String descriptor,String signature,String[] exceptions){// 跳过排除的包if(excludeScanPackages.stream().anyMatch(ignorePkg ->this.currentClass.startsWith(ignorePkg.trim()+"."))){returnsuper.visitMethod(access, name, desc, signature, exceptions);}MethodVisitor methodVisitor =super.visitMethod(access, name, descriptor, signature, exceptions);// 跳过抽象方法、native 方法(无字节码,无法解析调用)if((access &Opcodes.ACC_ABSTRACT)!=0||(access &Opcodes.ACC_NATIVE)!=0){return methodVisitor;}// 解析方法内的调用指令returnnewMethodCallVisitor(Opcodes.ASM9, methodVisitor, currentClass, name, context);}}
方法级别访问器(MethodCallVisitor)

继承 ASM 的 MethodVisitor,负责方法内调用指令的解析与过滤,最终仅收集满足以下所有条件的方法调用关系:

  1. 调用方 / 被调用方均不属于 “忽略包”;
  2. 调用方不是构造方法 / 静态初始化方法;
  3. 被调用方不是构造方法 / 静态初始化方法;
  4. 若配置了 “扫描包”,被调用方必须属于 “扫描包” 范围(未配置则无此限制)。
/** * 方法级别的 ASM 解析器:识别方法内的调用指令,构建调用链 */publicclassMethodCallVisitorextendsMethodVisitor{// 调用方类名(全限定名)privatefinalString callerClass;// 调用方方法名privatefinalString callerMethod;// 全局上下文privatefinalCallChainContext context;publicMethodCallVisitor(int api,MethodVisitor methodVisitor,String callerClass,String callerMethod,CallChainContext context){super(api, methodVisitor);this.callerClass = callerClass;this.callerMethod = callerMethod;this.context = context;}/** * 核心方法:解析方法调用指令 * @param opcode 调用指令类型(invokevirtual/invokestatic 等) * @param owner 被调用类的 ASM 格式名称(如 com/example/OrderDao) * @param name 被调用方法名 * @param descriptor 方法描述符(如 (Ljava/lang/String;)V) * @param isInterface 是否是接口方法 */@OverridepublicvoidvisitMethodInsn(int opcode,String owner,String name,String descriptor,boolean isInterface){super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);// 1. 转换被调用类名为全限定名String calleeClass = owner.replace("/",".");// 2. 包过滤:只处理指定包下的被调用类Set<String> includePackages = context.getIncludePackages();if(includePackages !=null&&!includePackages.isEmpty()){boolean match = includePackages.stream().anyMatch(pkg -> calleeClass.startsWith(pkg));if(!match){return;}}// 3. 跳过构造方法/静态初始化方法(可选,根据业务需求调整)if("<init>".equals(name)||"<clinit>".equals(name)){return;}// 4. 存储调用关系MethodCall methodCall =newMethodCall(callerClass, callerMethod, calleeClass, name); context.addMethodCall(methodCall);}}

3.4 开发插件入口 Mojo 类

Mojo 是 Maven 插件的核心执行入口类(Mojo = Maven Old Java Object,等价于插件的 “main 类”),所有插件逻辑都围绕这个类展开。

创建 AnalyzeCallChainMojo 类

创建 AnalyzeCallChainMojo 类整合 「遍历.class 文件 → 触发 ASM 解析 → 输出结果」全流程,代码如下:

/** * Maven插件核心Mojo:方法调用链分析 * Goal: analyze(调用命令:mvn call-chain:analyze) */@Mojo( name ="analyze", threadSafe =true, defaultPhase =LifecyclePhase.COMPILE, requiresDependencyResolution =ResolutionScope.COMPILE )@Execute(phase =LifecyclePhase.COMPILE)publicclassAnalyzeCallChainMojoextendsAbstractMojo{// ========== Maven注入参数 ==========/** Maven项目对象(自动注入) */@Parameter(defaultValue ="${project}", readonly =true)protectedMavenProject project;/** 扫描的.class文件根目录(默认:target/classes) */@Parameter(defaultValue ="${project.build.outputDirectory}", required =true)privateString classRootDir;/** 需扫描的包名列表 */@Parameter(name ="scanPackages", property ="scanPackages")privateList<String> scanPackages =newArrayList<>();/** 排除扫描的包名列表 */@Parameter(name ="excludeScanPackages", property ="excludeScanPackages")privateList<String> excludeScanPackages =newArrayList<>();/** 目标分析类全限定名(必填) */@Parameter(name ="targetClass", property ="targetClass", required =true)privateString targetClass;// 核心执行方法(后续实现)@Overridepublicvoidexecute()throwsMojoExecutionException,MojoFailureException{......}}

该类继承 AbstractMojo(Maven 插件基类),@Mojo 是 Maven 插件开发的核心注解,每个参数都对应插件的核心行为配置:

  • name = "analyze":插件的 Goal 名称,是调用插件的核心标识,调用插件时执行命令 mvn call-chain:analyze
  • threadSafe = true:标记插件是否线程安全,设置为true时,避免 Maven 在并行构建(如mvn -T 4 clean compile)时报错。
  • defaultPhase = LifecyclePhase.COMPILE:插件默认绑定的 Maven 生命周期阶段,因为插件需要解析编译后的.class 文件,所以必须在编译完成后执行。
  • requiresDependencyResolution = ResolutionScope.COMPILE:指定插件执行前需要解析的依赖范围,Maven 会先解析项目compile范围的依赖(如 ASM、Maven Plugin API),再执行插件;确保插件运行时依赖已就绪,避免ClassNotFound异常。

@Execute 注解是对 @Mojo 的补充,用于强制 Maven 执行插件之前,会先执行COMPILE阶段(编译项目生成.class 文件),避免因跳过编译(如mvn call-chain:analyze -Dmaven.compile.skip=true)导致插件找不到.class 文件的问题;

@Execute(phase = LifecyclePhase.COMPILE) 

@Parameter 是 Maven Plugin API 提供的核心注解,Maven 框架会自动 将 pom.xml 中中配置的插件参数 注入到 Mojo 类的成员变量中。

实现核心执行流程(execute)

execute() 是插件的入口,流程如下:

@Overridepublicvoidexecute()throwsMojoExecutionException,MojoFailureException{// 1. 初始化参数,避免空集合NPE scanPackages =Optional.ofNullable(scanPackages).orElseGet(ArrayList::new); excludeScanPackages =Optional.ofNullable(excludeScanPackages).orElseGet(ArrayList::new);// 2. 校验扫描目录合法性validateClassRootDir();// 3. 打印扫描配置(便于调试)logScanConfig();// 4. 解析所有.class文件,提取调用关系Set<MethodCallInfo> allMethodCallInfos =parseAllClassFiles();// 5. 构建调用关系映射(调用方→被调用方列表)Map<String,List<String>> methodCallRelationMap =buildMethodCallRelationMap(allMethodCallInfos);// 6. 筛选目标类的调用关系Map<String,List<String>> targetClassCallRelationMap =filterTargetClassCallRelations(methodCallRelationMap);// 7. 输出树形调用链结果printCallChainResult(targetClass, targetClassCallRelationMap);}
扫面 class 文件(traverseClassFiles + scanClassFilesRecursive)

递归遍历 classRootDir 目录下所有 .class 文件,收集文件路径:

/** * 递归遍历.class文件,返回所有class文件路径 */privateList<String>traverseClassFiles(String dirPath)throwsMojoExecutionException{File scanDir =newFile(dirPath);if(!scanDir.exists()||!scanDir.isDirectory()){thrownewMojoExecutionException(String.format("扫描路径非法: %s", dirPath));}List<String> classFilePaths =newArrayList<>();scanClassFilesRecursive(scanDir, classFilePaths);returnCollections.unmodifiableList(classFilePaths);}/** * 递归扫描.class文件 */privatevoidscanClassFilesRecursive(File file,List<String> result){if(file.isDirectory()){File[] childFiles = file.listFiles();if(childFiles ==null){getLog().warn(String.format("无法访问目录: %s", file.getAbsolutePath()));return;}for(File child : childFiles){scanClassFilesRecursive(child, result);}}elseif(file.getName().endsWith(CLASS_FILE_SUFFIX)){ result.add(file.getAbsolutePath());}}
解析 class 文件(parseClassFile)

通过 ASM 的 ClassReader 和自定义 ClassCallVisitor 解析 .class 文件,提取方法调用关系:

/** * 解析.class文件,提取方法调用关系 */privateSet<MethodCallInfo>parseClassFile(String classFilePath,List<String> scanPackages,List<String> excludeScanPackages)throwsMojoExecutionException{try(FileInputStream fis =newFileInputStream(classFilePath)){ClassReader classReader =newClassReader(fis);Set<MethodCallInfo> methodCallSet =newHashSet<>();// 自定义ClassVisitor,解析类结构并捕获调用关系ClassCallVisitor classVisitor =newClassCallVisitor(ASM_API_VERSION, methodCallSet, scanPackages, excludeScanPackages);// 执行解析:跳过调试信息和栈帧,提升效率 classReader.accept(classVisitor,ClassReader.SKIP_DEBUG |ClassReader.SKIP_FRAMES);return methodCallSet;}catch(IOException e){String errorMsg =String.format("解析类文件失败: %s", classFilePath);thrownewMojoExecutionException(errorMsg, e);}}
🐖 ClassCallVisitor 是自定义的 ASM Visitor,核心逻辑是遍历方法指令,捕获 INVOKEVIRTUAL/INVOKESTATIC 等调用指令,封装为 MethodCallInfo
构建调用关系(buildMethodCallRelationMap)

根据全量 MethodCallInfo 构建「调用方方法全限定名→被调用方列表」的映射:

/** * 构建调用关系映射(调用方→被调用方列表) */publicstaticMap<String,List<String>>buildMethodCallRelationMap(Collection<MethodCallInfo> methodCallInfos){if(Objects.isNull(methodCallInfos)|| methodCallInfos.isEmpty()){returnCollections.emptyMap();}return methodCallInfos.stream().distinct()// 去重重复的调用关系.collect(Collectors.groupingBy(MethodCallInfo::getCallerMethodFull,// 分组键:调用方方法全限定名Collectors.mapping(MethodCallInfo::getCalleeMethodFull,Collectors.toList())// 映射值:被调用方列表));}
筛选目标类的调用关系(filterTargetClassCallRelations)

根据 buildMethodCallRelationMap() 构建的调用关系筛查出目标类中所有方法的调用关系:

/** * 筛选目标类的调用关系(按类名前缀过滤) */privateMap<String,List<String>>filterTargetClassCallRelations(Map<String,List<String>> methodCallRelationMap){if(Objects.isNull(methodCallRelationMap)|| methodCallRelationMap.isEmpty()){returnCollections.emptyMap();}String targetClassPrefix = targetClass + METHOD_FULL_NAME_SEPARATOR;// 筛选+排序return methodCallRelationMap.entrySet().stream().filter(entry -> entry.getKey().startsWith(targetClassPrefix)).collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().stream().sorted().toList(),(v1, v2)-> v1,TreeMap::new));}
树形输出与循环检测(printMethodCallChain)

递归打印树形调用链,并通过visitedMethods集合检测循环调用:

/** * 递归打印方法调用链 */privatevoidprintMethodCallChain(String currentMethod,List<String> calleeList,String prefix,boolean isLastNode,Map<String,List<String>> relationMap,Set<String> visitedMethods){// 空值保护(避免NPE)if(Objects.isNull(currentMethod)|| currentMethod.trim().isEmpty()){return;}// ========== 循环调用检测(传递引用+回溯) ==========if(visitedMethods.contains(currentMethod)){String symbol = isLastNode ? TREE_LAST_NODE_SYMBOL : TREE_NODE_SYMBOL;getLog().info(String.format("%s%s%s%s", prefix, symbol, currentMethod," 「循环调用」"));return;}// 标记当前方法为已访问 visitedMethods.add(currentMethod);// ========== 打印当前节点 ==========String nodeSymbol = isLastNode ? TREE_LAST_NODE_SYMBOL : TREE_NODE_SYMBOL;getLog().info(String.format("%s%s%s", prefix, nodeSymbol, currentMethod));// ========== 处理子节点 ==========List<String> parsedCalleeList =Optional.ofNullable(calleeList).map(ArrayList::new)// 避免修改原列表.orElse(newArrayList<>()).stream().filter(Objects::nonNull)// 过滤null的被调用方.sorted(Comparator.naturalOrder()).toList();if(parsedCalleeList.isEmpty()){// 回溯:移除当前方法,避免影响兄弟节点检测 visitedMethods.remove(currentMethod);return;}// 构建子节点前缀String childPrefix = prefix +(isLastNode ? TREE_LAST_CHILD_PREFIX : TREE_CHILD_PREFIX);// 遍历子节点递归打印for(int i =0; i < parsedCalleeList.size(); i++){String calleeMethod = parsedCalleeList.get(i);boolean isLastChild =(i == parsedCalleeList.size()-1);List<String> nextCalleeList =Optional.ofNullable(relationMap.get(calleeMethod)).orElse(Collections.emptyList());// 传递同一个visitedMethods引用(核心:实现深层循环检测)printMethodCallChain(calleeMethod, nextCalleeList, childPrefix, isLastChild, relationMap, visitedMethods);}// ========== 回溯:移除当前方法,完成循环检测闭环 ========== visitedMethods.remove(currentMethod);}

核心逻辑

  • 通过visitedMethods集合记录已访问的方法,检测到循环调用时标记并终止递归;
  • 递归遍历子节点时,通过prefix控制树形缩进(如├──/└──),保证输出格式清晰;
  • 回溯移除当前方法,确保兄弟节点的循环检测不受影响。

四、插件测试与使用

4.1 安装插件到本地仓库

在插件项目根目录执行:

mvn clean install

4.2 在业务项目中引入插件

在需要解析调用链的业务项目 pom.xml 中添加插件配置:

<build><plugins><plugin><groupId>com.shijie.plugin</groupId><artifactId>call-chain-maven-plugin</artifactId><version>1.0.0</version><configuration><!-- 必填:目标分析类全限定名 --><targetClass>com.shijie.service.UserService</targetClass><!-- 可选:扫描的包名列表 --><scanPackages><scanPackage>com.shijie.service</scanPackage><scanPackage>com.shijie.dao</scanPackage></scanPackages><!-- 可选:排除的包名列表 --><excludeScanPackages><excludeScanPackage>com.shijie.test</excludeScanPackage></excludeScanPackages></configuration></plugin></plugins></build>

4.3 执行插件

打开终端执行以下命令:

mvn call-chain-analyzer:analyze -f pom.xml 

或在 IDEA 右侧的 Maven 面板「插件」中找到该插件,双击执行:

image-20260116164238646

插件会在控制台输出目标类的方法调用链,示例如下:

image-20260116163944744

Read more

GitHub 寻宝指南:四种高效发现优质开源项目的方法

GitHub 寻宝指南:四种高效发现优质开源项目的方法

文章目录 * 引言:从“收藏家”到“寻宝猎人”,升级你的 GitHub 发现技能 * 方法一:利用 GitHub 自身的功能(基础) * 1. GitHub Explore (探索) * 2. GitHub 高级搜索 * 方法二:借助社区整理的精选列表(高效) * 1. Awesome Lists * 2. 关注领域专家 * 方法三:善用第三方辅助网站(多维) * 1. Star History * 2. LibHunt * 方法四:拥抱 AI 进行智能搜索(前沿) * GitHub 的 AI 搜索 (Ask Copilot) * 实战演示: * 结语:

By Ne0inhk
IDEA和GIT实现cherry pick拣选部分变更到新分支

IDEA和GIT实现cherry pick拣选部分变更到新分支

前言 在工作中,当你出现一些情况,需要将一个分支的部分变动提取出来,只需要更新提取出来的情况就需要用到当前文章提到的git的功能 并且正常情况下,你工作是没有权限直接合并到生产分支,并且前一个需求还没合并到生产分支,如果你想要复用这部分的改动逻辑,那么就需要用到这个操作,也叫cherry-pick拣选 核心作用 核心作用是将一个或多个已有的提交(commit)复制到当前所在的分支上。 你可以把它想象成在一棵果树上,只挑选(pick)几颗你想要的,而不是把整根树枝都搬过来。 为什么需要它? 主要用于那些不需要合并整个分支,而只需要其中几个特定提交的情况。 将修复补丁应用到多个分支 这是最常见、最经典的场景。假设你有一个bugfix分支上修复了一个关键 bug,这个提交的 hash 是 a1b2c3d。现在你需要将这个修复同时应用到: main 分支(生产环境) develop 分支(开发环境) 可能还有旧的维护分支 v1.x 你不需要将整个 bugfix 分支合并到这些分支上,只需要在每个目标分支上执行: git cherry-pick a1b2c3d 意外在错

By Ne0inhk
夜莺-Nightingale-开源云原生监控分析系统部署 Prometheus 作为时序库使用(配置多数据源)

夜莺-Nightingale-开源云原生监控分析系统部署 Prometheus 作为时序库使用(配置多数据源)

夜莺-Nightingale-开源云原生监控分析系统部署 Prometheus 作为时序库使用(配置多数据源) * 一、前言 * 二、Prometheus安装步骤 * 1. 下载并安装Prometheus * 2. 关键配置:启用Remote Write接收器 * 3. 创建Systemd服务 * 4. 启动并验证服务 * 三、验证Remote Write功能 * 四、修改夜莺配置文件对接时序库 * 1. 再增加一个Prometheus 时序库。 * 2. 重启夜莺监控(N9E)服务: * 3. 夜莺数据源管理新增数据源 * 五、常见问题解决 * 1. 夜莺转发数据时报404错误 * 2. 权限问题 * 3. 端口冲突 * 六、总结 * 参考链接 💐The Begin💐点点关注,收藏不迷路💐 一、前言 Prometheus是一款开源的监控系统和时序数据库,

By Ne0inhk
今日AI榜单速览(GitHub Trending AI Top3)

今日AI榜单速览(GitHub Trending AI Top3)

🔥 个人主页:杨利杰YJlio❄️ 个人专栏:《Sysinternals实战教程》《Windows PowerShell 实战》《WINDOWS教程》《IOS教程》《微信助手》《锤子助手》《Python》《Kali Linux》《那些年未解决的Windows疑难杂症》🌟 让复杂的事情更简单,让重复的工作自动化 今日AI热榜 * 1 1 今日榜单速览(GitHub Trending AI Top3) * 2 2 ruvnet / RuView:WiFi DensePose 的“无线透视”路线 * 2 我的一句话总结 * 2 为什么今天它能冲到第一? * 2 图:它的可视化界面长这样(很直观) * 2 我如何最快验证(不折腾工具链) * 3 3 K-Dense-AI / claude-scientific-skills:给

By Ne0inhk