自助外贸网站建设,网站建设企业需要准备资料,自助免费网站制作,展厅设计行业平台文章目录1. 作为程序员的最高追求2.如何实现动态编译2.1 生成源码2.2 调用编译器API对Test源码文件进行编译生成字节码2.3 调用类加载器对字节码进行加载得到Class对象2.4 使用Class对象创建对象进行使用3. Java编译API学习4. 类加载机制4.1 类加载过程4.2 类加载器的层次结构4…
文章目录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(result0){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(result0){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