Java agent
Java Agent是一种特殊的Java程序,它可以在JVM启动时或运行时动态加载,用于监控和修改其他Java应用程序的行为。通过Java Agent,开发者可以在不修改目标应用程序源码的情况下,动态地插入功能,如性能分析、日志记录、代码覆盖率测试、热更新等。
一、Java Agent的主要功能
1、监控类的加载:在类加载到JVM时,可以对类进行操作,例如记录日志、统计加载时间。2、修改类的字节码:在类被加载时,可以修改其字节码,例如插入调试代码、改变类的方法行为。3、重新定义已加载的类:在程序运行时,可以重新定义已经加载的类(需要JVM支持)。4、监控和获取对象的内存信息:可以获取对象的大小,用于内存分析。
二、Java Agent的实现方式
1、JVM启动时加载:在启动Java应用程序时,通过-javaagent参数加载。这种方式会在目标应用启动前执行,可以拦截所有类的加载过程2、运行时动态附加:在应用程序已经启动的情况下,通过附加到目标JVM进程来加载。这需要Java提供的Attach API
三、Java Agent的历史背景和具体应用场景
Java Agent功能是JDK1.5引入的,通过java.lang.instrument接口实现。这个接口基于JVMTI(Java Virtual Machine Tool Interface)机制,允许开发者构建一个独立于应用程序的代理程序,用于监测和协助运行在JVM上的程序
四、示例
示例1:静态加载方式(启动执行)
工程1 (agent)
步骤1:pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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><groupId>org.example</groupId><artifactId>demo-javaagent</artifactId><version>1.0</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.25.0-GA</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.1</version><configuration><descriptorRefs><!--将应用的所有依赖包都打到jar包中。如果依赖的是 jar 包,jar 包会被解压开,平铺到最终的 uber-jar 里去。输出格式为 jar--><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><!-- 设置manifest配置文件--><manifestEntries><!--Premain-Class: 代表 Agent 静态加载时会调用的类全路径名。--><Premain-Class>demo.MethodAgentMain</Premain-Class><!--Agent-Class: 代表 Agent 动态加载时会调用的类全路径名。--><Agent-Class>demo.MethodAgentMain</Agent-Class><!--Can-Redefine-Classes: 是否可进行类定义。--><Can-Redefine-Classes>true</Can-Redefine-Classes><!--Can-Retransform-Classes: 是否可进行类转换。--><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration><executions><execution><!--绑定到package生命周期阶段上--><phase>package</phase><goals><!--绑定到package生命周期阶段上--><goal>single</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>${maven.compiler.source}</source><target>${maven.compiler.target}</target></configuration></plugin></plugins></build>
</project>
步骤2: 创建 premain 方法,方法的主要功能是修改 App setName() 方法体
package demo;import java.lang.instrument.Instrumentation;public class MethodAgentMain {public static void premain(String args, Instrumentation inst) {MyTransformer tran = new MyTransformer();inst.addTransformer(tran);}
}
package demo;import javassist.*;import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;public class MyTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if ("org/example/App".equals(className)) {try {// 从ClassPool获得CtClass对象final ClassPool classPool = ClassPool.getDefault();// 尝试添加额外的类路径(如果需要)classPool.appendClassPath(new ClassClassPath(this.getClass()));classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));final CtClass clazz = classPool.get("org.example.App");CtMethod convertToAbbr = clazz.getDeclaredMethod("setName");String methodBody = "{\n" +" this.name = \"ccc\" + \" aaa\";\n" +" }";convertToAbbr.setBody(methodBody);byte[] byteCode = clazz.toBytecode();clazz.detach();return byteCode;} catch (NotFoundException | CannotCompileException | IOException e) {e.printStackTrace();}}System.out.println(className);return classfileBuffer;}}
步骤3: 编译打包
执行 mvn clean package 编译打包,最终打包生成了 agent jar 包,结果示例:
工程2(主工程)
package org.example;public class App {private int code;private String name;public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "App{" +"code=" + code +", name='" + name + '\'' +'}';}
}
package org.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {App app = new App();app.setName("a");app.setCode(123);System.out.println(app);SpringApplication.run(DemoApplication.class, args);}
}
启动 javaagent
java -javaagent:demo-javaagent-1.0-jar-with-dependencies.jar -jar demo-1.0.0-SNAPSHOT.jar > output.log
查看运行结果:
App{code=123, name='ccc aaa'}
name 属性被成功修改。
示例2:动态加载方式(启动之后,接口调用触发)
在接口调用时触发某些行为,可以使用 Java Agent 来改变接口方法调用的行为
工程1 (agent)
步骤1:pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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><groupId>org.example</groupId><artifactId>demo-javaagent</artifactId><version>1.0</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.25.0-GA</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.1</version><configuration><descriptorRefs><!--将应用的所有依赖包都打到jar包中。如果依赖的是 jar 包,jar 包会被解压开,平铺到最终的 uber-jar 里去。输出格式为 jar--><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><!-- 设置manifest配置文件--><manifestEntries><!--Premain-Class: 代表 Agent 静态加载时会调用的类全路径名。--><Premain-Class>demo.MethodAgentMain</Premain-Class><!--Agent-Class: 代表 Agent 动态加载时会调用的类全路径名。--><Agent-Class>demo.MethodAgentMain</Agent-Class><!--Can-Redefine-Classes: 是否可进行类定义。--><Can-Redefine-Classes>true</Can-Redefine-Classes><!--Can-Retransform-Classes: 是否可进行类转换。--><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration><executions><execution><!--绑定到package生命周期阶段上--><phase>package</phase><goals><!--绑定到package生命周期阶段上--><goal>single</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>${maven.compiler.source}</source><target>${maven.compiler.target}</target></configuration></plugin></plugins></build>
</project>
步骤2: 创建 premain 方法,方法的主要功能是修改方法体,在方法调用前后添加日志输出
package demo;import java.lang.instrument.Instrumentation;public class MethodAgentMain {public static void premain(String args, Instrumentation inst) {MyTransformer tran = new MyTransformer();inst.addTransformer(tran);}
}
package demo;import javassist.*;import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;public class MyTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if (className != null && className.startsWith("org/example/controller/")) {ClassPool pool = ClassPool.getDefault();// 尝试添加额外的类路径(如果需要)pool.appendClassPath(new ClassClassPath(this.getClass()));pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));CtClass ctClass;try {ctClass = pool.makeClass(new java.io.ByteArrayInputStream(classfileBuffer));for (CtBehavior method : ctClass.getDeclaredBehaviors()) {if (method.isEmpty() || method.getMethodInfo() == null) {continue;}// 修改方法体,在方法调用前后添加日志输出method.insertBefore("System.out.println(\"Before method call: \" + $sig);");method.insertAfter("System.out.println(\"After method call: \" + $sig);");}return ctClass.toBytecode();} catch (Exception e) {e.printStackTrace();}}return null;}}
步骤3: 编译打包
执行 mvn clean package 编译打包,最终打包生成了 agent jar 包,结果示例:
工程2(主工程)
一个普通的spring工程
package org.example.controller;import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @Description: TODO* @Author: Top* @Version: V1.0* @Date: 2020-01-15 15:03*/
@RestController
@RequestMapping("/api/{edition}")
public class ConsumerController {@Autowiredprivate Environment env;@GetMapping("/detail")@ResponseBodypublic String detail(String name) throws JsonProcessingException {return name;}
启动工程:
java -javaagent:demo-javaagent-1.0-jar-with-dependencies.jar -jar demo-1.0.0-SNAPSHOT.jar > output.log
执行结果:
Before method call: [Ljava.lang.Class;@bab9ac
consumer
After method call: [Ljava.lang.Class;@2756b30e
Java agent原理说明:
主流的JVM都提供了Instrumentation的实现,但是鉴于Instrumentation的特殊功能,并不适合直接提供在JDK的runtime里,而更适合出现在Java程序的外层,以上帝视角在合适的时机出现。
因此如果想使用Instrumentation功能,拿到Instrumentation实例,我们必须通过Java agent。
Java agent是一种特殊的Java程序(Jar文件),它是Instrumentation的客户端。与普通Java程序通过main方法启动不同,agent 并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过Instrumentation API与虚拟机交互。
Java agent与Instrumentation密不可分,二者也需要在一起使用。因为Instrumentation的实例会作为参数注入到Java agent的启动方法中。
Instrumentation是Java提供的JVM接口,该接口提供了一系列查看和操作Java类定义的方法,例如修改类的字节码、向 classLoader 的 classpath 下加入jar文件等。使得开发者可以通过Java语言来操作和监控JVM内部的一些状态,进而实现Java程序的监控分析,甚至实现一些特殊功能(如AOP、热部署)。
public interface Instrumentation {/*** 注册一个Transformer,从此之后的类加载都会被Transformer拦截。* Transformer可以直接对类的字节码byte[]进行修改*/void addTransformer(ClassFileTransformer transformer);/*** 对JVM已经加载的类重新触发类加载。使用的就是上面注册的Transformer。* retransformClasses可以修改方法体,但是不能变更方法签名、增加和删除方法/类的成员属性*/void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;/*** 获取一个对象的大小*/long getObjectSize(Object objectToSize);/*** 将一个jar加入到bootstrap classloader的 classpath里*/void appendToBootstrapClassLoaderSearch(JarFile jarfile);/*** 获取当前被JVM加载的所有类对象*/Class[] getAllLoadedClasses();
}
注意:
Instrumentation的局限性
在运行时,我们可以通过Instrumentation的redefineClasses方法进行类重定义,在redefineClasses方法上有一段注释需要特别注意:
java 代码解读复制代码 * The redefinition may change method bodies, the constant pool and attributes.* The redefinition must not add, remove or rename fields or methods, change the* signatures of methods, or change inheritance. These restrictions maybe be* lifted in future versions. The class file bytes are not checked, verified and installed* until after the transformations have been applied, if the resultant bytes are in* error this method will throw an exception.这里面提到,我们不可以增加、删除或者重命名字段和方法,改变方法的签名或者类的继承关系。认识到这一点很重要,当我们通过ASM获取到增强的字节码之后,如果增强后的字节码没有遵守这些规则,那么调用redefineClasses方法来进行类的重定义就会失败。
相关文章:

Java agent
Java Agent是一种特殊的Java程序,它可以在JVM启动时或运行时动态加载,用于监控和修改其他Java应用程序的行为。通过Java Agent,开发者可以在不修改目标应用程序源码的情况下,动态地插入功能,如性能分析、日志记录…...

Web无障碍
文章目录 🟢Web Accessibility-Web无障碍🟢一、Web Accessibility-Web1. web无障碍设计2. demo3.使用相关相关开源无障碍工具条(调用可能会根据网络有点慢) 如有其他更好方案,可以私信我哦✒️总结 🟢Web Accessibility-Web无障碍…...

概率基本概念 --- 离散型随机变量实例
条件概率&独立事件 随机变量 - 离散型随机变量 - 非离散型随机变量 连续型随机变量奇异性型随机变量 概率表示 概率分布函数概率密度函数概率质量函数全概率公式贝叶斯公式 概率计算 数学期望方差协方差 计算实例 假设有两个离散型随机变量X和Y,它们代…...

毕业项目推荐:基于yolov8/yolov5/yolo11的动物检测识别系统(python+卷积神经网络)
文章目录 概要一、整体资源介绍技术要点功能展示:功能1 支持单张图片识别功能2 支持遍历文件夹识别功能3 支持识别视频文件功能4 支持摄像头识别功能5 支持结果文件导出(xls格式)功能6 支持切换检测到的目标查看 二、数据集三、算法介绍1. YO…...
基于 WEB 开发的高校学籍管理系统设计与实现
标题:基于 WEB 开发的高校学籍管理系统设计与实现 内容:1.摘要 摘要:随着信息技术的不断发展,高校学籍管理系统的信息化建设已成为必然趋势。本文以高校学籍管理系统为研究对象,探讨了基于 WEB 开发的高校学籍管理系统的设计与实现。通过对系…...

阿里云发现后门webshell,怎么处理,怎么解决?
当收到如下阿里云通知邮件时,大部分管理员都会心里一惊吧!出现Webshell,大概是网站被入侵了。 尊敬的 xxxaliyun.com: 云盾云安全中心检测到您的服务器:47.108.x.xx(xx机)出现了紧急安全事件…...

HTB:Bank[WriteUP]
目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 提取出靶机TCP开放端口 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用curl对域名进行访问…...

如何用数字万用表测量是否漏电?
测量电气设备或线路是否漏电是确保安全的重要步骤。台式数字万用表(DMM)是一种常见的测试工具,它可以帮助我们检测和确认是否存在漏电现象。本文将详细介绍如何使用台式数字万用表进行漏电检测,包括准备工作、具体操作步骤和安全注…...

黑马跟学.苍穹外卖.Day04
黑马跟学.苍穹外卖.Day04 苍穹外卖-day04课程内容1. Redis入门1.1 Redis简介1.2 Redis下载与安装1.2.1 Redis下载1.2.2 Redis安装 1.3 Redis服务启动与停止1.3.1 服务启动命令1.3.2 客户端连接命令1.3.3 修改Redis配置文件1.3.4 Redis客户端图形工具 2. Redis数据类型2.1 五种常…...

uniapp使用scss mixin抽离css常用的公共样式
1、编写通用scss样式文件 // 通用 Flex Mixin mixin flex($direction: row, $justify: flex-start, $align: stretch, $wrap: nowrap) {display: flex;flex-direction: $direction;justify-content: $justify;align-items: $align;flex-wrap: $wrap; }// 水平居中 mixin flex-…...
用Python解决“A. Accounting”问题:完整教程与代码实现
引言 在这篇文章中,我们将深入探讨编程竞赛中的一道经典问题“A. Accounting”,并用Python实现一个高效的解决方案。本文将涵盖题目分析、算法设计和Python代码实现,以及代码的完整讲解和优化方法。 一、问题描述 在一个遥远的国家里&…...

FreeU: Free Lunch in Diffusion U-Net 笔记
FreeU: Free Lunch in Diffusion U-Net 摘要 作者研究了 U-Net 架构对去噪过程的关键贡献,并发现其主干部分主要在去噪方面发挥作用,而其跳跃连接主要是向解码器模块引入高频特征,这使得网络忽略了主干部分的语义信息。基于这一发现&#…...

腾讯云AI代码助手编程挑战赛-古诗词学习
一、作品介绍 在科技与文化深度交融的当下,“腾讯云 AI 代码助手编程挑战赛 - 每日古诗词” 宛如一颗璀璨的新星,闪耀登场。它绝非一场普通的赛事,而是一座连接编程智慧与古典诗词韵味的桥梁。 这项挑战赛以独特的视角,将每日古…...

链式二叉树,递归的暴力美学
目录 1.链式二叉树概念 2.链式二叉树的实现 3.先序遍历 4.中序遍历 5.后序遍历 6.求链式二叉树的结点个数 7.链式二叉树的叶子结点个数 8.求二叉树的k层的结点个数 9.链式二叉树求深度 10.求值为x的结点 11.链式二叉树的销毁 12.二叉树的层序遍历 13.判断二叉树是否…...
计算机网络之---数据传输与比特流
数据传输的概念 数据传输是指将数据从一个设备传输到另一个设备的过程。传输过程涉及将高层协议中的数据(如包、帧等)转化为比特流,在物理介质上传输。 比特流的概念 比特流是数据传输中最基本的单位,它是由0和1组成的连续比特…...

基于单片机的数字电能表(论文+源码)
1. 系统整体方案设计 数字电能表系统设计解决了传统的用电设备的应用问题,能够让用户通过手机等移动设备获取电器的实时工作状态及数据信息,能够帮助找出高能耗的电器,及时停用或替换高能耗用电设备。在功能上需要实现高压交流电压的测量&am…...

打造三甲医院人工智能矩阵新引擎(五):精确分割模型篇 Medical SAM 2
一、引言 1.1 研究背景与意义 在当今的医疗领域,医学图像分割技术起着举足轻重的作用。它能够精准地从医学图像中分离出特定的器官、组织或病变区域,为临床诊断、手术规划、疾病监测等诸多环节提供不可或缺的支持。例如,在肿瘤疾病的诊疗过程中,通过对 CT、MRI 等影像的精…...

python无需验证码免登录12306抢票 --selenium(2)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 [TOC](python无需验证码免登录12306抢票 --selenium(2)) 前言 提示:这里可以添加本文要记录的大概内容: 就在刚刚我抢的票:2025年1月8日…...
第1章 Web系统概述 教案
谢从华,高蕴梅 著.Web前端设计基础入门——HTML5、CSS3、JavaScript(微课视频版),2023, 清华大学出版社. ISBN:9787302641261. 1、教学目标 知识目标 学生能够准确阐述 Internet 的含义、发展历程、提供的网络服务,以…...

AI是IT行业的变革力量,还是“职业终结者”?
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 AI是…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

轻量级Docker管理工具Docker Switchboard
简介 什么是 Docker Switchboard ? Docker Switchboard 是一个轻量级的 Web 应用程序,用于管理 Docker 容器。它提供了一个干净、用户友好的界面来启动、停止和监控主机上运行的容器,使其成为本地开发、家庭实验室或小型服务器设置的理想选择…...

动态规划-1035.不相交的线-力扣(LeetCode)
一、题目解析 光看题目要求和例图,感觉这题好麻烦,直线不能相交啊,每个数字只属于一条连线啊等等,但我们结合题目所给的信息和例图的内容,这不就是最长公共子序列吗?,我们把最长公共子序列连线起…...