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

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程序&#xff0c;它可以在JVM启动时或运行时动态加载&#xff0c;用于监控和修改其他Java应用程序的行为‌。通过Java Agent&#xff0c;开发者可以在不修改目标应用程序源码的情况下&#xff0c;动态地插入功能&#xff0c;如性能分析、日志记录…...

Web无障碍

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

概率基本概念 --- 离散型随机变量实例

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

毕业项目推荐:基于yolov8/yolov5/yolo11的动物检测识别系统(python+卷积神经网络)

文章目录 概要一、整体资源介绍技术要点功能展示&#xff1a;功能1 支持单张图片识别功能2 支持遍历文件夹识别功能3 支持识别视频文件功能4 支持摄像头识别功能5 支持结果文件导出&#xff08;xls格式&#xff09;功能6 支持切换检测到的目标查看 二、数据集三、算法介绍1. YO…...

基于 WEB 开发的高校学籍管理系统设计与实现

标题:基于 WEB 开发的高校学籍管理系统设计与实现 内容:1.摘要 摘要&#xff1a;随着信息技术的不断发展&#xff0c;高校学籍管理系统的信息化建设已成为必然趋势。本文以高校学籍管理系统为研究对象&#xff0c;探讨了基于 WEB 开发的高校学籍管理系统的设计与实现。通过对系…...

阿里云发现后门webshell,怎么处理,怎么解决?

当收到如下阿里云通知邮件时&#xff0c;大部分管理员都会心里一惊吧&#xff01;出现Webshell&#xff0c;大概是网站被入侵了。 尊敬的 xxxaliyun.com&#xff1a; 云盾云安全中心检测到您的服务器&#xff1a;47.108.x.xx&#xff08;xx机&#xff09;出现了紧急安全事件…...

HTB:Bank[WriteUP]

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

如何用数字万用表测量是否漏电?

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

黑马跟学.苍穹外卖.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”问题:完整教程与代码实现

引言 在这篇文章中&#xff0c;我们将深入探讨编程竞赛中的一道经典问题“A. Accounting”&#xff0c;并用Python实现一个高效的解决方案。本文将涵盖题目分析、算法设计和Python代码实现&#xff0c;以及代码的完整讲解和优化方法。 一、问题描述 在一个遥远的国家里&…...

FreeU: Free Lunch in Diffusion U-Net 笔记

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

腾讯云AI代码助手编程挑战赛-古诗词学习

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

链式二叉树,递归的暴力美学

目录 1.链式二叉树概念 2.链式二叉树的实现 3.先序遍历 4.中序遍历 5.后序遍历 6.求链式二叉树的结点个数 7.链式二叉树的叶子结点个数 8.求二叉树的k层的结点个数 9.链式二叉树求深度 10.求值为x的结点 11.链式二叉树的销毁 12.二叉树的层序遍历 13.判断二叉树是否…...

计算机网络之---数据传输与比特流

数据传输的概念 数据传输是指将数据从一个设备传输到另一个设备的过程。传输过程涉及将高层协议中的数据&#xff08;如包、帧等&#xff09;转化为比特流&#xff0c;在物理介质上传输。 比特流的概念 比特流是数据传输中最基本的单位&#xff0c;它是由0和1组成的连续比特…...

基于单片机的数字电能表(论文+源码)

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

打造三甲医院人工智能矩阵新引擎(五):精确分割模型篇 Medical SAM 2

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

python无需验证码免登录12306抢票 --selenium(2)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 [TOC](python无需验证码免登录12306抢票 --selenium(2)) 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 就在刚刚我抢的票&#xff1a;2025年1月8日…...

第1章 Web系统概述 教案

谢从华&#xff0c;高蕴梅 著.Web前端设计基础入门——HTML5、CSS3、JavaScript&#xff08;微课视频版&#xff09;,2023, 清华大学出版社. ISBN&#xff1a;9787302641261. 1、教学目标 知识目标 学生能够准确阐述 Internet 的含义、发展历程、提供的网络服务&#xff0c;以…...

AI是IT行业的变革力量,还是“职业终结者”?

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 AI是…...

Redis故障转移

概述 本文主要讲述了Redis故障转移的原理及过程&#xff0c;可与「Redis高可用架构」文章一同阅读&#xff0c;可更好理解相关内容&#xff0c;及整个Redis高可用架构的实现原理。 Leader 选举 哨兵首先进入 WATI_START 状态进行准备&#xff0c;等待哨兵成为哨兵集群的 Leade…...

【Android基础回顾】五:AMS(Activity Manager Service)

Android 的 AMS&#xff08;Activity Manager Service&#xff09;是 Android 系统中的核心服务之一&#xff0c;负责管理整个应用生命周期、任务栈、进程和四大组件&#xff08;Activity、Service、BroadcastReceiver、ContentProvider&#xff09;的运行。它运行在系统进程 s…...

计算机视觉与深度学习 | 基于MATLAB的图像特征提取与匹配算法总结

基于MATLAB的图像特征提取与匹配算法全面指南 图像特征提取与匹配 基于MATLAB的图像特征提取与匹配算法全面指南一、图像特征提取基础特征类型分类二、点特征提取算法1. Harris角点检测2. SIFT (尺度不变特征变换)3. SURF (加速鲁棒特征)4. FAST角点检测5. ORB (Oriented FAST …...

【MATLAB去噪算法】基于CEEMDAN联合小波阈值去噪算法(第四期)

CEEMDAN联合小波阈值去噪算法相关文献 一、EMD 与 EEMD 的局限性 &#xff08;1&#xff09;EMD (经验模态分解) 旨在自适应地将非线性、非平稳信号分解成一系列 本征模态函数 (IMFs)&#xff0c;这些 IMFs 从高频到低频排列。 核心问题&#xff1a;模态混合 (Mode Mixing) 同…...

Linux中shell编程表达式和数组讲解

一、表达式 1.1 测试表达式 样式1: test 条件表达式 样式2: [ 条件表达式 ] 注意&#xff1a;以上两种方法的作用完全一样&#xff0c;后者为常用。但后者需要注意方括号[、]与条件表达式之间至少有一个空格。test跟 [] 的意思一样条件成立&#xff0c;状态返回值是0条件不成…...

Spring Boot + Prometheus 实现应用监控(基于 Actuator 和 Micrometer)

文章目录 Spring Boot Prometheus 实现应用监控&#xff08;基于 Actuator 和 Micrometer&#xff09;环境准备示例结构启动和验证验证 Spring Boot 应用Prometheus 抓取配置&#xff08;静态方式&#xff09;Grafana 面板配置总结 Spring Boot Prometheus 实现应用监控&…...

【React】useId

在 React 中&#xff0c;useId 是 React 18 引入的一个 Hook&#xff0c;用于生成一个在组件生命周期中保持稳定的唯一 ID。它主要用于&#xff1a; 无障碍&#xff08;accessibility&#xff09;场景&#xff0c;比如表单控件需要一个 id 和 label 的 htmlFor 属性配对。服务…...

AI对测试行业的应用

AI对测试行业的应用 AI技术在软件测试领域的应用已从概念验证全面迈向工程化落地&#xff0c;正在重构测试流程、提升效率边界&#xff0c;并为质量保障体系带来范式级变革。以下从技术突破、行业实践与未来趋势三个维度展开深度解析&#xff1a; ⚙️ 一、核心技术突破&#…...

解锁Java线程池:性能优化的关键

一、引言 在 Java 并发编程的世界里&#xff0c;线程池是一个至关重要的概念。简单来说&#xff0c;线程池就是一个可以复用线程的 “池子”&#xff0c;它维护着一组线程&#xff0c;这些线程可以被重复使用来执行多个任务&#xff0c;而不是为每个任务都创建一个新的线程。​…...

Verilog编程技巧01——如何编写三段式状态机

前言 Verilog编程技巧系列文章将聚焦于介绍Verilog的各种编程范式或者说技巧&#xff0c;编程技巧和编程规范有部分重合&#xff0c;但并非完全一样。规范更注重编码的格式&#xff0c;像变量命名、缩进、注释风格等&#xff0c;而编程技巧则更偏重更直观易读、更便于维护、综合…...