Java--自定义Class并且在内存中编译,加载,实例化

时间:2022-12-18 21:33:13

本文的目的:

使用者在程序运行期间,可以动态的写Java Class,不需要生成任何.Class文件就可以完全在内存中编译,加载,实例化。

1、需要用到的组件介绍

1)JavaCompiler:用于编译Java Code。

2)CharSequenceJavaFileObject:用于保存Java Code,提供方法给JavaCompiler获取String形式的Java Code。

3)ClassFileManager:用于JavaCompiler将编译好后的Class文件保存在指定对象中。

4)JavaClassObject:ClassFileManager告诉JavaCompiler需要将Class文件保存在JavaClassObject中,但是由JavaClassObject来决定最终以byte流来保存数据。

5)DynamicClassLoader:自定义类加载器,用于加载最后的二进制Class

2、源码展现:

CharSequenceJavaFileObject.java

package com.ths.platform.framework.dynamic;

import javax.tools.SimpleJavaFileObject;
import java.net.URI; /**
* 用于将java源码保存在content属性中
*/
public class CharSequenceJavaFileObject extends SimpleJavaFileObject { /**
* 保存java code
*/
private String content; /**
* 调用父类构造器,并设置content
* @param className
* @param content
*/
public CharSequenceJavaFileObject(String className, String content){
super(URI.create("string:///" + className.replace('.', '/')
+ Kind.SOURCE.extension), Kind.SOURCE);
this.content = content;
} /**
* 实现getCharContent,使得JavaCompiler可以从content获取java源码
* @param ignoreEncodingErrors
* @return
*/
@Override
public String getCharContent(boolean ignoreEncodingErrors) {
return content;
}
}

ClassFileManager.java

package com.ths.platform.framework.dynamic;

import java.io.IOException;

import javax.tools.*;

/**
* 类文件管理器
* 用于JavaCompiler将编译好后的class,保存到jclassObject中
*/
public class ClassFileManager extends ForwardingJavaFileManager { /**
* 保存编译后Class文件的对象
*/
private JavaClassObject jclassObject; /**
* 调用父类构造器
* @param standardManager
*/
public ClassFileManager(StandardJavaFileManager standardManager) {
super(standardManager);
} /**
* 将JavaFileObject对象的引用交给JavaCompiler,让它将编译好后的Class文件装载进来
* @param location
* @param className
* @param kind
* @param sibling
* @return
* @throws IOException
*/
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling)
throws IOException {
if (jclassObject == null)
jclassObject = new JavaClassObject(className, kind);
return jclassObject;
} public JavaClassObject getJavaClassObject() {
return jclassObject;
}
}

JavaClassObject.java

package com.ths.platform.framework.dynamic;

import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
/**
* 将输出流交给JavaCompiler,最后JavaCompiler将编译后的class文件写入输出流中
*/
public class JavaClassObject extends SimpleJavaFileObject { /**
* 定义一个输出流,用于装载JavaCompiler编译后的Class文件
*/
protected final ByteArrayOutputStream bos = new ByteArrayOutputStream(); /**
* 调用父类构造器
* @param name
* @param kind
*/
public JavaClassObject(String name, Kind kind) {
super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
} /**
* 获取输出流为byte[]数组
* @return
*/
public byte[] getBytes() {
return bos.toByteArray();
} /**
* 重写openOutputStream,将我们的输出流交给JavaCompiler,让它将编译好的Class装载进来
* @return
* @throws IOException
*/
@Override
public OutputStream openOutputStream() throws IOException {
return bos;
} /**
* 重写finalize方法,在对象被回收时关闭输出流
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
bos.close();
}
}

DynamicEngine.java(职责:使用JavaCompiler编译Class,并且使用DynamicClassLoader加载Class)

package com.ths.platform.framework.dynamic;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List; import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider; /**
* 在Java SE6中最好的方法是使用StandardJavaFileManager类。
* 这个类可以很好地控制输入、输出,并且可以通过DiagnosticListener得到诊断信息,
* 而DiagnosticCollector类就是listener的实现。
* 使用StandardJavaFileManager需要两步。
* 首先建立一个DiagnosticCollector实例以及通过JavaCompiler的getStandardFileManager()方法得到一个StandardFileManager对象。
* 最后通过CompilationTask中的call方法编译源程序。
*/
public class DynamicEngine {
//单例
private static DynamicEngine ourInstance = new DynamicEngine(); public static DynamicEngine getInstance() {
return ourInstance;
}
private URLClassLoader parentClassLoader;
private String classpath;
private DynamicEngine() {
//获取类加载器
this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader(); //创建classpath
this.buildClassPath();
} /**
* @MethodName : 创建classpath
*/
private void buildClassPath() {
this.classpath = null;
StringBuilder sb = new StringBuilder();
for (URL url : this.parentClassLoader.getURLs()) {
String p = url.getFile();
sb.append(p).append(File.pathSeparator);
}
this.classpath = sb.toString();
} /**
* @MethodName : 编译java代码到Object
* @Description : TODO
* @param fullClassName 类名
* @param javaCode 类代码
* @return Object
* @throws Exception
*/
public Object javaCodeToObject(String fullClassName, String javaCode) throws Exception {
long start = System.currentTimeMillis(); //记录开始编译时间
Object instance = null;
//获取系统编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 建立DiagnosticCollector对象
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); // 建立用于保存被编译文件名的对象
// 每个文件被保存在一个从JavaFileObject继承的类中
ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null)); List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode)); //使用编译选项可以改变默认编译行为。编译选项是一个元素为String类型的Iterable集合
List<String> options = new ArrayList<String>();
options.add("-encoding");
options.add("UTF-8");
options.add("-classpath");
options.add(this.classpath); JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
// 编译源程序
boolean success = task.call(); if (success) {
//如果编译成功,用类加载器加载该类
JavaClassObject jco = fileManager.getJavaClassObject();
DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);
Class clazz = dynamicClassLoader.loadClass(fullClassName,jco);
instance = clazz.newInstance();
} else {
//如果想得到具体的编译错误,可以对Diagnostics进行扫描
String error = "";
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
error += compilePrint(diagnostic);
}
}
long end = System.currentTimeMillis();
System.out.println("javaCodeToObject use:"+(end-start)+"ms");
return instance;
} /**
* @MethodName : compilePrint
* @Description : 输出编译错误信息
* @param diagnostic
* @return
*/
private String compilePrint(Diagnostic diagnostic) {
System.out.println("Code:" + diagnostic.getCode());
System.out.println("Kind:" + diagnostic.getKind());
System.out.println("Position:" + diagnostic.getPosition());
System.out.println("Start Position:" + diagnostic.getStartPosition());
System.out.println("End Position:" + diagnostic.getEndPosition());
System.out.println("Source:" + diagnostic.getSource());
System.out.println("Message:" + diagnostic.getMessage(null));
System.out.println("LineNumber:" + diagnostic.getLineNumber());
System.out.println("ColumnNumber:" + diagnostic.getColumnNumber());
StringBuffer res = new StringBuffer();
res.append("Code:[" + diagnostic.getCode() + "]\n");
res.append("Kind:[" + diagnostic.getKind() + "]\n");
res.append("Position:[" + diagnostic.getPosition() + "]\n");
res.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");
res.append("End Position:[" + diagnostic.getEndPosition() + "]\n");
res.append("Source:[" + diagnostic.getSource() + "]\n");
res.append("Message:[" + diagnostic.getMessage(null) + "]\n");
res.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");
res.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");
return res.toString();
}
}

DynamicClassLoader.java

package com.ths.platform.framework.dynamic;

import java.net.URLClassLoader;
import java.net.URL; /**
* 自定义类加载器
*/
public class DynamicClassLoader extends URLClassLoader {
public DynamicClassLoader(ClassLoader parent) {
super(new URL[0], parent);
} public Class findClassByClassName(String className) throws ClassNotFoundException {
return this.findClass(className);
} public Class loadClass(String fullName, JavaClassObject jco) {
byte[] classData = jco.getBytes();
return this.defineClass(fullName, classData, 0, classData.length);
}
}

DynaCompTest.java(测试类,从myclass文件中读出源码并在内存中编译)

package com.ths.platform.framework.dynamic;

import sun.misc.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream; public class DynaCompTest
{
public static void main(String[] args) throws Exception {
String fullName = "com.seeyon.proxy.MyClass";
File file = new File("/Users/yangyu/Downloads/myclass");
InputStream in = new FileInputStream(file);
byte[] bytes = IOUtils.readFully(in, -1, false);
String src = new String(bytes);
in.close(); System.out.println(src);
DynamicEngine de = DynamicEngine.getInstance();
Object instance = de.javaCodeToObject(fullName,src.toString());
System.out.println(instance);
}
}

/Users/yangyu/Downloads/myclass文件(这里使用文件,实际也可以在程序中直接拼凑String)

package com.seeyon.proxy;

public class MyClass {

    public String say(String str){
return "hello"+str;
}
}

Java--自定义Class并且在内存中编译,加载,实例化的更多相关文章

  1. Qt 5&period;2中编译加载MySQL数据库驱动问题的总结

    背景: 本科毕业设计涉及图形界面与数据库查询.选择使用Qt实现图形界面编程,使用MySQL构建数据库.之前安装了Qt 5.2,后来又安装了MySQL Server 5.6 (FULL完全安装).接着就 ...

  2. 【Java面试题】解释内存中的栈(stack)、堆&lpar;heap&rpar;和静态存储区的用法

    Java面试题:解释内存中的栈(stack).堆(heap)和静态存储区的用法 堆区: 专门用来保存对象的实例(new 创建的对象和数组),实际上也只是保存对象实例的属性值,属性的类型和对象本身的类型 ...

  3. Java 错误:找不到或无法加载主类(源文件中含有包名 package)

    1. 问题定位 编译(javac)和执行(java)java 程序时,出现这种类型的错误:找不到或无法加载主类: 首先排除是否是环境变量配置不当造成的问题,只要保证,命令行界面能够识别 javac/j ...

  4. Tomcat启动时加载数据到缓存---web&period;xml中listener加载顺序(例如顺序:1、初始化spring容器,2、初始化线程池,3、加载业务代码,将数据库中数据加载到内存中)

    最近公司要做功能迁移,原来的后台使用的Netty,现在要迁移到在uap上,也就是说所有后台的代码不能通过netty写的加载顺序加载了. 问题就来了,怎样让迁移到tomcat的代码按照原来的加载顺序进行 ...

  5. 某APK中使用了动态注册BroadcastReceiver,Launcher中动态加载此APK出现java&period;lang&period;SecurityException异常的解决方法

    在某APK中,通过如下方法动态注册了一个BroadcastReceiver,代码参考如下: @Override protected void onAttachedToWindow() { super. ...

  6. 运行Java cmd程序 找不到或无法加载主类怎么解决

    //这个问题原因有以下几种,但是和环境变量并没有太大的关系 //能够执行java 和 javac 就证明你的环境变量已经配置好了,其实 classpath 可以不配置 //假如有如下文件:H:\cod ...

  7. 在MVC应用程序中动态加载PartialView

    原文:在MVC应用程序中动态加载PartialView 有时候,我们不太想把PartialView直接Render在Html上,而是使用jQuery来动态加载,或是某一个事件来加载.为了演示与做好这个 ...

  8. cocos2dx lua中异步加载网络图片,可用于显示微信头像

    最近在做一个棋牌项目,脚本语言用的lua,登录需要使用微信登录,用户头像用微信账户的头像,微信接口返回的头像是一个url,那么遇到的一个问题就是如何在lua中异步加载这个头像,先在引擎源码里找了下可能 ...

  9. 在mybatis 中批量加载mapper&period;xml

    可以直接加载一个包文件名,将这个包里的所有*mapper.xml文件加载进来. 指定mapper接口的包名,mybatis自动扫描包下边所有mapper接口进行加载: 必须按一定的标准:即xml文件和 ...

随机推荐

  1. BZOJ4373 &colon; 算术天才⑨与等差数列

    设$pre[i]$表示第$i$个数上一次出现的位置,$d[i]=abs(a[i]-a[i+1])$. 用线段树维护区间内$a$的最小值.最大值,$pre$的最大值以及$d$的$\gcd$. 对于询问$ ...

  2. MAVEN ERROR&colon; unable to find valid certification path to requested target 解决办法

    第一次使用MAVEN编译项目,出现如下错误 解决办法:Maven的setting.xml中添加如下代码 <mirrors> <mirror> <id>Central ...

  3. 单表多次join的sql

    select o1.emp_name as 员工姓名1 , o2.emp_name as 员工姓名2 from tableTest join employee o1 on tabletest.[Emp ...

  4. OpenSSH 高级运用两则

    00×0.相关介绍 OpenSSH(OpenBSD Secure Shell)使用 SSH 通过计算机网络加密通信的实现. 它是替换由 SSH Communications Security 所提供的 ...

  5. 五种js判断是否为整数(转)

    五种js判断是否为整数类型方式 作者:snandy 这篇文章主要介绍了五种JavaScript判断是否为整数类型方式,需要的朋友可以参考下   这篇看看如何判断为整数类型(Integer),JavaS ...

  6. C语言--第0次作业

    1.你认为大学的学习生活.同学关系.师生应该是怎样?请一个个展开描写. 学习生活 大学是一个过渡时间,它不同于高中与社会.我希望自己可以养成自主学习的习惯,也希望能在大学学习中找到属于自己的节奏,不被 ...

  7. 【C&num;复习总结】垃圾回收机制&lpar;GC&rpar;1

    摘要:今天我们漫谈C#中的垃圾回收机制,本文将从垃圾回收机制的原理讲起,希望对大家有所帮助. GC的前世与今生 虽然本文是以.NET作为目标来讲述GC,但是GC的概念并非才诞生不久.早在1958年,由 ...

  8. PHP——emjoin表情存入数据库

    前言 还有一种解决的方法是更改数据库,这里就不写了,这里直接对emoji进行转码 代码 mb_strlen() | strlen() | rawurlencode() | rawurldecode() ...

  9. numpy函数:&lbrack;1&rsqb;shape用法

    shape函数是numpy.core.fromnumeric中的函数,它的功能是读取矩阵的长度,比如shape[0]就是读取矩阵第一维度的长度.它的输入参数可以使一个整数表示维度,也可以是一个矩阵.

  10. jQuery实现表格行上移下移和置顶

    jQuery实现表格行上移下移和置顶 我们在操作列表数据的时候,需要将数据行排列顺序进行调整,如上移和下移行,将行数据置顶等,这些操作都可以在前端通过点击按钮来完成,并且伴随着简单的动态效果,轻松实现 ...