当前位置: 首页 > news >正文

动态编译执行java类

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

    最近几天看了一些热加载方面的资料,然后动手搭建了个基本框架,暂时实现了有限制性的热加载功能,先和大家分享一下

    整个框架结构如下图所示:

功能的实现上,主要分为2步:
     1. 编译.java文件,生成.class文件。
         这里我使用了eclipse的编译器类 
EclipseCompiler 来实现

     2.加载编译后的.class文件。
         自定义一个类加载器,每次热更新都新建一个类加载器实例来实现。java传统的类加载是走双亲委派模式,当要加载一个类的时候,需要先由自身的父加载器来加载,只有父加载器还未加载过该类,才能由自身来加载。(我们也可以覆盖ClassLoader.loadClass()方法,打破这个规则)。由于不同的类加载器实例具有独立的空间,而且处于同一继承等级,具有同一个父加载器,这就确保了实例A加载的类,实例B也能重新加载,互不影响。

好了,下面就看代码实现吧。

package hotswap.test;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import hotswap.HotswapEngine;

public class Test {

	/**
	 * @author Nate
	 * @date 2013-11-19
	 */
	public static void main(String[] args) throws Exception {
		//需要热加载的类路径
		final String classPath = System.getProperty("user.dir") + "/hotcodes";
		//需要热加载执行的类源文件路径
		final String javaFilePath = classPath + "/codes/A.java";
		
		HotswapEngine engine = new HotswapEngine();
		
		//第一次加载执行
		Class<?> clazz1 = engine.reload(classPath, javaFilePath);
		evalMainMethod(clazz1);
		
		System.err.println("==> 请修改文件,然后按任意键重新加载");
		System.in.read();
		
		//修改代代码后,再次加载执行
		Class<?> clazz2 = engine.reload(classPath, javaFilePath);
		evalMainMethod(clazz2);
	}
	
	/**
	 * 执行指定类的main方法
	 * 
	 * @author Nate
	 * @date 2013-11-19
	 */
	public static void evalMainMethod(Class<?> clazz, String...args) {
		try {
			Method mainMethod = clazz.getMethod("main",
					new Class[] { String[].class });
			
			if (mainMethod == null) return;
			
			int modifiers = mainMethod.getModifiers();
			if (!Modifier.isPublic(modifiers) ||
					!Modifier.isStatic(modifiers))
				return;
			
			mainMethod.invoke(null, new Object[]{args});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
package hotswap;

import hotswap.JavaFileManager.JavaSourceObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.JavaCompiler.CompilationTask;
import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;

public class HotswapEngine {
	
	private final JavaCompiler compiler = new EclipseCompiler();
	
	public Class<?> reload(String classPath, String javaFilePath) {
		try {
			File javaFile = new File(javaFilePath);
			if (!javaFile.exists() || !javaFile.isFile())
				return null;
			//文件名
			String fileName = javaFile.getName();
			//完全类名
			String classFullName = javaFilePath.substring(classPath.length() + 1, javaFilePath.lastIndexOf('.'));
			classFullName = classFullName.replace('/', '.').replace('\\', '.');
			
			//编译选项
			List<String> options = new ArrayList<String>();
			options.add("-warn:-enumSwitch");
			options.add("-g");
			options.add("-deprecation");
			options.add("-1.7");
			options.add("-encoding");
			options.add("UTF-8");
			//options.add("-sourcepath");
			//options.add(classPath);
			options.add("-classpath");
			options.add(classPath);
			
			//java文件管理器
			JavaFileManager fileManager = new JavaFileManager();
			
			//编译单元集合
			List<JavaFileObject> compilationUnits = new ArrayList<JavaFileObject>(1);
			//加载java文件源码
			String sourceCode = loadJavaSourceCode(javaFile);
			//生成java源码对象
			JavaSourceObject sourceObj = JavaFileManager.newJavaSourceObject(fileName, sourceCode);
			compilationUnits.add(sourceObj);
			
			//执行编译任务
			CompilationTask task = compiler.getTask(null, fileManager, null, options, null, compilationUnits);
			if (!task.call()) {
				//编译失败
				return null;
			}
			
			//获取编译后的class文件
			Map<String, byte[]> classBytes = fileManager.getClassBytes();
			fileManager.close();
			
			//加载class文件
			HotswapClassLoader loader = new HotswapClassLoader(classBytes, classPath, ClassLoader.getSystemClassLoader());
			Class<?> clazz = loader.findClass(classFullName);
			return clazz;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static String loadJavaSourceCode(File javaFile) {
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(javaFile)));
			char[] arr = new char[8 * 1024];
			StringBuilder buf = new StringBuilder();
			int numChars;
			while ((numChars = reader.read(arr, 0, arr.length)) > 0) {
				buf.append(arr, 0, numChars);
			}
			return buf.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}

}


package hotswap;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

public class HotswapClassLoader extends URLClassLoader {
	
	private final Map<String, byte[]> classBytes;
	
	public HotswapClassLoader(Map<String, byte[]> classBytes, String classPath, ClassLoader parent) {
		super(toURLs(classPath), parent);
		this.classBytes = classBytes;
	}

	@Override
	protected Class<?> findClass(String className) throws ClassNotFoundException {
		//先从我们编译后的class文件缓存里面找
		byte buf[] = classBytes.get(className);
		if (buf != null) {
			classBytes.put(className, null);
			return defineClass(className, buf, 0, buf.length);
		}
		return super.findClass(className);
	}
	
	private static URL[] toURLs(String classPath) {
		if (classPath == null) {
			return new URL[0];
		}
		
		List<URL> list = new ArrayList<URL>();
		StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator);
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			File file = new File(token);
			if (file.exists()) {
				try {
					list.add(file.toURI().toURL());
				} catch (MalformedURLException mue) {}
			} else {
				try {
					list.add(new URL(token));
				} catch (MalformedURLException mue) {}
			}
		}
		
		URL res[] = new URL[list.size()];
		list.toArray(res);
		return res;
	}

}


package hotswap;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.Map;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.JavaFileObject.Kind;
import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager;

public class JavaFileManager extends EclipseFileManager {
	//保存已经重新编译的class文件
	protected Map<String, byte[]> classBytes;

	public JavaFileManager() {
		super(null, null);
		classBytes = new HashMap<String, byte[]>();
	}
	
	public Map<String, byte[]> getClassBytes() {
		return classBytes;
	}

	@Override
	public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling)
			throws IOException {
		if (kind == Kind.CLASS) {
			return new JavaClassObject(className.replace('/', '.'));
		}
		return super.getJavaFileForOutput(location, className, kind, sibling);
	}
	
	/**
	 * 新建一个java源文件对象
	 * 
	 * @author Nate
	 * @param name 文件名
	 * @param source java源码字符串
	 * 
	 * @date 2013-11-21
	 */
	public static JavaSourceObject newJavaSourceObject(String name, String source) {
		return new JavaSourceObject(name, source);
	}
	
	/**
	 * java class文件对象(.class)
	 * 
	 * @author Nate
	 * @date 2013-11-21
	 */
	public class JavaClassObject extends SimpleJavaFileObject {
		protected final String name;

		protected JavaClassObject(String name) {
			super(toURI(name), Kind.CLASS);
			this.name = name;
		}
		
		/**
		 * 编译器编译时回调, 这里我们用自己的输出流来接受编译后的class文件
		 */
		@Override
		public OutputStream openOutputStream() throws IOException {
			
			return new FilterOutputStream(new ByteArrayOutputStream()) {
				@Override
				public void close() throws IOException {
					out.close();
					ByteArrayOutputStream bos = (ByteArrayOutputStream)out;
					classBytes.put(name, bos.toByteArray());
				}
			};
		}
	}
	
	/**
	 * java 源文件对象(.java)
	 * 
	 * @author Nate
	 * @date 2013-11-21
	 */
	public static class JavaSourceObject extends SimpleJavaFileObject {
		final String source;
		
		JavaSourceObject(String name, String source) {
			super(toURI(name), Kind.SOURCE);
			this.source = source;
		}
		
		@Override
		public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
			return CharBuffer.wrap(source);
		}
	}
	
	static final String JAVA_FILE_EXT = ".java";
	static URI toURI(String name) {
		File file = new File(name);
		if (file.exists()) {
			return file.toURI();
		}
		try {
			final StringBuilder newUri = new StringBuilder();
			newUri.append("file:///");
			newUri.append(name.replace('.', '/'));
			if (name.endsWith(JAVA_FILE_EXT)) {
				newUri.replace(newUri.length() - JAVA_FILE_EXT.length(), newUri.length(), JAVA_FILE_EXT);
			}
			return URI.create(newUri.toString());
		} catch (Exception exp) {
			return null;
		}
	}

}


package codes;

public class A {

	public static void main(String args[]) {
		System.out.println("aaaaaaaaaaaaaaaaa");
		//System.out.println("AAAAAAAAAAAAAAAAA");
		
		//看看引用的类能不能也热更新
		new B().say();
	}

}


package codes;

public class B {
	
	public void say() {
		System.out.println("bbbbbbbbbbbbbbbbb");
		//System.out.println("BBBBBBBBBBBBBBBBB");
	}

}


运行测试类的结果:


这里有个地方要注意一下的,需要将主代码和需要热加载的代码分开来放,并且要把主代码运行的类路径设为只包含主代码的文件夹,不能让主代码的类路径覆盖了主代码和需要热加载的代码,不然的话,新的加载器实例会加载不了,因为都被父加载器先加载了。 另外还可以更直接点,把需要热加载的代码放到另外一个项目。

转载于:https://my.oschina.net/u/1263654/blog/178066

相关文章:

  • 第三部分:Android 应用程序接口指南---第一节:应用程序组件---第三章3-3日历provider...
  • Android中的回调Callback
  • 组成_05_CPU
  • 组播地址列表
  • linux 关闭SSH 连接用户
  • 编程面试的10大算法概念汇总
  • solarwinds engineer's toolset   V9.1 安装 及注册机
  • 理解并取证:IPv6与IPv4在报文结构上的区别
  • hibernate 如何实现延迟加载
  • VMware中Ubuntu网络正常但ping不出去
  • web本地存储-LocalStorage
  • Firebug Command Line 详细分析
  • 使用shell编程,创建10000个目录,结构类似于9/9/99
  • 制作mac U盘启动
  • 压缩版MySQL在Windows下的安装
  • 时间复杂度分析经典问题——最大子序列和
  • 2017届校招提前批面试回顾
  • Android 控件背景颜色处理
  • emacs初体验
  • ES6简单总结(搭配简单的讲解和小案例)
  • javascript面向对象之创建对象
  • magento 货币换算
  • Python3爬取英雄联盟英雄皮肤大图
  • React16时代,该用什么姿势写 React ?
  • SOFAMosn配置模型
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • Sublime Text 2/3 绑定Eclipse快捷键
  • 闭包,sync使用细节
  • 警报:线上事故之CountDownLatch的威力
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • 微信开源mars源码分析1—上层samples分析
  • 线性表及其算法(java实现)
  • const的用法,特别是用在函数前面与后面的区别
  • #Z2294. 打印树的直径
  • $(function(){})与(function($){....})(jQuery)的区别
  • $(selector).each()和$.each()的区别
  • (c语言)strcpy函数用法
  • (javascript)再说document.body.scrollTop的使用问题
  • (笔试题)分解质因式
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (附源码)springboot学生选课系统 毕业设计 612555
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • . Flume面试题
  • .bat批处理出现中文乱码的情况
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .NET Core引入性能分析引导优化
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET 反射 Reflect
  • .net安装_还在用第三方安装.NET?Win10自带.NET3.5安装
  • .Net中wcf服务生成及调用
  • [Android] Upload package to device fails #2720
  • [Angularjs]asp.net mvc+angularjs+web api单页应用
  • [Bzoj4722]由乃(线段树好题)(倍增处理模数小快速幂)
  • [C/C++]数据结构 深入挖掘环形链表问题