自定义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的。
- 看看 Proxy的实现 ,最后你会发现 把 class byte数组 转化为 class 对象的方法 是
private static native Class<?> defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);
可以看到 它是 一个 native的方法。
好吧 我们无能为力了。 - 接下来看看 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;
}
});
}