Java-自定义的ClassLoader 生成的class字节数组,如何才能加载到 AppClassLoader等其他的classloader

自定义ClassLoader

如何自定义 自己的 classLoader,其实很简单;只需要继承ClassLoader,然后重写一些方法实现自己的业务就可以了。
下面以 加载 ASM(等其他方式)产生的 class 的 byte数组 生成 Class 对象 为例子:

private static class MyClassLoader extends ClassLoader {
        public Class<?> defineClass(String name, byte[] b) {
            // ClassLoader是个抽象类,而ClassLoader.defineClass 方法是protected的
            // 所以我们需要定义一个子类将这个方法暴露出来
            return super.defineClass(name, b, 0, b.length);
        }

    }

利用字节码 ASM 技术 即时生成 class

为什么要 利用ASM 即时生成 class 啦?
在一些高级框架里面一般会使用到 某一个 实体类 来完成数据格式的自动转化,例如 spark 的 DataSet[Row] 转化为 DataSet[User] , DataSet[Person] 等,但是这个 Row 里面的字段可能是变动的,所以 可以 对应的 利用 row的字段信息 自动生成 实体类和getter setter 方法,完成自动转化 输出到别的系统。

public class GeneratorClassByASM {
	//这里是因为 ASM 有好人类型的 return 和 load 指令
    private static Map<Class, Integer> mappingReturns = new HashMap<>();
    private static Map<Class, Integer> mappingLoads = new HashMap<>();
    public static MyClassLoader cl = new MyClassLoader();
    static {
        mappingReturns.put(Integer.class, IRETURN);
        mappingLoads.put(Integer.class, ILOAD);

        mappingReturns.put(int.class, IRETURN);
        mappingLoads.put(int.class, ILOAD);

        mappingReturns.put(String.class, ARETURN);
        mappingLoads.put(String.class, ALOAD);

        mappingReturns.put(Long.class, LRETURN);
        mappingLoads.put(Long.class, LLOAD);

        mappingReturns.put(long.class, IRETURN);
        mappingLoads.put(long.class, ILOAD);

        mappingReturns.put(Double.class, DRETURN);
        mappingLoads.put(Double.class, DLOAD);

        mappingReturns.put(double.class, DRETURN);
        mappingLoads.put(double.class, DLOAD);

        mappingReturns.put(Float.class, FRETURN);
        mappingLoads.put(Float.class, FLOAD);

        mappingReturns.put(float.class, FRETURN);
        mappingLoads.put(float.class, FLOAD);
    }

    /**
     * ASM Class 的  set 方法
     * @param cw
     * @param fieldName
     * @param fileType
     * @param packageName 这里是 包 路径 /../../
     * @param className
     */
    private static void generatorSetMethod(ClassWriter cw, String fieldName, Class fileType, String packageName, String className){
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC , "set" + initUpper(fieldName),
                "("+ Type.getType(fileType).getDescriptor() +")V", null, null);
        mv.visitVarInsn(mappingLoads.get(fileType), 0);
        mv.visitVarInsn(mappingLoads.get(fileType), 1);
        mv.visitFieldInsn(Opcodes.PUTFIELD, packageName + className, fieldName, Type.getType(fileType).getDescriptor());
        mv.visitInsn(RETURN);
        mv.visitMaxs(2, 2);
        mv.visitEnd();
    }

    /**
     * ASM Class 的  get 方法
     * @param cw
     * @param fieldName
     * @param fileType
     * @param packageName 这里是 包 路径 /../../
     * @param className
     */
    private static void generatorGetMethod(ClassWriter cw, String fieldName, Class fileType, String packageName, String className){
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC , "get" + initUpper(fieldName),
                "()" + Type.getType(fileType).getDescriptor(), null, null);
        mv.visitVarInsn(mappingLoads.get(fileType), 0);
        mv.visitFieldInsn(GETFIELD, packageName  + className, fieldName, Type.getType(fileType).getDescriptor());
        mv.visitInsn(mappingReturns.get(fileType));
        mv.visitMaxs(1,1);
        mv.visitEnd();
    }

    /**
     * ASM Class 的无参构造方法
     * @param cw
     */
    private static void generatorInitmethos(ClassWriter cw){
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC , "<init>",
                "()V" , null, null);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
    }

    /**
     * ASM Class 的  属性字段
     * @param cw
     * @param fieldName
     * @param fileType
     */
    private static void generatorFields(ClassWriter cw, String fieldName, Class fileType){
        cw.visitField(Opcodes.ACC_PRIVATE, fieldName, Type.getType(fileType).getDescriptor(), null, null);
    }

    private static String initUpper(String fieldName){
        return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }

    public static byte[] geneClassMain(String packageName, String className){
        String packageName1 = packageName.replace(".", "/");
        System.out.println(packageName1);
        ClassWriter cw = new ClassWriter(0);
        // 定义对象头:版本号、修饰符、全类名、签名、父类、实现的接口
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, packageName1 + className,
                null, "java/lang/Object", null);
        generatorInitmethos(cw);
        generatorFields(cw, "id", String.class);
        generatorFields(cw, "time", String.class);
        generatorSetMethod(cw, "id", String.class, packageName1, className);
        generatorGetMethod(cw, "id", String.class, packageName1, className);
        generatorSetMethod(cw, "time", String.class, packageName1, className);
        generatorGetMethod(cw, "time", String.class, packageName1, className);
        cw.visitEnd();
        return cw.toByteArray();
    }

    public static void main(String[] args) throws Exception {
//        GeneratorClassByASM generatorClassByASM = new GeneratorClassByASM();
//        generatorClassByASM.run1();

//        GeneratorClassByASM.getPiClass();

    }

    private static class MyClassLoader extends ClassLoader {
        public Class<?> defineClass(String name, byte[] b) {
            // ClassLoader是个抽象类,而ClassLoader.defineClass 方法是protected的
            // 所以我们需要定义一个子类将这个方法暴露出来
            return super.defineClass(name, b, 0, b.length);
        }

    }
}

如何 加载上面产生的 class 的 byte数组 到 特定的 ClassLoader

大家都知道 不同的 classloader 产生的 class 对象 是不同的;相应的 你的 自定义 classloader 可以 通过 自己重写 的 defineClass 方法 把 你产生的 class 的 byte数组 转化为 Class 对象;但是 你的 正常的程序 所使用的 classloader 一般都是 Appclassloader(没有public defineClass 的方法 哦) ;所以 你的 非 自定义 的 classlaoder 是加载不到。

那么怎么才能加载到啦?
下面介绍2中方法。

利用 java -classpath 间接加载 class 文件的方式

这种方式比较简单,就是先把 class byte数组 保存到 java 运行是的 -classpath 的其中的某个path里面。那么当 jvm 需要加载这个class的时候,就会自动在 classpath 里面查找的,如果程序第一步就 生成这个 class 的class文件,那么别的 classloader 必然会加载到的。
有一个最大的缺点 就是 必须 保存文件。

利用 反射技术 加载 class byte 数组 到 指定的 classloader

这种方法是比较复杂的。我们先来看看如何想到的。
简单说一个怎么发现的,我们基本都知道 java 使用 ASM 技术 的一般是在 动态代理上。并且动态代理一共有2中 java自己的动态代理Proxy 和 cglib的 动态代理。哪它们产生的 class byte数组 是怎么 加载到 某个 classloader的。

  1. 看看 Proxy的实现 ,最后你会发现 把 class byte数组 转化为 class 对象的方法 是
    private static native Class<?> defineClass0(ClassLoader loader, String name,
    byte[] b, int off, int len);
    可以看到 它是 一个 native的方法。
    好吧 我们无能为力了。
  2. 接下来看看 cglib 是怎么 把 产生的 class byte数组 是怎么 加载到 某个 classloader的。
    通过查看源码,你可以看到 AbstractClassGenerator 的 create 方法 找到 gen = ReflectUtils.defineClass(className, b, loader); 这个 方法,这个不就是我们 苦苦 寻找的 方法吗!这个 方法在 ReflectUtils 类中。后面我们会看看它是怎么实现的。

这里展示一下 使用方法:
前置条件 需要 cglib的依赖。

<dependency>
	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>3.1</version>
</dependency>
String packageName = "com.yyb.flink10.xxx.";
String className = "Pi";
byte[] byteOfClass = GeneratorClassByASM.geneClassMain(packageName, className);
Class pi = ReflectUtils.defineClass(packageName + className, byteOfClass, LoadClassByClassloader.class.getClassLoader());
Class<?> xx = Class.forName(packageName + className);
System.out.println(xx.newInstance());

看看就是这么简单!!!

解析 cglib 把 class byte数组加载到 某个 classloader 的原理

先看看 ReflectUtils.defineClass 方法:

// className 类的全路径类名, byte[] b 就是 class 的 byte数组, loader 就是需要 那个 加载器 加载
public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception {
        Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), PROTECTION_DOMAIN };
        //注意这个 DEFINE_CLASS ,是 java.lang.ClassLoader 的 特定 defineClass 方法,并设置方法是 可以访问的,最后通过 方法的invoke 调用 目标 classloader的 这个 特定 defineClass ,得到 加载完成 class 的 byte数组 后的 classloader 对象。自此大功告成!!!
        Class c = (Class)DEFINE_CLASS.invoke(loader, args);
        // Force static initializers to run.
        //最后 反射 检查 是否 已经 加载。
        Class.forName(className, true, loader);
        return c;
    }

DEFINE_CLASS

private static Method DEFINE_CLASS;
private static final ProtectionDomain PROTECTION_DOMAIN;
static {
   PROTECTION_DOMAIN = (ProtectionDomain)AccessController.doPrivileged(new PrivilegedAction() {
   public Object run() {
        return ReflectUtils.class.getProtectionDomain()
   }
   });
        
   AccessController.doPrivileged(new PrivilegedAction() {
      public Object run() {
        try {//通过反射 拿到 java.lang.ClassLoader class 对象
              Class loader = Class.forName("java.lang.ClassLoader"); // JVM crash w/o this
              //获取 java.lang.ClassLoader class 对象 的 getDeclaredMethod 的 特定方法
             DEFINE_CLASS = loader.getDeclaredMethod("defineClass",
                                                            new Class[]{ String.class,
                                                                         byte[].class,
                                                                         Integer.TYPE,
                                                                         Integer.TYPE,
                                                                         ProtectionDomain.class });
                    //设置方法是 可以访问的
                    DEFINE_CLASS.setAccessible(true);
                } catch (ClassNotFoundException e) {
                    throw new CodeGenerationException(e);
                } catch (NoSuchMethodException e) {
                    throw new CodeGenerationException(e);
                }
                return null;
            }
        });
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值