本文的目的:

使用者在程序运行期间,可以动态的写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.2中编译加载MySQL数据库驱动问题的总结

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

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

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

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

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

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

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

  5. 某APK中使用了动态注册BroadcastReceiver,Launcher中动态加载此APK出现java.lang.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.xml

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

随机推荐

  1. eclipse启动时报告错误:Java was started but returned exit code=-805306369

    这两天也没改过eclipse和java的配置,但eclipse启动时报告错误:Java was started but returned exit code=-805306369 后来在eclipse ...

  2. 修改windows自带的Ctrl+Space输入法切换快捷键

    使用场景: 多为我等码农使用一些编辑器时,编辑器的默认代码提示热键为 ctrl+space ,但这个热键被系统的输入法开关占用.如果遇到可以设置快捷键的编辑器还好,要是不能设置的话(比如火狐浏览器的代 ...

  3. Base64编码原理分析

    Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,在了解Base64编码之前,先了解几个基本概念:位.字节. 位:"位(bit)"是计算机中最小的数据单位.每一位 ...

  4. salesforce 零基础学习(四十五)Approval Lock & UnLock相关注意事项

    我们都知道,当一条记录进入审批流程以后会自动加锁,apex提供Approval类的lock和unlock方法可以让我们使用代码对记录进行加锁和解锁. 项目中遇到一个需求,需要当某种情况下对记录进行先解 ...

  5. java compiler level does not match the version of the installed java project facet 解决方案

    项目出现 java compiler level does not match the version of the installed java project facet 错误,一般是项目移植出现 ...

  6. Pyhton 利用threading远程下发文件和远程执行linux系统命令

    #!/usr/bin/env python # encoding: utf-8 #__author__ = 'cp' #__date__ = '21/07/16 上午 10:32' import th ...

  7. 应用Web.Config配置网站

    1.配置数据库连接 在ASP.NET中配置数据库连接的两种方式: appSettings和connectionStrings 命名空间: using System.Configuration; 1)a ...

  8. Trace Flag

    Trace Flag能够影响Sql Server的行为,主要用于diagnose performance issue,官方解释是: Trace flags are used to temporaril ...

  9. SVN代码冲突解决方案小集合

    对于刚接触svn的人来说,svn冲突后,不能提交是件让人很郁闷的事情.最让人郁闷的事,是代码间的覆盖.你把我代码盖了,我会很火大的.谁把谁的盖了都不爽. 为什么会出现代码冲突问题呢,因为不同的人,同时 ...

  10. 【SQL】SQL Server登录常见问题

    很多人SQL装的很不规范,导致各种问题或者说时间长了,密码给忘记了,怎么办呢?重装? 逆天就拿几个比较常见的来说事吧,首先是各种设置的问题:(我家里用的是简易版,你们开发最好用企业版)         ...