ASM动态构建Class,调用方法出现异常:AbstractMethodError
需求背景:
在项目中到Entity与DTO的转换,会涉及到枚举或对象与普通的数据类型进行转换,使用Hutool的coptyProperties进行拷贝,只支持同类的数据类型,夸数据类型拷贝需要定义转换类Converter,一个一个的写转换类是在麻烦,且枚举类偏多,想要通过ASM动态编码技术,根据枚举生成对应的Conveter转换类,然后在项目初始化的时候统一载入到Hutool的ConverterRegistry。
问题:
在使用asm生成class之后,生成class实例化反射调用方法能够成功运行,但是实例化多态转换之后,无法调用方法,会出现错误:AbstractMethodError。PS:hutool为三方jar包,这里的Class生成也是有AppClassLoader进行加载,不存在类加载器命名空间不一样的问题。这里问题的关键点就是抽象类的convert执行,就好像我的asm生成子类没有实现抽象方法异常。
·
前置代码:
/**
* Hutool copy转换类型
*/
public interface ICopyConverter<T> extends Converter<T> {
/**
* 操作目标的class
*
* @return
*/
Class<?> getTypeClass();
}
/**
* BeanUtils copy抽象转换
* 这里不使用默认值defaultValue,这是Hutool的BeanUtils,Convert转换类,所以只对BeanUtils.copyProperties起作用
*/
public abstract class AbstractCopyConverter<T> implements ICopyConverter<T> {
@Override
public T convert(Object value, T defaultValue) throws IllegalArgumentException {
if (ObjectUtil.isEmpty(value)) {
throw new ValidateException("Parameter conversion exception, please check the parameter status");
}
return convert(value);
}
public abstract T convert(Object value);
}
//生成Class
private static Class asmGenerateEnumConverterClasses(){
// ... 此处省略生成细节代码
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//需要使用AppClassLoader类加载加载,否则会因命名空间不一致,导致class冲突
return ReflectUtils.defineClass(originClassName, bytes, classLoader);
}
//这里是生成的Class文件,反编译后展示的Java文件。是完全没有问题的。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.s.message.middle.infrastructure.config.converter.impl;
import com.s.message.middle.domain.enums.IsContinue;
import com.s.message.middle.infrastructure.config.converter.AbstractCopyConverter;
import com.s.message.middle.util.EnumUtils;
public class IsContinueConverter extends AbstractCopyConverter<IsContinue> {
public IsContinueConverter() {
}
public IsContinue convert(Object value) {
return (IsContinue)EnumUtils.toE(value, IsContinue.class, Boolean.FALSE);
}
public Class<?> getTypeClass() {
return IsContinue.class;
}
}
测试用例
public static void main(String[] args) throws Exception {
//系统枚举太多,手动新增太麻烦且不利于代码迭代维护,这里通过asm动态字节码技术,动态生成(需系统枚举实现接口EnumInterface)
try {
ConverterRegistry instance = ConverterRegistry.getInstance();
//生成转换类Class
List<Class> classes = asmGenerateEnumConverterClasses();
for (Class clazz : classes) {
Object ins = clazz.newInstance();
Method method = clazz.getMethod("getTypeClass");
Object typeClass = method.invoke(ins);
instance.putCustom((Type) typeClass, clazz);
}
instance.putCustom(CronExpress.class, CronExpressConverter.class);
} catch (Exception e) {
log.error("ASM动态编译枚举转换器发生异常", e);
throw new BaseException("ASM动态编译枚举转换器发生异常");
}
ConverterRegistry instance = ConverterRegistry.getInstance();
//这里convert1可以正常执行,因为CronExpressConverter是写在项目里的Java文件。
Converter<Object> customConverter1 = instance.getCustomConverter(CronExpress.class);
Object convert1 = customConverter1.convert("1 1 1 1 1 ?", null);
System.out.println(convert1);
//这里是asm动态编码生成Class文件,通过反射执行方法,可以正常执行
Class<?> clazz = Class.forName("com.s.message.middle.infrastructure.config.converter.impl.IsContinueConverter");
System.out.println(clazz.getMethod("convert",Object.class).invoke(clazz.newInstance(),1));
//这里convert2执行失败,异常:Exception in thread "main" java.lang.Error: java.lang.AbstractMethodError
Converter<IsContinue> customConverter2 = instance.getCustomConverter(IsContinue.class);
IsContinue convert2 = customConverter2.convert(1, null);
System.out.println(convert2);
}
从上而下: 依次执行结果为:
CronExpress(value=1 1 1 1 1 ?, expression=CronExpression<1 1 1 1 1 ?>)
YES
Exception in thread "main" java.lang.AbstractMethodError
at com.s.message.middle.infrastructure.config.converter.AbstractCopyConverter.convert(AbstractCopyConverter.java:20)
at com.s.message.middle.infrastructure.config.converter.CopyConverterInitializationConfigure.main(CopyConverterInitializationConfigure.java:85)
请问原因是什么呢,怎么解决这个问题。另外顺便问个题外话,时候其他好一点的方案可以实现上述需求,解决繁琐的无意义开发工作量?