字节码进阶之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的主要功能包括:
- 在类文件加载到JVM之前,改变类文件的字节码。
- 在运行时计算应用程序中对象的大小。
- 在运行时更改类的定义。
- 为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字节码操作库)来实现。以下是一个如何实现的例子:
- 需要一个
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;}}
}
- 需要一个
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);}
}
- 需要创建一个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
- 实现
premain
方法:在你的Java Agent类中,实现premain
方法。 - 创建
MANIFEST.MF
文件:在MANIFEST.MF
文件中,设置Premain-Class
属性为你的Java Agent类的全名。 - 打包为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的一部分,它允许开发人员在运行时修改类的字节码。使用此功能,可以实现许多高级操…...
Android 13.0 锁屏页面禁止下拉状态栏
1.概述 在13.0的系统产品定制化中,在默认的锁屏界面的时候原生系统是可以下拉状态栏的,但是定制的产品是需要禁用下拉状态栏的,所以需要在锁屏页面的时候禁用下拉状态栏,需要从两部分查看下拉状态栏流程然后禁用状态栏 接下来就来分析下看这个功能怎么实现 2.锁屏页面禁止…...

Windows10 Docker 安装教程
Docker Desktop是什么? Docker Desktop是适用于Windows的Docker桌面,是Docker设计用于在Windows 10上运行。它是一个本地 Windows 应用程序,为构建、交付和运行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漏洞的理解更深刻
靶场安装: 靶场安装请参考以下博客,既详细有提供工具: 【网络安全 --- xss-labs靶场】xss-labs靶场安装详细教程,让你巩固对xss漏洞的理解及绕过技巧和方法(提供资源)-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++ 模板进阶
非类型模板参数 模板参数分为:类型模板参数与非类型模板参数 类型模板参数即:出现在模板参数列表中,跟在class或者typename之后的参数类型名称非类型模板参数即:用一个常量作为类(函数)模板的一个参数,在类(函数)模…...

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

Hadoop3教程(三十三):(生产调优篇)慢磁盘监控与小文件归档
文章目录 (161)慢磁盘监控(162)小文件归档小文件过多的问题如何对小文件进行归档 参考文献 (161)慢磁盘监控 慢磁盘,是指写入数据时特别慢的一类磁盘。这种磁盘并不少见,当机器运行…...

物联网知识复习
物联网的内涵和体系结构 物联网的基本内涵 物联网的基本内涵在于物联,物物相连或者物和人相连的互联网。 也就是说,它是要由物主动发起的,物物互联的互联网。 它的第一层意思是说物和物相连;第二层意思是说物和人相连。 物联网的…...
Golang爬虫入门指南
引言 网络爬虫是一种自动化程序,用于从互联网上收集信息。随着互联网的迅速发展,爬虫技术在各行各业中越来越受欢迎。Golang作为一种高效、并发性好的编程语言,也逐渐成为爬虫开发的首选语言。本文将介绍使用Golang编写爬虫的基础知识和技巧…...

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

记录一次线下渗透电气照明系统(分析与实战)
项目地址:https://github.com/MartinxMax/S-Clustr 注意 本次行动未造成任何设备损坏,并在道德允许范围内测试 >ethical hacking< 发现过程 在路途中,发现一个未锁的配电柜,身为一个电工自然免不了好奇心(非专业人士请勿模仿,操作不当的话220V人就直了) 根据照片,简…...
Android ADB 常用命令及详解
Android ADB 常用命令及详解 Android Debug Bridge(ADB)是 Android 开发工具包(SDK)的一部分,用于与 Android 设备通信和执行各种任务。无论你是 Android 开发者还是普通用户,了解 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共享内存
前言 作者:小蜗牛向前冲 名言:我可以接受失败,但我不能接受放弃 如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 本期学习目标&…...

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

Nautilus Chain 与 Coin98 生态达成合作,加速 Zebec 生态亚洲战略进
目前,行业内首个模块化 Layer3 架构公链 Nautilus Chain 已经上线主网,揭示了模块化区块链领域迎来了全新的进程。在主网上线后,Nautilus Chain 将扮演 Zebec 生态中最重要的底层设施角色,并将为 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 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...

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