原理
要实现动态加载一个类(可能会修改该类),一般有两种方法,实例化不同的classloader来加载,另一种方法是用同一个classloader来加载,但类名会修改。用的还是JDK编译工具来编译代码,classloader用自定义的classloader。代码如下:
/**
* Created by gaofla on 2018/3/14.
*/
public class ClassEngine {
private URLClassLoader parentClassLoader;
//单例
private static ClassEngine customClassCompiler = new ClassEngine();
private ClassEngine() {
this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();
}
public static ClassEngine getInstance() {
if (customClassCompiler == null) {
try {
synchronized (ClassEngine.class) {
if (customClassCompiler == null) {
customClassCompiler = new ClassEngine();
}
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
return customClassCompiler;
}
/**
* 编译并加载类
*
* @param className
* @param javaCode
* @param javaCodeVersion
* @param newClassLoader
* @return
* @throws Exception
*/
public Class<?> compileAndLoadClass(String className, String javaCode, String javaCodeVersion, boolean newClassLoader) throws Exception {
// TODO newClassLoader 每次可以新new一个classloader来加载
// 类名= 原来的名字+ "_版本号"
String classNameSuffix = "_" + javaCodeVersion;
return compileAndLoadClass(className, javaCode, classNameSuffix);
}
/**
* 真正的编译和类加载实现
*
* @param className
* @param javaCode
* @param classNameSuffix
* @return
* @throws Exception
*/
public Class<?> compileAndLoadClass(String className, String javaCode, String classNameSuffix) throws Exception {
Class clz = null;
String newClassName = className + classNameSuffix;
// Step 1: 类名替换
String newJavaCode = getNewJavaCode(javaCode, className, classNameSuffix);
// Step 2: 编译代码
ClassFileManager classFileManager = compile(newClassName, newJavaCode);
// Step 3: 加载类
clz = loadClass(classFileManager, newClassName, CustomerClassLoader.getDefaultSameCustomClassLoader(ClassEngine.getInstance().getParentClassLoader()));
return clz;
}
/**
* 加载类
*
* @param fileManager
* @param className
* @param customClassLoader
* @return
*/
private Class<?> loadClass(ClassFileManager fileManager, String className, CustomerClassLoader customClassLoader) {
JavaClassObject jco = fileManager.getMainJavaClassObject();
Class clz = customClassLoader.loadClass(className, jco);
return clz;
}
/**
* 更换类名
*
* @param originJavaCode
* @param className
* @param classNameSuffix
* @return
*/
private String getNewJavaCode(String originJavaCode, String className, String classNameSuffix) {
Pattern pattern = Pattern.compile("([^A-Za-z0-9_])" + className + "(?![A-Za-z0-9_]+)");
Matcher matcher = pattern.matcher(originJavaCode);
boolean find = matcher.find();
StringBuffer sb = new StringBuffer();
while (find) {
matcher.appendReplacement(sb, matcher.group() + classNameSuffix);
find = matcher.find();
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* 编译类名
*
* @param fullClassName
* @param javaCode
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
private ClassFileManager compile(String fullClassName, String javaCode) throws IllegalAccessException, InstantiationException {
//获取系统编译器
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");
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
// 编译源程序不成功
if (!task.call()) {
System.out.println(compileError(diagnostics));
return null;
}
return fileManager;
}
/**
* 编译错误信息
*
* @param diagnostics
* @return
*/
private static String compileError(DiagnosticCollector<JavaFileObject> diagnostics) {
StringBuilder sb = new StringBuilder();
for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
sb.append(compileError(diagnostic)).append("\n");
}
return sb.toString();
}
/**
* 编译错误信息
*
* @param diagnostic
* @return
*/
private static String compileError(Diagnostic<?> diagnostic) {
StringBuilder sb = new StringBuilder();
sb.append("Code:[" + diagnostic.getCode() + "]\n");
sb.append("Kind:[" + diagnostic.getKind() + "]\n");
sb.append("Position:[" + diagnostic.getPosition() + "]\n");
sb.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");
sb.append("End Position:[" + diagnostic.getEndPosition() + "]\n");
sb.append("Source:[" + diagnostic.getSource() + "]\n");
sb.append("Message:[" + diagnostic.getMessage(null) + "]\n");
sb.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");
sb.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");
return diagnostic.toString();
}
public URLClassLoader getParentClassLoader() {
return parentClassLoader;
}
public void setParentClassLoader(URLClassLoader parentClassLoader) {
this.parentClassLoader = parentClassLoader;
}
}
/**
* Created by gaofla on 2018/3/14.
*/
public class CustomerClassLoader extends URLClassLoader {
private static CustomerClassLoader customClassLoader;
private CustomerClassLoader(ClassLoader parent) {
super(new URL[0], parent);
}
public Class findClassByClassName(String className) throws ClassNotFoundException {
return this.findClass(className);
}
/**
* 加载类
*
* @param fullName
* @param jco
* @return
*/
public Class loadClass(String fullName, JavaClassObject jco) {
byte[] classData = jco.getBytes();
return this.defineClass(fullName, classData, 0, classData.length);
}
/**
* 获取相同的类加载器实例
*
* @param parent
* @return
*/
public static CustomerClassLoader getDefaultSameCustomClassLoader(ClassLoader parent) {
if (customClassLoader == null) {
try {
synchronized (CustomerClassLoader.class) {
if (customClassLoader == null) {
customClassLoader = new CustomerClassLoader(parent);
}
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
return customClassLoader;
}
}
/**
* Created by gaofla on 2018/3/14.
*/
public class ClassFileManager extends ForwardingJavaFileManager {
/**
* 编译存储的类包括子类
*/
private List<JavaClassObject> javaClassObjectList;
public ClassFileManager(StandardJavaFileManager
standardManager) {
super(standardManager);
this.javaClassObjectList = new ArrayList<JavaClassObject>();
}
public JavaClassObject getMainJavaClassObject() {
if (this.javaClassObjectList != null && this.javaClassObjectList.size() > 0) {
int size = this.javaClassObjectList.size();
return this.javaClassObjectList.get((size - 1));
}
return null;
}
public List<JavaClassObject> getInnerClassJavaClassObject() {
if (this.javaClassObjectList != null && this.javaClassObjectList.size() > 0) {
int size = this.javaClassObjectList.size();
if (size == 1) {
return null;
}
return this.javaClassObjectList.subList(0, size - 1);
}
return null;
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
String className, JavaFileObject.Kind kind, FileObject sibling)
throws IOException {
JavaClassObject jclassObject = new JavaClassObject(className, kind);
this.javaClassObjectList.add(jclassObject);
return jclassObject;
}
}
/**
* Created by gaofla on 2018/3/14.
*/
public class CharSequenceJavaFileObject extends SimpleJavaFileObject {
private CharSequence content;
public CharSequenceJavaFileObject(String className,
CharSequence content) {
super(URI.create("string:///" + className.replace('.', '/')
+ Kind.SOURCE.extension), Kind.SOURCE);
this.content = content;
}
@Override
public CharSequence getCharContent(
boolean ignoreEncodingErrors) {
return content;
}
}
/**
* Created by gaofla on 2018/3/14.
*/
public class JavaClassObject extends SimpleJavaFileObject {
protected final ByteArrayOutputStream bos =
new ByteArrayOutputStream();
public JavaClassObject(String name, Kind kind) {
super(URI.create("string:///" + name.replace('.', '/')
+ kind.extension), kind);
}
public byte[] getBytes() {
return bos.toByteArray();
}
@Override
public OutputStream openOutputStream() throws IOException {
return bos;
}
}
/**
* Created by gaofla on 2018/3/14.
*/
public class Test {
public static void main(String args[]) throws Exception {
String javaCode = "import com.classloader.compile.Base;\n" +
"public class T implements Base {\n" +
" @Override\n" +
" public void say() {\n" +
" System.out.println(\"hello\");\n" +
" }\n" +
"}";
String version = "1";
Class<?> clazz = ClassEngine.getInstance().compileAndLoadClass("T", javaCode, version, false);
Base base1 = (Base) clazz.newInstance();
base1.say();
javaCode = "import com.classloader.compile.Base;\n" +
"public class T implements Base {\n" +
" @Override\n" +
" public void say() {\n" +
" System.out.println(\"hello aaa\");\n" +
" }\n" +
"}";
version = "2";
clazz = ClassEngine.getInstance().compileAndLoadClass("T", javaCode, version, false);
Base base2 = (Base) clazz.newInstance();
base2.say();
}
}