优秀营销网站设计/新浪微舆情大数据平台
文章目录
- 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类文件中的内容。
一个类的加载流程如下:- 如果MyProgram类拥有类型为另一个类的域,或者超类,那么这些类文件也会被加载,这一过程被称为类的解析(加载某个类所依赖的所有类的过程被称为类的解析)
- 接着,虚拟机执行MyProgram中的静态的main方法
- 如果main方法或者main方法调用的方法要用到更多的类,那么接下来就会加载这些类.
-
然而,类加载机制并非只使用单个的类加载器,每个Java程序至少拥有三个类加载器:
-
系统类加载器(BootStrap):
系统类加载器负责加载系统类(对rt.jar中的类进行加载,为java程序运行时必须的类)。系统类时虚拟机不可分隔的一部分,通常是一些有C语言是实现的底层类。系统类加载器没有对应的ClassLoader对象,它是虚拟机的一部分。 -
扩展类加载器(ExtClassLoader):
扩展类加载器用于从jre/lib/ext目录加载 ”标准的扩展“。 我们可以将Jar文件放到该目录下,这样即使没有任何类路径,扩展类加载器也能找到其中的各个类。 -
应用类加载器(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完整报告下载)
本文来源:about.gitlab.com 译者:极狐(GitLab) 市场部内容团队 如 GitLab 预测:2023 年企业会将更多的时间和资源投入到持续的安全左移上(详情请戳👉:重磅!GitLab 提出五大预测,洞见…...

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

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

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

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

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

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

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

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

猿创征文 | re:Invent 朝圣之路:“云“行业风向标
💗wei_shuo的个人主页 💫wei_shuo的学习社区 🌐Hello World ! AWS 亚马逊云科技re:Invent全球大会 2022年亚马逊云科技re:Invent全球大会震撼来袭,即将于北京时间11月30日-12月2日在美国内华达州,拉斯维加斯…...

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

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

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

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

软测面试了一个00后,绝对能称为是内卷届的天花板
前言 公司前段缺人,也面了不少测试,结果竟然没有一个合适的。一开始瞄准的就是中级的水准,也没指望来大牛,提供的薪资也不低,面试的人很多,但平均水平很让人失望。令我印象最深的是一个00后测试员…...

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

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

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

C++ 之指针
文章目录参考描述指针运算符地址运算符奇偶分体指针的创建间接寻址运算符句点运算符运算符优先级问题箭头运算符运算符优先级指针野指针空指针通用指针解引用分析指针的算术运算加减运算自增运算与自减运算比较运算指针与常量指针常量常量指针指向常量的指针常量指针与数组数组…...

数据结构与算法---JS与栈
前言js里,是没有栈这种原生的数据结构。但是我们可以通过自定义创建栈类,来实现对添加/删除元素时更多的控制。创建栈类// 初始化一个基于数组的栈类 class Stack {constructor() {this.items [];} }为什么我们要选择数组作为栈类的存储数据类型&#x…...

HDLBits: 在线学习 SystemVerilog(二十三)-Problem 158-162(找BUG)
HDLBits: 在线学习 SystemVerilog(二十三)-Problem 158-162(找BUG)HDLBits 是一组小型电路设计习题集,使用 Verilog/SystemVerilog 硬件描述语言 (HDL) 练习数字硬件设计~网址如下:https://hdlbits.01xz.ne…...

国外SEO升级攻略:如何应对搜索引擎算法变化?
搜索引擎优化(SEO)是一个动态的领域,搜索引擎的算法经常会发生变化,这意味着SEO专业人员需要保持更新的技术知识和策略, 以适应变化并提高网站的排名。 以下是一些应对搜索引擎算法变化的升级攻略: 创造…...

X.509证书
证书格式ASN.1是一种抽象的数据结构,描述了复杂的对象,以及对象之间的关系。证书本质上是一个文件,需要一种专门的格式,才能在互联网中传输,证书需要通过一个规则将ASN.1转换为二进制文件,这就需要对证书以…...

高等数学——微分方程
文章目录概念一阶微分方程可降阶的微分方程高阶线性微分方程线性微分方程解的结构常系数齐次线性微分方程常系数非齐次线性微分方程概念 微分方程:含有未知函数的导数或微分的方程称为微分方程。微分方程的阶:微分方程中所出现的未知函数最高阶导数的阶…...

JAVA小记-生成PDF文件
项目场景: 例如:项目中需要生成PDF文件 项目使用情况 1、引入pom.xml <!--pdf相关依赖--> <dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13</version> </dependency>…...

Noah-MP陆面过程模型建模方法与站点、区域模拟
陆表过程的主要研究内容以及陆面模型在生态水文研究中的地位和作用 熟悉模型的发展历程,常见模型及各自特点; Noah-MP模型的原理 Noah-MP模型所需的系统环境与编译环境的搭建方法您都了解吗?? linux系统操作环境您熟悉吗&…...

全国青少年软件编程(Scratch)等级考试一级真题——2019.9
青少年软件编程(Scratch)等级考试试卷(一级)分数:100 题数:37一、单选题(共25题,每题2分,共50分)1.小明在做一个采访的小动画,想让主持人角色说“大家好!”3秒…...

第十四届蓝桥杯三月真题刷题训练——第 6 天
目录 第 1 题:星期计算 问题描述 运行限制 代码: 第 2 题:考勤刷卡 问题描述 输入格式 输出格式 样例输入 样例输出 评测用例规模与约定 运行限制 代码: 第 3 题:卡片 问题描述 输入格式 输出格式 样…...