动态代理的基础--内存中生成字符串代码并编译加载

动态代理是很多框架都在使用的技术,现在不少框架的类动态生成使用的是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 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 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= javaFileManager.getClam();
    MyClassLoader myclassLoader = new MyClassLoader(bytes);
    //省略反射使用相关代码

你可能感兴趣的:(java)