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

字节码进阶之java Instrumentation原理详解

文章目录

  • 0. 前言
  • 1. 基础
  • 2. Java Instrumentation API
    • 使用示例
  • 3. Java Agent
  • 4. 字节码操作库
  • 5. 实际应用
  • 6. 注意事项和最佳实践

在这里插入图片描述

0. 前言

Java Instrumentation是Java API的一部分,它允许开发人员在运行时修改类的字节码。使用此功能,可以实现许多高级操作,例如性能监控、代码覆盖率分析等。

通过Java提供的Instrumentation接口,我们能够在运行时改变和监控Java程序,这种能力在许多场景中都非常有用,比如性能调优、故障排查、代码覆盖率分析等。然而,Instrumentation接口的使用和字节码操作本身都是高级主题,需要深入理解JVM和字节码的工作原理。

本文我们一起深入讲解Java Instrumentation的原理,并通过实际的例子展示如何使用字节码操作库来实现类的修改。

在字节码的世界里,一切皆可能。让我们开搞!

1. 基础

Java Instrumentation的概念和用途

Java Instrumentation是Java编程语言的一个特性,它允许开发者在Java程序运行时检查和修改应用程序的行为,特别是类和对象的行为。Java提供了一个名为java.lang.instrument的包,其中包含API用于在运行时更改和监控Java类。

Java Instrumentation的主要功能包括:

  1. 在类文件加载到JVM之前,改变类文件的字节码。
  2. 在运行时计算应用程序中对象的大小。
  3. 在运行时更改类的定义。
  4. 为JVM提供一种获取加载到Java应用程序的类文件的方式。

Java Instrumentation的主要用途包括性能监控(例如,计算方法调用的时间)、故障排查、代码覆盖率分析、内存分析、线程分析等。

Java Agent的介绍

Java Agent是Java Instrumentation的一种应用,是一种特殊的Java程序,它能够通过Java Instrumentation API修改其他Java程序的字节码。Java Agent在主程序之前启动,可以在类加载到JVM之前改变类的字节码。

Java Agent主要有两种类型:静态agent和动态agent。静态agent在JVM启动时通过命令行参数指定,并在主程序启动之前运行。动态agent则可以在JVM运行时随时加载。

Java Agent可以用来实现各种复杂的任务,例如性能监控、日志记录、代码审计等。一些常见的Java诊断和监控工具(如JProfiler、VisualVM等)就是通过Java Agent实现的。

2. Java Instrumentation API

Java Instrumentation API

Java Instrumentation API位于java.lang.instrument包内,这个包提供了类和接口供开发者进行字节码操作。

java.lang.instrument包中的关键类和接口

  • Instrumentation:此接口提供了用于实施字节码转换和获取对象的相关信息的方法。

  • ClassFileTransformer:这是一个接口,其中定义了一个transform方法,允许我们在类加载到JVM之前对其进行转换。

  • UnmodifiableClassException:这是一个异常,会在尝试修改或重定义它的状态时,由Instrumentation.retransformClasses方法和Instrumentation.redefineClasses方法抛出。

Instrumentation接口及其方法

Instrumentation接口提供了许多方法,这些方法可以用来改变和检查应用程序的行为,包括:

  • addTransformer(ClassFileTransformer transformer, boolean canRetransform): 添加一个类文件转换器。
  • removeTransformer(ClassFileTransformer transformer): 移除一个类文件转换器。
  • redefineClasses(ClassDefinition... definitions): 重新定义类。
  • retransformClasses(Class<?>... classes): 改变已经加载到JVM的类。
  • getObjectSize(Object objectToSize): 返回对象的大小(以字节为单位)。

ClassFileTransformer接口

ClassFileTransformer接口是Java Instrumentation API中的重要组成部分,它让我们在类加载到虚拟机之前,可以修改类的字节码。

  • transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer): 这个方法会在类被加载到JVM之前被调用,它可以修改类的字节码。

使用示例

假设我们想要跟踪每个方法调用的执行时间,我们可以使用Java Instrumentation和ASM(一个常用的Java字节码操作库)来实现。以下是一个如何实现的例子:

  1. 需要一个ClassFileTransformer来转换类文件:
import org.objectweb.asm.*;import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;public class ProfilingTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if (className.startsWith("my/package/")) { // only transform classes in my packageClassReader cr = new ClassReader(classfileBuffer);ClassWriter cw = new ClassWriter(cr, 0);ClassVisitor cv = new ProfilingClassAdapter(cw);cr.accept(cv, 0);return cw.toByteArray();} else {return classfileBuffer;}}
}
  1. 需要一个ClassVisitor来开始转换类,并一个MethodVisitor来插入我们的代码
import org.objectweb.asm.*;public class ProfilingClassAdapter extends ClassVisitor {public ProfilingClassAdapter(ClassVisitor cv) {super(Opcodes.ASM5, cv);}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);return new ProfilingMethodAdapter(mv, access, name, desc);}
}public class ProfilingMethodAdapter extends MethodVisitor {private String methodName;public ProfilingMethodAdapter(MethodVisitor mv, int access, String name, String desc) {super(Opcodes.ASM5, mv);this.methodName = name;}@Overridepublic void visitCode() {mv.visitCode();mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);mv.visitVarInsn(Opcodes.LSTORE, 1); // store start time to local variable}@Overridepublic void visitInsn(int opcode) {if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);mv.visitVarInsn(Opcodes.LLOAD, 1); // load start timemv.visitInsn(Opcodes.LSUB); // get elapsed time by subtracting start time from current timemv.visitVarInsn(Opcodes.LSTORE, 3); // store elapsed time to local variablemv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv.visitLdcInsn("execution time of " + methodName + ": ");mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv.visitVarInsn(Opcodes.LLOAD, 3); // load elapsed timemv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false); }mv.visitInsn(opcode);}
}
  1. 需要创建一个Java Agent来使用这个ClassFileTransformer
import java.lang.instrument.Instrumentation;public class ProfilingAgent {public static void premain(String agentArgs, Instrumentation inst) {inst.addTransformer(new ProfilingTransformer());}
}

以上代码在每个方法的开始和结束时,插入了代码来获取当前时间,计算出方法的执行时间,并将执行时间打印出来。此代码编译后,需要打包为JAR文件,并在JAR的MANIFEST.MF文件中指定Premain-Class属性为ProfilingAgent,然后就可以使用-javaagent:ProfilingAgent.jar参数启动JVM了。

3. Java Agent

Java Agent的概念

Java Agent是一种特殊的Java应用程序,它能够通过Java Instrumentation API修改其他Java程序的字节码。Java Agent在主程序之前启动,可以在类加载到JVM之前改变类的字节码。Java Agent可以用来实现各种复杂的任务,例如性能监控、日志记录、代码审计等。

如何创建Java Agent

要创建Java Agent,你需要创建一个Java类,并实现premain方法。这个方法在主程序的main方法之前执行,它的签名如下:

public static void premain(String agentArgs, Instrumentation inst)

你可以使用Instrumentation参数来添加你的ClassFileTransformer

创建Agent的步骤:实现premain方法、创建MANIFEST.MF文件、打包为JAR

  1. 实现premain方法:在你的Java Agent类中,实现premain方法。
  2. 创建MANIFEST.MF文件:在MANIFEST.MF文件中,设置Premain-Class属性为你的Java Agent类的全名。
  3. 打包为JAR:将你的类和MANIFEST.MF文件打包为一个JAR文件。

在JVM启动时附加Java Agent

当启动JVM时,你可以使用-javaagent参数来指定你的Java Agent JAR文件,如下:

java -javaagent:myAgent.jar -jar myApplication.jar

在运行时动态附加Java Agent(Attach API)

从Java 6开始,你也可以在运行时动态地附加Java Agent。这需要你再实现一个agentmain方法,这个方法在动态附加Java Agent时会被调用。其签名如下:

public static void agentmain(String agentArgs, Instrumentation inst)

要动态附加Java Agent,你需要使用Java的Attach API,例如:

VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentJarFilePath, agentArgs);
vm.detach();

上述代码会将Java Agent附加到运行中的JVM中,其中pid是你要附加的JVM的进程ID,agentJarFilePath是你的Java Agent JAR文件的路径,agentArgs是传递给agentmain方法的参数。

4. 字节码操作库

使用字节码操作库实现类的修改

字节码操作库能够帮助我们操作和修改Java类的字节码。这些库提供了一种在运行时修改Java类的方法,例如添加、修改或删除类的字段和方法,改变类的继承结构等。这些操作是通过直接修改类的字节码来实现的。

ASM

ASM是一个通用的Java字节码操纵和分析框架。它可以用来修改现有类或者动态生成新的类。ASM提供了一些核心API用于直接操作字节码,这使得ASM非常强大,但也意味着使用ASM需要了解Java字节码的详细知识。

ByteBuddy

Byte Buddy是一个新的库,用于创建和修改Java类。Byte Buddy的API设计得非常友好,适合那些不熟悉Java字节码的开发者使用。Byte Buddy也提供了一些高级特性,如方法调用代理和Java Agent的支持。

CGLIB

CGLIB(Code Generation Library)是一个开源的项目,它提供了一些强大的高级功能,如方法拦截和创建代理类。CGLIB通过使用ASM框架在运行时动态生成和加载新的Java类。CGLIB常常被用在许多流行的开源项目中,如Spring和Hibernate。

Javassist

Javassist(Java Programming Assistant)使Java字节码的编辑变得简单。它是一个类库,为修改字节码提供了两级的接口:源码级和字节码级。源码级接口允许以源码形式(如字符串)修改类的字段和方法,字节码级接口允许直接修改字节码。

对比和选择适合的库

库名称描述优点缺点
ASM一个通用的Java字节码操纵和分析框架提供了一些核心API用于直接操作字节码,非常强大使用ASM需要了解Java字节码的详细知识
ByteBuddy用于创建和修改Java类的库API设计得非常友好,适合那些不熟悉Java字节码的开发者使用,提供了一些高级特性相比于ASM,可能无法进行一些更底层的操作
CGLIB提供了一些强大的高级功能,如方法拦截和创建代理类常常被用在许多流行的开源项目中,如Spring和Hibernate使用ASM框架,需要一定的字节码知识
Javassist使Java字节码的编辑变得简单提供了源码级和字节码级接口,使得操作更直观虽然方便,但在性能和灵活性上可能不如ASM

这只是一个大概的比较,具体使用哪个库还是要根据个人的实际需求和偏好来决定。每个库都有各自的优点和特色,了解它们的特性和优缺点,可以帮助你做出更好的决定。

  • 如果你需要最大的灵活性,并且不介意处理底层的字节码细节,那么ASM可能是最好的选择。
  • 如果你希望有一个简单、易用的API,并且需要一些高级特性,如方法调用代理,那么Byte Buddy可能是最好的选择。
  • 如果你需要生成代理类,或者你正在使用依赖于CGLIB的库(如Spring和Hibernate),那么CGLIB可能是最好的选择。
  • 如果你希望能够以源代码形式修改类,那么Javassist可能是最好的选择。

5. 实际应用

如何使用Java Instrumentation实现性能监控

为了使用Java Instrumentation实现性能监控,可以创建一个Java Agent,这个Agent会添加代码来跟踪每个方法的执行时间。

如何使用Java Instrumentation实现代码覆盖率分析

代码覆盖率分析是检查你的测试用例覆盖了多少代码的一种方法。可以使用Java Agent来修改类的字节码,添加代码来跟踪每个方法和代码块的执行情况。

public class CodeCoverageMethodAdapter extends MethodVisitor {private String className;private String methodName;public CodeCoverageMethodAdapter(MethodVisitor mv, String className, String methodName) {super(Opcodes.ASM5, mv);this.className = className;this.methodName = methodName;}// 添加代码来跟踪方法的执行情况@Overridepublic void visitCode() {mv.visitCode();mv.visitFieldInsn(Opcodes.GETSTATIC, "my/package/CodeCoverage", "executedMethods", "Ljava/util/Set;");mv.visitLdcInsn(className + "." + methodName);mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Set", "add", "(Ljava/lang/Object;)Z", true);mv.visitInsn(Opcodes.POP);}
}

CodeCoverage是一个你自己创建的类,它有一个Set类型的静态字段executedMethods,用于存储已经执行过的方法。然后,你需要在每个方法的开始处添加代码,将当前方法添加到executedMethods中。

如何使用Java Instrumentation实现故障排查和诊断工具

故障排查和诊断工具通常需要获取一些低级别的信息,例如对象的创建和销毁,方法的调用情况等。可以使用Java Agent来收集这些信息。下面是一个跟踪对象创建的例子:

public class ObjectCreationClassAdapter extends ClassVisitor {public ObjectCreationClassAdapter(ClassVisitor cv) {super(Opcodes.ASM5, cv);}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);if ("<init>".equals(name)) { // 如果是构造方法mv = new ObjectCreationMethodAdapter(mv);}return mv;}
}public class ObjectCreationMethodAdapter extends MethodVisitor {public ObjectCreationMethodAdapter(MethodVisitor mv) {super(Opcodes.ASM5, mv);}// 在对象创建后添加代码来跟踪对象的创建@Overridepublic void visitInsn(int opcode) {if (opcode == Opcodes.RETURN) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, "my/package/Tracker", "trackObjectCreation", "()V", false);}mv.visitInsn(opcode);}
}

<>Tracker是一个创建的类,它有一个静态方法trackObjectCreation,用于跟踪对象的创建。然后,你需要在每个构造方法的结束处添加代码,调用trackObjectCreation方法。

6. 注意事项和最佳实践

避免破坏类的结构和逻辑

字节码操作是一项强大的功能,但如果使用不当,可能会导致一些意想不到的问题。在进行字节码操作时,你需要确保不要破坏类的结构和逻辑。例如,不要删除或更改类的重要方法,不要添加破坏类行为的代码等。

优雅地处理异常

在字节码操作的过程中,可能会出现各种各样的异常。你需要确保在你的代码中妥善处理这些异常。一般来说,你应该尽量减少对异常的静默处理,而是应该将异常记录下来,便于后续的问题排查。

注意性能影响

虽然字节码操作可以实现强大的功能,但它同时也可能对应用程序的性能产生影响。你需要确保你的字节码操作不会对性能产生太大的影响。在进行性能敏感的字节码操作时,你可能需要对你的代码进行性能测试,以确保它不会成为性能瓶颈。

一些常见的性能影响因素包括:增加过多的方法调用,添加大量的同步代码,过度使用反射等。

相关文章:

字节码进阶之java Instrumentation原理详解

文章目录 0. 前言1. 基础2. Java Instrumentation API使用示例 3. Java Agent4. 字节码操作库5. 实际应用6. 注意事项和最佳实践 0. 前言 Java Instrumentation是Java API的一部分&#xff0c;它允许开发人员在运行时修改类的字节码。使用此功能&#xff0c;可以实现许多高级操…...

Android 13.0 锁屏页面禁止下拉状态栏

1.概述 在13.0的系统产品定制化中,在默认的锁屏界面的时候原生系统是可以下拉状态栏的,但是定制的产品是需要禁用下拉状态栏的,所以需要在锁屏页面的时候禁用下拉状态栏,需要从两部分查看下拉状态栏流程然后禁用状态栏 接下来就来分析下看这个功能怎么实现 2.锁屏页面禁止…...

Windows10 Docker 安装教程

Docker Desktop是什么&#xff1f; Docker Desktop是适用于Windows的Docker桌面&#xff0c;是Docker设计用于在Windows 10上运行。它是一个本地 Windows 应用程序&#xff0c;为构建、交付和运行dockerized应用程序提供易于使用的开发环境。Docker Desktop for Windows 使用 …...

JWT认证

目录 前言 JWT组成部分 JWT工作原理 在Express中使用JWT 安装JWT相关的包 导入JWT相关的包 定义密钥 登录成功后调用jwt.sign()生成JWT字符串 将JWT字符串还原为JSON对象 捕获解析JWT失败后产生的错误 结尾 前言 Session 认证机制需要配合 Cookie 才能实现。由于 Co…...

【网络安全 --- xss-labs靶场通关(1-10关)】详细的xss-labs靶场通关思路及技巧讲解,让你对xss漏洞的理解更深刻

靶场安装&#xff1a; 靶场安装请参考以下博客&#xff0c;既详细有提供工具&#xff1a; 【网络安全 --- xss-labs靶场】xss-labs靶场安装详细教程&#xff0c;让你巩固对xss漏洞的理解及绕过技巧和方法&#xff08;提供资源&#xff09;-CSDN博客【网络安全 --- xss-labs通…...

Mathematics-Vocabulary·数学专业英语词汇

点击查看: Mathematics-Vocabulary数学专业英语词汇点击查看: Mathematics-Vocabulary-Offline数学专业英语词汇离线版本 Chinese-English translation英译汉The study of mathematics in English requires understanding the subject-specific vocabulary and terminology. Ma…...

画程序流程图

一。在线程序流程图。类图和时序图 Integrations | Mermaid 二。VSCODE画UML图和各种种 1.下载plantuml.jarReleases plantuml/plantuml GitHubGenerate diagrams from textual description. Contribute to plantuml/plantuml development by creating an account on GitHu…...

C++ 模板进阶

非类型模板参数 模板参数分为&#xff1a;类型模板参数与非类型模板参数 类型模板参数即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之后的参数类型名称非类型模板参数即&#xff1a;用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模…...

jenkins 安装与使用、用户权限划分

jenkins 安装与使用 安装插件&#xff1a; 开启该插件功能 验证用户管理 创建web01~02 使用web01登录 用户权限划分 安装 Role-Based Strategy 插件后&#xff0c;系统管理 中多了如图下所示的一个功能&#xff0c;用户权限的划分就是靠他来做的 创建角色 重新访问 创建项目…...

Hadoop3教程(三十三):(生产调优篇)慢磁盘监控与小文件归档

文章目录 &#xff08;161&#xff09;慢磁盘监控&#xff08;162&#xff09;小文件归档小文件过多的问题如何对小文件进行归档 参考文献 &#xff08;161&#xff09;慢磁盘监控 慢磁盘&#xff0c;是指写入数据时特别慢的一类磁盘。这种磁盘并不少见&#xff0c;当机器运行…...

物联网知识复习

物联网的内涵和体系结构 物联网的基本内涵 物联网的基本内涵在于物联&#xff0c;物物相连或者物和人相连的互联网。 也就是说&#xff0c;它是要由物主动发起的&#xff0c;物物互联的互联网。 它的第一层意思是说物和物相连&#xff1b;第二层意思是说物和人相连。 物联网的…...

Golang爬虫入门指南

引言 网络爬虫是一种自动化程序&#xff0c;用于从互联网上收集信息。随着互联网的迅速发展&#xff0c;爬虫技术在各行各业中越来越受欢迎。Golang作为一种高效、并发性好的编程语言&#xff0c;也逐渐成为爬虫开发的首选语言。本文将介绍使用Golang编写爬虫的基础知识和技巧…...

1024渗透测试如何暴力破解其他人主机的密码(第十一课)

1024渗透测试如何暴力破解其他人主机的密码(第十一课) 1 crunch 工具 crunch是一个密码生成器&#xff0c;一般用于在渗透测试中生成随机密码或者字典攻击。下面是常见的一些使用方法&#xff1a; 生成密码字典 生成6位数字的字典&#xff1a;crunch 6 6 -t -o dict.txt …...

记录一次线下渗透电气照明系统(分析与实战)

项目地址:https://github.com/MartinxMax/S-Clustr 注意 本次行动未造成任何设备损坏,并在道德允许范围内测试 >ethical hacking< 发现过程 在路途中,发现一个未锁的配电柜,身为一个电工自然免不了好奇心(非专业人士请勿模仿,操作不当的话220V人就直了) 根据照片,简…...

Android ADB 常用命令及详解

Android ADB 常用命令及详解 Android Debug Bridge&#xff08;ADB&#xff09;是 Android 开发工具包&#xff08;SDK&#xff09;的一部分&#xff0c;用于与 Android 设备通信和执行各种任务。无论你是 Android 开发者还是普通用户&#xff0c;了解 ADB 命令是非常有用的&a…...

GO 工程下载依赖操作流程(go mod)

1. 写一个main.go文件 package main import ("fmt""net/http""github.com/ClickHouse/clickhouse-go" ) func main() {fmt.Println("服务启动......")http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Requ…...

[Linux打怪升级之路]-system V共享内存

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 本期学习目标&…...

STM32不使用 cubeMX实现外部中断

这篇文章将介绍如何不使用 cubeMX完成外部中断的配置和实现。 文章目录 前言一、文件加入工程二、代码解析exti.cexti.hmain.c 注意&#xff1a;总结 前言 实验开发板&#xff1a;STM32F103C8T6。所需软件&#xff1a;keil5 &#xff0c; cubeMX 。实验目的&#xff1a;如何不…...

Nautilus Chain 与 Coin98 生态达成合作,加速 Zebec 生态亚洲战略进

目前&#xff0c;行业内首个模块化 Layer3 架构公链 Nautilus Chain 已经上线主网&#xff0c;揭示了模块化区块链领域迎来了全新的进程。在主网上线后&#xff0c;Nautilus Chain 将扮演 Zebec 生态中最重要的底层设施角色&#xff0c;并将为 Zebec APP 以及 Zebec Payroll 规…...

method.isAnnotationPresent(Xxx.class)一直为null

​​​​package com.dj.springtest.aspect;import com.dj.springtest.annotation.RequireRoles; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.s…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

Vue ③-生命周期 || 脚手架

生命周期 思考&#xff1a;什么时候可以发送初始化渲染请求&#xff1f;&#xff08;越早越好&#xff09; 什么时候可以开始操作dom&#xff1f;&#xff08;至少dom得渲染出来&#xff09; Vue生命周期&#xff1a; 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...