动态代理是很多框架都在使用的技术,现在不少框架的类动态生成使用的是cglib,最近想起jdk1.6开放了类的编译api尝试做了个demo。
一.类的动态生成方式:
1.使用编译时注解,编译时生成.java文件,让jdk编译为class
2.使用字符串拼接代码,cglib生成.class文件
3.使用字符串拼接成代码,JavaCompiler生成class文件
二.JavaCompiler实际使用
1.生成动态代码,这里为了简便直接拼装一个class出来,为了验证实现接口也能编译出来,我加上了一个接口。
public static String getClassJava() {
StringBuilder builder = new StringBuilder("package com.xsq.test");
builder.append(";import com.xsq.test.compiler.TestInte;");
builder.append("public class TestLoadClass implements TestInte{ public void test(){ System.out.println(\"我就是一个测试78789798798\"); } }");
return builder.toString();
}
2.有了代码我们就需要编译代码,这里有2种方式,(1)写出文件,从文件编译;(2)直接在内存编译,我们拼接的class本来就在内存,没必要写出再加载浪费时间和空间,所以直接从内存编译
1)JavaCompiler编译时需要一个JavaFileObject和一个StandardJavaFileManager,系统的JavaFileObject都是针对文件的,这里我们先自定义一个针对Str的JavaFileObject
/**
* 描述:
* 自定义String字符串对象文件处理
*
* @author XiangQingSong
* @create 2020-01-08-15:32
*/
public class StrSrcJavaObject extends SimpleJavaFileObject{
/**str拼接的类*/
private String content;
/**类名*/
private String name;
/**编译后的文件内容*/
private final Map clam = new HashMap();
/**
*
*/
public StrSrcJavaObject(String name, String count) {
super(URI.create("string:///" + name.replace(".", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.content = count;
this.name = name;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
System.out.println("获取拼接类" + content);
return content;
}
@Override
public OutputStream openOutputStream() throws IOException {
System.out.println("打开输出流output");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream() {
@Override
public void close() throws IOException {
System.out.println("关闭输出流");
super.close();
clam.put(name, this.toByteArray());
}
};
return byteArrayOutputStream;
}
public Map getBytes() {
return clam;
}
}
3)系统的StandardJavaFileManager会把生成的字节码写到.class文件,我们再自定义一个StandardJavaFileManager将编译生成的class文件直接写到内存中
/**
* 描述:
* 自定义类编译文件管理器
*
* @author XiangQingSong
* @create 2020-01-08-15:31
*/
public class MyJavaFileManager extends ForwardingJavaFileManager {
public MyJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
return (JavaFileObject) sibling;
}
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
4)获取编译后的class字节码
//获取编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//获取文件管理器
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
StrSrcJavaObject object = new StrSrcJavaObject("com.xsq.test.TestLoadClass", getClassJava());
ForwardingJavaFileManager javaFileManager = new MyJavaFileManager(fileManager);
Iterable extends JavaFileObject> fileObjects = Arrays.asList(object);
JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, null, null, null, fileObjects);
Boolean reslult = task.call();
if (Objects.isNull(reslult) || !reslult.booleanValue()) {
throw new RuntimeException("编译出错");
}
// 获取编译后的字节码
Map bytes= object.getBytes();
5)有了编译后的字节码我们就可以使用动态生成的类,当然使用之前得加载这个类,这里我们自定义一个classLoader来加载我们的动态类
/**
* 描述:
* 自定义类加载器
*
* @author XiangQingSong
* @create 2020-01-08-15:28
*/
public class MyClassLoader extends URLClassLoader {
private final Map cl = new HashMap<>();
public MyClassLoader(@Nullable Map cl) {
super(new URL[0], Thread.currentThread().getContextClassLoader());
this.cl.putAll(cl);
}
@Override
public Class> findClass(String name) throws ClassNotFoundException {
byte[] buf = cl.get(name);
if (Objects.isNull(buf)) {
//非动态生成的交给jvm加载器查找加载
return super.findClass(name);
}
//删除加载后的
cl.remove(name);
//加载
return defineClass(name, buf, 0, buf.length);
}
}
6)加载完成以后通过反射使用我们的类
MyClassLoader myclassLoader = new MyClassLoader(bytes);
try {
Class cla = myclassLoader.loadClass("com.xsq.test.TestLoadClass");
Object obj = cla.newInstance();
Method method = cla.getMethod("test", null);
method.invoke(obj, null);
Method method1=cla.getMethod("getTest2",null);
method1.invoke(obj,null);
return cla;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
7)由于class是会被虚拟机卸载的,所以一般我们会使用一个容器将动态生成的class保存起来,防止被卸载,也便于使用时可以直接获取到对应的对象。
3.上面的方法只能加载单一的一个java文件,如果有内部类或者一个文件下多个类的情况是会找不到对应class的,所以对其做了修改
1)修改MyJavaFileManager
public class MyJavaFileManager extends ForwardingJavaFileManager{ public MyJavaFileManager(JavaFileManager fileManager) { super(fileManager); } /** * 编译后的文件内容 */ private final Map clam = new HashMap (); @Override public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { //每次调用都会使用className if (kind == JavaFileObject.Kind.CLASS) { //改造 StrSrcJavaObject object= new StrSrcJavaObject(className,this); return object; } return super.getJavaFileForOutput(location, className, kind, sibling); } public Map getClam() { return clam; } }
2)修改StrSrcJavaObject
public class StrSrcJavaObject extends SimpleJavaFileObject { /** * str拼接的类 */ private String content; private String name; private MyJavaFileManager fileManager; /** * */ public StrSrcJavaObject(String name, String count) { //必须使用public的类名,这里会被作用为.java文件名 super(URI.create("string:///" + name.replace(".", "/") + Kind.SOURCE.extension), Kind.SOURCE); this.content = count; this.name = name; } /** * @param name * @param fileManager */ public StrSrcJavaObject(String name, MyJavaFileManager fileManager) { super(URI.create("string:///" + name), Kind.CLASS); this.name = name; this.fileManager = fileManager; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return content; } @Override public OutputStream openOutputStream() throws IOException { //有多少个类Output就会被调用多少次 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream() { @Override public void close() throws IOException { super.close(); fileManager.getClam().put(name,this.toByteArray()); } }; return byteArrayOutputStream; } }
3)修改使用方式
/** * * @return */ public static String getClassJava() { StringBuilder builder = new StringBuilder("package com.xsq.test.compiler"); builder.append(";import com.xsq.test.compiler.TestInte;"); builder.append("public class TestLoadClass implements TestInte{ public void test(){ System.out.println(\"我就是一个测试78789798798\"); new te().tes(); new T2().tes2(); } " + " private static class te{\n" + " private void tes(){\n" + " System.out.println(\"Imstatic\");\n" + " }\n" + " }" + "}"); builder.append("class T2{" + " public void tes2(){System.out.println(\"t2\");}" + "}"); return builder.toString(); } public static Class> loder() { //获取编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //获取文件管理器 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); StrSrcJavaObject object = new StrSrcJavaObject("com.xsq.test.compiler.TestLoadClass", getClassJava()); MyJavaFileManager javaFileManager = new MyJavaFileManager(fileManager); Iterable extends JavaFileObject> fileObjects = Arrays.asList(object); JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, null, null, null, fileObjects); Boolean reslult = task.call(); if (Objects.isNull(reslult) || !reslult.booleanValue()) { throw new RuntimeException("编译出错"); } // 获取编译后的字节码 Mapbytes= javaFileManager.getClam(); MyClassLoader myclassLoader = new MyClassLoader(bytes); //省略反射使用相关代码