本文的目的:

使用者在程序运行期间,可以动态的写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

  1. package com.ths.platform.framework.dynamic;
  2.  
  3. import javax.tools.SimpleJavaFileObject;
  4. import java.net.URI;
  5.  
  6. /**
  7. * 用于将java源码保存在content属性中
  8. */
  9. public class CharSequenceJavaFileObject extends SimpleJavaFileObject {
  10.  
  11. /**
  12. * 保存java code
  13. */
  14. private String content;
  15.  
  16. /**
  17. * 调用父类构造器,并设置content
  18. * @param className
  19. * @param content
  20. */
  21. public CharSequenceJavaFileObject(String className, String content){
  22. super(URI.create("string:///" + className.replace('.', '/')
  23. + Kind.SOURCE.extension), Kind.SOURCE);
  24. this.content = content;
  25. }
  26.  
  27. /**
  28. * 实现getCharContent,使得JavaCompiler可以从content获取java源码
  29. * @param ignoreEncodingErrors
  30. * @return
  31. */
  32. @Override
  33. public String getCharContent(boolean ignoreEncodingErrors) {
  34. return content;
  35. }
  36. }

ClassFileManager.java

  1. package com.ths.platform.framework.dynamic;
  2.  
  3. import java.io.IOException;
  4.  
  5. import javax.tools.*;
  6.  
  7. /**
  8. * 类文件管理器
  9. * 用于JavaCompiler将编译好后的class,保存到jclassObject中
  10. */
  11. public class ClassFileManager extends ForwardingJavaFileManager {
  12.  
  13. /**
  14. * 保存编译后Class文件的对象
  15. */
  16. private JavaClassObject jclassObject;
  17.  
  18. /**
  19. * 调用父类构造器
  20. * @param standardManager
  21. */
  22. public ClassFileManager(StandardJavaFileManager standardManager) {
  23. super(standardManager);
  24. }
  25.  
  26. /**
  27. * 将JavaFileObject对象的引用交给JavaCompiler,让它将编译好后的Class文件装载进来
  28. * @param location
  29. * @param className
  30. * @param kind
  31. * @param sibling
  32. * @return
  33. * @throws IOException
  34. */
  35. @Override
  36. public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling)
  37. throws IOException {
  38. if (jclassObject == null)
  39. jclassObject = new JavaClassObject(className, kind);
  40. return jclassObject;
  41. }
  42.  
  43. public JavaClassObject getJavaClassObject() {
  44. return jclassObject;
  45. }
  46. }

JavaClassObject.java

  1. package com.ths.platform.framework.dynamic;
  2.  
  3. import javax.tools.SimpleJavaFileObject;
  4. import java.io.ByteArrayOutputStream;
  5. import java.io.IOException;
  6. import java.io.OutputStream;
  7. import java.net.URI;
  8. /**
  9. * 将输出流交给JavaCompiler,最后JavaCompiler将编译后的class文件写入输出流中
  10. */
  11. public class JavaClassObject extends SimpleJavaFileObject {
  12.  
  13. /**
  14. * 定义一个输出流,用于装载JavaCompiler编译后的Class文件
  15. */
  16. protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();
  17.  
  18. /**
  19. * 调用父类构造器
  20. * @param name
  21. * @param kind
  22. */
  23. public JavaClassObject(String name, Kind kind) {
  24. super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
  25. }
  26.  
  27. /**
  28. * 获取输出流为byte[]数组
  29. * @return
  30. */
  31. public byte[] getBytes() {
  32. return bos.toByteArray();
  33. }
  34.  
  35. /**
  36. * 重写openOutputStream,将我们的输出流交给JavaCompiler,让它将编译好的Class装载进来
  37. * @return
  38. * @throws IOException
  39. */
  40. @Override
  41. public OutputStream openOutputStream() throws IOException {
  42. return bos;
  43. }
  44.  
  45. /**
  46. * 重写finalize方法,在对象被回收时关闭输出流
  47. * @throws Throwable
  48. */
  49. @Override
  50. protected void finalize() throws Throwable {
  51. super.finalize();
  52. bos.close();
  53. }
  54. }

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

  1. package com.ths.platform.framework.dynamic;
  2.  
  3. import java.io.File;
  4. import java.net.URL;
  5. import java.net.URLClassLoader;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8.  
  9. import javax.tools.Diagnostic;
  10. import javax.tools.DiagnosticCollector;
  11. import javax.tools.JavaCompiler;
  12. import javax.tools.JavaFileObject;
  13. import javax.tools.ToolProvider;
  14.  
  15. /**
  16. * 在Java SE6中最好的方法是使用StandardJavaFileManager类。
  17. * 这个类可以很好地控制输入、输出,并且可以通过DiagnosticListener得到诊断信息,
  18. * 而DiagnosticCollector类就是listener的实现。
  19. * 使用StandardJavaFileManager需要两步。
  20. * 首先建立一个DiagnosticCollector实例以及通过JavaCompiler的getStandardFileManager()方法得到一个StandardFileManager对象。
  21. * 最后通过CompilationTask中的call方法编译源程序。
  22. */
  23. public class DynamicEngine {
  24. //单例
  25. private static DynamicEngine ourInstance = new DynamicEngine();
  26.  
  27. public static DynamicEngine getInstance() {
  28. return ourInstance;
  29. }
  30. private URLClassLoader parentClassLoader;
  31. private String classpath;
  32. private DynamicEngine() {
  33. //获取类加载器
  34. this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();
  35.  
  36. //创建classpath
  37. this.buildClassPath();
  38. }
  39.  
  40. /**
  41. * @MethodName : 创建classpath
  42. */
  43. private void buildClassPath() {
  44. this.classpath = null;
  45. StringBuilder sb = new StringBuilder();
  46. for (URL url : this.parentClassLoader.getURLs()) {
  47. String p = url.getFile();
  48. sb.append(p).append(File.pathSeparator);
  49. }
  50. this.classpath = sb.toString();
  51. }
  52.  
  53. /**
  54. * @MethodName : 编译java代码到Object
  55. * @Description : TODO
  56. * @param fullClassName 类名
  57. * @param javaCode 类代码
  58. * @return Object
  59. * @throws Exception
  60. */
  61. public Object javaCodeToObject(String fullClassName, String javaCode) throws Exception {
  62. long start = System.currentTimeMillis(); //记录开始编译时间
  63. Object instance = null;
  64. //获取系统编译器
  65. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  66. // 建立DiagnosticCollector对象
  67. DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
  68.  
  69. // 建立用于保存被编译文件名的对象
  70. // 每个文件被保存在一个从JavaFileObject继承的类中
  71. ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));
  72.  
  73. List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
  74. jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode));
  75.  
  76. //使用编译选项可以改变默认编译行为。编译选项是一个元素为String类型的Iterable集合
  77. List<String> options = new ArrayList<String>();
  78. options.add("-encoding");
  79. options.add("UTF-8");
  80. options.add("-classpath");
  81. options.add(this.classpath);
  82.  
  83. JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
  84. // 编译源程序
  85. boolean success = task.call();
  86.  
  87. if (success) {
  88. //如果编译成功,用类加载器加载该类
  89. JavaClassObject jco = fileManager.getJavaClassObject();
  90. DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);
  91. Class clazz = dynamicClassLoader.loadClass(fullClassName,jco);
  92. instance = clazz.newInstance();
  93. } else {
  94. //如果想得到具体的编译错误,可以对Diagnostics进行扫描
  95. String error = "";
  96. for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
  97. error += compilePrint(diagnostic);
  98. }
  99. }
  100. long end = System.currentTimeMillis();
  101. System.out.println("javaCodeToObject use:"+(end-start)+"ms");
  102. return instance;
  103. }
  104.  
  105. /**
  106. * @MethodName : compilePrint
  107. * @Description : 输出编译错误信息
  108. * @param diagnostic
  109. * @return
  110. */
  111. private String compilePrint(Diagnostic diagnostic) {
  112. System.out.println("Code:" + diagnostic.getCode());
  113. System.out.println("Kind:" + diagnostic.getKind());
  114. System.out.println("Position:" + diagnostic.getPosition());
  115. System.out.println("Start Position:" + diagnostic.getStartPosition());
  116. System.out.println("End Position:" + diagnostic.getEndPosition());
  117. System.out.println("Source:" + diagnostic.getSource());
  118. System.out.println("Message:" + diagnostic.getMessage(null));
  119. System.out.println("LineNumber:" + diagnostic.getLineNumber());
  120. System.out.println("ColumnNumber:" + diagnostic.getColumnNumber());
  121. StringBuffer res = new StringBuffer();
  122. res.append("Code:[" + diagnostic.getCode() + "]\n");
  123. res.append("Kind:[" + diagnostic.getKind() + "]\n");
  124. res.append("Position:[" + diagnostic.getPosition() + "]\n");
  125. res.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");
  126. res.append("End Position:[" + diagnostic.getEndPosition() + "]\n");
  127. res.append("Source:[" + diagnostic.getSource() + "]\n");
  128. res.append("Message:[" + diagnostic.getMessage(null) + "]\n");
  129. res.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");
  130. res.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");
  131. return res.toString();
  132. }
  133. }

DynamicClassLoader.java

  1. package com.ths.platform.framework.dynamic;
  2.  
  3. import java.net.URLClassLoader;
  4. import java.net.URL;
  5.  
  6. /**
  7. * 自定义类加载器
  8. */
  9. public class DynamicClassLoader extends URLClassLoader {
  10. public DynamicClassLoader(ClassLoader parent) {
  11. super(new URL[0], parent);
  12. }
  13.  
  14. public Class findClassByClassName(String className) throws ClassNotFoundException {
  15. return this.findClass(className);
  16. }
  17.  
  18. public Class loadClass(String fullName, JavaClassObject jco) {
  19. byte[] classData = jco.getBytes();
  20. return this.defineClass(fullName, classData, 0, classData.length);
  21. }
  22. }

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

  1. package com.ths.platform.framework.dynamic;
  2.  
  3. import sun.misc.IOUtils;
  4.  
  5. import java.io.File;
  6. import java.io.FileInputStream;
  7. import java.io.InputStream;
  8.  
  9. public class DynaCompTest
  10. {
  11. public static void main(String[] args) throws Exception {
  12. String fullName = "com.seeyon.proxy.MyClass";
  13. File file = new File("/Users/yangyu/Downloads/myclass");
  14. InputStream in = new FileInputStream(file);
  15. byte[] bytes = IOUtils.readFully(in, -1, false);
  16. String src = new String(bytes);
  17. in.close();
  18.  
  19. System.out.println(src);
  20. DynamicEngine de = DynamicEngine.getInstance();
  21. Object instance = de.javaCodeToObject(fullName,src.toString());
  22. System.out.println(instance);
  23. }
  24. }

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

  1. package com.seeyon.proxy;
  2.  
  3. public class MyClass {
  4.  
  5. public String say(String str){
  6. return "hello"+str;
  7. }
  8. }

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. Aspose.Words 16.8 破解版、添加自定义HTML导出Jpeg压缩质量配置

    0x01 Aspose.Words 介绍Aspose.Words是一个商业.NET类库,可以使得应用程序处理大量的文件任务.Aspose.Words支持Doc,Docx,RTF,HTML,OpenDo ...

  2. jquery中bind()绑定多个事件

    bind()绑定事件 $(selector).bind(event,data,function): 参数event为事件名称(如"click,mouseover....."),da ...

  3. ESP8266刷AT固件与nodemcu固件

    这回是使用的这一款 因为这款默认的是支持AT指令的固件,,所以我们就刷nodemcu的 先看接线 GPIO0 默认是工作模式(不接线).如果接了低电平就是下载模式(给模块刷固件!!)所以接低电平.CH ...

  4. 无法启动WP Emulator

    记得以前Vware不能运行设置的东西了吗?http://www.cnblogs.com/dunitian/p/4480750.html 如果不清楚可以参考上面的链接 重启的时候选择第二项 重新打开就o ...

  5. OpenCASCADE General Transformation

    OpenCASCADE General Transformation eryar@163.com Abstract. OpenCASCADE provides a general transforma ...

  6. php语句

    判断变量的方法: 例: $a=""; var_dump(empty($a)); 输出的结果为true 若$a=10; var_dump(empty($a)); 输出falst 若没 ...

  7. 配置 L2 Population - 每天5分钟玩转 OpenStack(114)

    前面我们学习了L2 Population 的原理,今天讨论如何在 Neutron 中配置和启用此特性. 目前 L2 Population 支持 VXLAN with Linux bridge 和 VX ...

  8. ERROR 1819 (HY000): Your password does not satisfy the current policy requirements

    为了加强安全性,MySQL5.7为root用户随机生成了一个密码,在error log中,关于error log的位置,如果安装的是RPM包,则默认是/var/log/mysqld.log. 一般可通 ...

  9. ModelState.IsValid总为false原因

    总结在开发中遇到的一个问题 ModelState.IsValid 一直是false 且在局部变量中,没有发现有问题啊,Model非常正常有木有,可是为什么 ModelState.IsValid 总是f ...

  10. [Keras] mnist with cnn

    典型的卷积神经网络. Keras傻瓜式读取数据:自动下载,自动解压,自动加载. # X_train: array([[[[ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0. ...