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

37.Java进阶之实现动态编译

文章目录

  • 1. 作为程序员的最高追求
  • 2.如何实现动态编译
    • 2.1 生成源码
    • 2.2 调用编译器API对Test源码文件进行编译生成字节码
    • 2.3 调用类加载器对字节码进行加载得到Class对象
    • 2.4 使用Class对象创建对象进行使用
  • 3. Java编译API学习
  • 4. 类加载机制
    • 4.1 类加载过程
    • 4.2 类加载器的层次结构
      • 4.2.1 使用URLClassLoader加载jar包中的类
      • 4.2.2 干涉类加载过程
      • 4.2.3 自定义类加载器
  • 5.项目代码地址

1. 作为程序员的最高追求

当我们习惯了编写重复的业务代码,是否有时候会感觉到无聊至极!!

有时候,作为程序员,是否脑子中会不时的闪现出一个想法,如果我能写一个程序让系统能自动的写代码,

然后再自动的装载到系统中实时编译运行就好了。

实际上,这并不是不能实现,在JDK8中提供了编译相关的API供我们使用,
通过JDK8,我们可以实现程序自动生成源代码 ,然后自动进行编译加载,在不停掉系统的前提下新增类并使用它!!
这就是动态编译。

2.如何实现动态编译

简单来说分为四步:

生成源码–>编译源码生成字节码文件–>加载字节码得到Class对象–>使用Class对象创建对象并使用

2.1 生成源码

这一步就是根据业务的不同,我们可以灵活处理,是我们自由发挥的关键。

我们先写一个例子,加入我们打算在系统编译这个源文件并加载它,我们应该怎么做?

Test.java

package Progress.exa37.complier;
public class Test {public static void main(String[] args) {System.out.println("这是要用Java编译器Api进行编译的java源文件");}public void printInfo(){System.out.println("成功加载并生成对象,执行printInfo完成");}
}

2.2 调用编译器API对Test源码文件进行编译生成字节码

这一步我们需要详细学习,这是实现源码到字节码的关键,我们先按简单的来,怎么简单怎么来,
我们可以用JavaCompiler这个类对指定源文件进行编译:

public class Test{public static void main(String[] args) throws FileNotFoundException {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();OutputStream outputStream = new FileOutputStream("output.txt");OutputStream errStream = new FileOutputStream("error.txt");//注意这里的路径就是源文件的路径int result = compiler.run(null,outputStream,errStream,"../study/Java基础学习/src/main/java/Progress/exa38/complier/Test.java");if(result==0){System.out.println("编译成功!!!");}}
}

当运行完成后,会打印出下面的内容,并在程序同级目录下生成一个class文件:
在这里插入图片描述
到这里,我们已经完成了源码的编译。

2.3 调用类加载器对字节码进行加载得到Class对象

这一步非常关键,这一步的成功与否标志着我们能否顺利的创建对象。
一般用类加载器对字节码进行加载,
我们一般都选择自己写一个自定义加载器, 实现findClass方法用来得到此类的Class对象.

package Progress.exa37.loader;import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;public class MyClassLoader extends ClassLoader{/*** 根据路径和类全名对字节码文件进行读取并加载* @param pathName 字节码文件路径* @param className 类包名* @return 返回这个类的Class对象*/protected Class<?> findClass(String pathName,String className) {// 声明字节码数组byte[] cLassBytes = null;Path path = null;try {path = Paths.get(new URI(pathName));// 读取字节码文件的字节码cLassBytes = Files.readAllBytes(path);} catch (IOException | URISyntaxException e) {e.printStackTrace();}// 根据类的包名,字节码数组构建class对象Class<?> clazz = defineClass(className, cLassBytes, 0, cLassBytes.length);return clazz;}}

通过上面的方法我们可以获取到Class对象:

package Progress.exa37.loader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;/*** 使用自定义的类加载器对class文件加载并创建实例使用之*/
public class LoaderStudy {public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, MalformedURLException {MyClassLoader loader = new MyClassLoader();Class<?> aClass = loader.findClass("file:///E:/Personal/MyRepository/study/Java基础学习/src/main/java/Progress/exa37/complier/Test.class","Progress.exa37.complier.Test");}
}

2.4 使用Class对象创建对象进行使用

通过上面的一步,我们获取到Test.class字节码对应的Class对象后,我们就可以使用Class来创建实例对象了:

package Progress.exa37.loader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
/*** 使用自定义的类加载器对class文件加载并创建实例使用之*/
public class LoaderStudy {public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, MalformedURLException {MyClassLoader loader = new MyClassLoader();Class<?> aClass = loader.findClass("file:///E:/Personal/MyRepository/study/Java基础学习/src/main/java/Progress/exa37/complier/Test.class","Progress.exa37.complier.Test");try {Object obj = aClass.newInstance();Method method = aClass.getMethod("printInfo");method.invoke(obj);} catch (Exception e) {e.printStackTrace();}}
}

执行结果:
控制台打印: 成功加载并生成对象,执行printInfo完成

到这里,我们应该大概明白如何利用java编译器api和类加载器实现动态编译了。
那么对于类加载器,我们为何要去自定义一个呢? 难道不能使用Jdk原有的api去实现相应的功能吗?
要理解这一点,我们就得学习一些Java中的类加载机制。

3. Java编译API学习

通过上面的动手,我们可以发现在Java程序中对某个源文件进行编译可以这样:

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//注意这里的路径就是源文件的路径
int result = compiler.run(null,outputStream,errStream,"../study/Java基础学习/src/main/java/Progress/exa38/complier/Test.java");
if(result==0){System.out.println("编译成功!!!");
}

通过JavaCompiler对象的run方法可以对指定路径的源文件进行编译为字节码文件。

4. 类加载机制

下面了解Java的类加载机制。

我们知道Java程序的执行流程是先将Java源文件编译成字节码文件(存储虚拟机代码),
然后由虚拟机去加载这些字节码文件将其转换为对应平台的机器码进行执行。

而这个虚拟机加载字节码文件的过程,我们有必要进行了解

4.1 类加载过程

虚拟机只加载程序执行时所需要的类文件。

我们假设程序从MyProgram.class开始运行,那么虚拟机的执行步骤如下:

  • 虚拟机有一个用于加载类文件的机制,例如从磁盘中读取文件或者请求Web上的文件,虚拟机会使用该机制来加载MyProgram类文件中的内容。
    一个类的加载流程如下:

    1. 如果MyProgram类拥有类型为另一个类的域,或者超类,那么这些类文件也会被加载,这一过程被称为类的解析(加载某个类所依赖的所有类的过程被称为类的解析)
    2. 接着,虚拟机执行MyProgram中的静态的main方法
    3. 如果main方法或者main方法调用的方法要用到更多的类,那么接下来就会加载这些类.
  • 然而,类加载机制并非只使用单个的类加载器,每个Java程序至少拥有三个类加载器:

    1. 系统类加载器(BootStrap)

      系统类加载器负责加载系统类(对rt.jar中的类进行加载,为java程序运行时必须的类)。系统类时虚拟机不可分隔的一部分,通常是一些有C语言是实现的底层类。系统类加载器没有对应的ClassLoader对象,它是虚拟机的一部分。

    2. 扩展类加载器(ExtClassLoader)

      扩展类加载器用于从jre/lib/ext目录加载 ”标准的扩展“。 我们可以将Jar文件放到该目录下,这样即使没有任何类路径,扩展类加载器也能找到其中的各个类。

    3. 应用类加载器(AppClassLoader)

      应用类加载器用于加载应用类。 它由CLASSPATH环境变量或者-classpath命令行选项设置的类路径中的目录里或者jar/zip文件里找到这些类。

注意:在Java中,扩展类加载器和系统类加载器都是用Java实现的,他们都是URLClassLoader类的实例。

4.2 类加载器的层次结构

类加载器有一种父/子关系。除了系统类加载器外,其他的每个类加载器都有一个父类加载器。

根据虚拟机规定,类加载器会为它的父类加载器提供一个机会,以便加载任何给定的类,并且只有在其父类
加载器加载失败时,它才会加载给定的类。

例如:当要求应用类加载器加载一个类(例如java.util.ArrayList)时,应用类加载器会先去请求扩展类加载器
对ArrayList进行加载,然后扩展类会再去请求系统类加载器进行加载,系统类加载器会对其进行加载,如果加载
失败,则扩展类加载器会对其进行加载,如果扩展类加载器加载失败,则应用类加载器会对其进行加载并返回。
(有点类似责任链模式)

4.2.1 使用URLClassLoader加载jar包中的类

某些程序具有插件架构,其中代码的某些部分是作为可选的插件打包的。
如果插件被打包为JAR文件,那就可以直接用URLClassLoader类的实例去加载这些类。

URL url = new URL("file:///path/to/plugin.jar");
URLClassLoader loader = new URLClassLoader(new URL[]{url});
Class<?> cl = loader.loadClass("mypackage.myClass");
Object obj = cl.newInstance();

由于在URLClassLoader构造器中没有指定父类加载器,所以loader的父类加载器就是应用类加载器。
在Java中,所有的类加载器都应该继承ClassLoader抽象类。

大多数的时候,我们不需要去干预类加载的层次结构,通常,类是由于其他的类需要它而被加载的,这个过程对我们是透明的。

4.2.2 干涉类加载过程

偶尔,有时需要干涉指定类的加载过程。

思考下面例子:

应用的代码包含一个help方法,它需要调用Class.forName(classNameString), 而这个方法是从一个插件类中被调用的,
更巧的是,classNameString指定的正是一个包含在这个插件的Jar包的类。

插件的作者很合理的期望这个类应该被加载,但是,help方法是由应用类加载器加载的,而classNameString对于应用类
加载器是不可视的,这个类无法被正常的加载!!

要解决这个问题,help方法在调用Class.forName(classNameString)之前需要用恰当的类加载器先将这个类加载。

解决方案:

每个线程都有一个类加载器的引用,这个引用被称之为上下文类加载器。

主线程的上下文类加载器是应用类加载器。 当新线程创建时,它的上下文类加载器会被设置为创建该线程的上下文类加载器。
因此,如果不做额外的操作,那么所有的线程就都会将自己的上下文类加载器设置为应用类加载器!!


所以我们可以这样做:

Thread t = Thread.currentTherad();
t.setContextClassLoader(selfloader);

然后help这个方法就能用自定义的类加载器进行类加载了:

Thread t = Thread.currentThread();
ClassLoader loader = t.getContextClassLoader();
Class cl = loader.loadClass(className);

那么问题来了,我们该如何去编写自定义的类加载器呢?

4.2.3 自定义类加载器

我们可以编写自己的用于特殊目的的类加载器,这使得我们可以在向虚拟机传递字节码之前执行定制的检查。

例如我们可以编写一个类加载器,它可以拒绝加载没有标记为 piadfor的类。

如果要编写自己的类加载器,只需要继承ClassLoader类,然后覆盖这个类的findClass方法:

ClassLoader的loadClass方法用于将类的加载操作委托给其父类加载器进行,
只有当该类尚未加载并且父类加载器也无法加载该类时,才调用findClass方法。

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {//如果类加载器存在父类,先让父类加载if (parent != null) {c = parent.loadClass(name, false);} else {// 如果所有的父类都加载失败,调用rt加载器加载c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}// 如果父类加载器和rt加载器都加载失败,则直接调用自己的类加载器加载if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();// 这里是最后的防线c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

findClass方法的实现前提是:

  • 为来自本地文件系统或者其他源的类加载其字节码

  • 调用ClassLoader的defineClass方法向虚拟机提供字节码

    这样我们就能使用自定义加载器顺利加载自定义类,

    示例:

public class MyClassLoader extends ClassLoader {/*** 根据路径和类全名对字节码文件进行读取并加载* @param pathName 字节码文件路径 file:///E:/Personal/MyRepository/study/Java基础学习/src/main/java/Progress/exa37/complier/Test.class* @return 返回这个类的Class对象*/@Overrideprotected Class<?> findClass(String pathName)  throws ClassFormatError {// 声明字节码数组byte[] cLassBytes = null;Path path = null;try {path = Paths.get(new URI(pathName));// 读取字节码文件的字节码cLassBytes = Files.readAllBytes(path);} catch (IOException | URISyntaxException e) {e.printStackTrace();}String className = pathName.substring(pathName.indexOf("java")+5,pathName.indexOf(".class")).replace("/",".");// 根据类的包名,字节码数组构建class对象Class<?> clazz = defineClass(className, cLassBytes, 0, cLassBytes.length);return clazz;}
}

5.项目代码地址

https://gitee.com/yan-jiadou/study/tree/master/Java%E5%9F%BA%E7%A1%80%E5%AD%A6%E4%B9%A0/src/main/java/Progress/exa37

相关文章:

37.Java进阶之实现动态编译

文章目录1. 作为程序员的最高追求2.如何实现动态编译2.1 生成源码2.2 调用编译器API对Test源码文件进行编译生成字节码2.3 调用类加载器对字节码进行加载得到Class对象2.4 使用Class对象创建对象进行使用3. Java编译API学习4. 类加载机制4.1 类加载过程4.2 类加载器的层次结构4…...

【Python百日进阶-Web开发-Vue3】Day549 - Vue3 商城后台 09:Veux4-01基本概念

文章目录 一、Vuex是什么?1.1 Vuex官网1.2 Vuex安装1.3 Vuex概述1.4 核心概念二、Vuex的基本使用2.1 简单使用2.1.1 `src/store/index.js`创建store并导出2.1.2 `main.js`中引入并`use(store)`2.1.3 `src/views/index.vue`首页中使用store2.2 使用`mapState`简化 `$store.stat…...

GitLab 解析:为什么市场正在转向一体化 DevSecOps 平台?(附Forrester完整报告下载)

本文来源&#xff1a;about.gitlab.com 译者&#xff1a;极狐(GitLab) 市场部内容团队 如 GitLab 预测&#xff1a;2023 年企业会将更多的时间和资源投入到持续的安全左移上&#xff08;详情请戳&#x1f449;&#xff1a;重磅&#xff01;GitLab 提出五大预测&#xff0c;洞见…...

ThreadLocal的内部结构和源码探究

目录一. ThreadLocal的内部结构1 常见的误解2 现在的设计3 这样设计的好处二. ThreadLocal的核心方法源码1 set方法2 get方法3 remove方法**4 initialValue方法**三. ThreadLocalMap源码分析1 基本结构2 弱引用和内存泄漏3 hash冲突的解决一. ThreadLocal的内部结构 ​ 通过之…...

Linux文件系统(下)

逻辑卷管理如果用标准分区在硬盘上创建了文件系统&#xff0c;为已有文件系统添加额外的空间多少是一种痛苦的体验。你只能在同一个物理硬盘的可用空间范围内调整分区大小。如果硬盘上没有地方了&#xff0c;你就必须弄一个更大的硬盘&#xff0c;然后手动将已有的文件系统移动…...

合并链表相关的练习

目录 一、合并两个有序链表 二、两数相加 一、合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&…...

FFmpeg介绍及入门知识

1、简介 FFmpeg是一套由c语言编写的&#xff0c;可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序,自身采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案&#xff0c;包含了非常先进的音频/视频编解码库libavcodec&#xf…...

ASA材料3D打印服务 抗紫外线材料3D打印服务 抗紫外线模型制作-CASAIM中科院广州电子

3D打印技术又称增材制造&#xff0c;通常是采用数字技术材料打印机来实现的&#xff0c;常在模具制造、工业设计等领域被用于制造模型&#xff0c;后逐渐用于一些产品的直接制造。随着 3D 打印逐渐成为主流生产流程的一部分&#xff0c;ASA抗紫外线材料应运而生。中科院广州电子…...

MySQL workbench数据表和数据结构

数据表和数据结构的关系 数据表 学号姓名位置26002351李晓丽126002589张明伟226003214李雪冬326002132汪涵426006541邱明罕526003654李丽6 怎样去描述上面的数据表&#xff0c;用【数据表结构】表示 表头字段名字段类型位数备注学号xuehao整数/字符8 姓名xingming字符4 座…...

网络与信息安全岗位介绍—售后工程师

售后工程师是提供客户技术支持和服务的专业人士。他们的任务是提供客户技术支持&#xff0c;安装、维护和修复系统或产品&#xff0c;遵从安全操作规范&#xff0c;排除计算机故障&#xff0c;以及解决其他技术疑难杂症。 售后工程师还管理、安装、升级和维护现有硬件和软件&a…...

Nowcoder .链表分割

文章目录哨兵位节点哨兵位节点 链表分割 小于X 尾插到一个新链表 大于等于X 尾插到另一个链表 最后将两个链表链接起来 需要注意的细节&#xff1a;将第一个链表的尾与第二个链表的头相连接&#xff0c;再返回连接后的整个链表的头&#xff08;哨兵位头节点的下一个&#xff0…...

猿创征文 | re:Invent 朝圣之路:“云“行业风向标

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; AWS 亚马逊云科技re:Invent全球大会 2022年亚马逊云科技re:Invent全球大会震撼来袭&#xff0c;即将于北京时间11月30日-12月2日在美国内华达州&#xff0c;拉斯维加斯…...

mysql的distinct和group by的区别

GROUP BY 和 DISTINCT 都是用于从数据库中选择唯一值的 SQL 子句。它们之间的主要区别在于它们的作用方式和应用场景。 GROUP BY 语句用于将数据按照一个或多个列进行分组&#xff0c;然后对每个组应用一个聚合函数&#xff08;如 COUNT、SUM、AVG 等&#xff09;以得到每个组…...

Web前端:前端开发人员的职责有哪些?

前端开发&#xff0c;就是要创造上面提到的网站面向用户的部分背后的代码&#xff0c;并通过建立框架&#xff0c;构建沉浸性的用户体验。前端工程师还需要确保网站在各种浏览器和设备上都能正常运行&#xff0c;并且能够根据用户需求不断优化和改进网站。前端开发人员的角色和…...

BatchNorm1d的复现以及对参数num_features的理解

0. Intro 以pytorch为例&#xff0c;BatchNorm1d的参数num_features涉及了对什么数据进行处理&#xff0c;但是我总是记不住&#xff0c;写个blog帮助自己理解QAQ 1. 复现nn.BatchNorm1d(num_features1) 假设有一个input tensor&#xff1a; input torch.tensor([[[1.,2.,…...

【专项训练】动态规划-1

动态规划 以上,并没有什么本质的不一样,很多时候,就是一些小的细节问题! 要循环,要递归,就是有重复性! 动态规划:动态递推 分治 + 最优子结构 会定义状态,把状态定义对 斐波那契数列 递归、记忆化搜索,比较符合人脑思维 递推:直接开始写for循环,开始递推 这里…...

软测面试了一个00后,绝对能称为是内卷届的天花板

前言 公司前段缺人&#xff0c;也面了不少测试&#xff0c;结果竟然没有一个合适的。一开始瞄准的就是中级的水准&#xff0c;也没指望来大牛&#xff0c;提供的薪资也不低&#xff0c;面试的人很多&#xff0c;但平均水平很让人失望。令我印象最深的是一个00后测试员&#xf…...

赢球票问题

题目描述 某机构举办球票大奖赛。获奖选手有机会赢得若干张球票。 主持人拿出 N 张卡片&#xff08;上面写着 1⋯N 的数字&#xff09;&#xff0c;打乱顺序&#xff0c;排成一个圆圈。 你可以从任意一张卡片开始顺时针数数: 1,2,3 ⋯⋯ 如果数到的数字刚好和卡片上的数字相…...

Go语言基础之Error接口

Go语言基础之Error接口1.Error 接口2.创建错误3.fmt.Errorf1.Error 接口 Go 语言中把错误当成一种特殊的值来处理&#xff0c;不支持其他语言中使用try/catch捕获异常的方式 Go 语言中使用一个名为 error 接口来表示错误类型 type error interface {Error() string }error 接…...

Sqoop详解

目录 一、sqoop基本原理 1.1、何为Sqoop&#xff1f; 1.2、为什么需要用Sqoop&#xff1f; 1.3、关系图 1.4、架构图 二、Sqoop可用命令 2.1、公用参数&#xff1a;数据库连接 2.2、公用参数&#xff1a;import 2.3、公用参数&#xff1a;export 2.4、公用参数&#xff…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

Fabric V2.5 通用溯源系统——增加图片上传与下载功能

fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

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 开发者设计的强大库&#xff…...

android13 app的触摸问题定位分析流程

一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

OD 算法题 B卷【正整数到Excel编号之间的转换】

文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的&#xff1a;a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...