ASM动态构建Class,调用方法出现异常:AbstractMethodError

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 extends Converter {

    /**
     * 操作目标的class
     *
     * @return
     */
    Class getTypeClass();
}

/**
 * BeanUtils copy抽象转换
 * 这里不使用默认值defaultValue,这是Hutool的BeanUtils,Convert转换类,所以只对BeanUtils.copyProperties起作用
 */
public abstract class AbstractCopyConverter implements ICopyConverter {

    @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 {
    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 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 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 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)

请问原因是什么呢,怎么解决这个问题。另外顺便问个题外话,时候其他好一点的方案可以实现上述需求,解决繁琐的无意义开发工作量?

问题分析:
根据问题描述,使用 ASM 动态生成了一个转换枚举的转换类 IsContinueConverter,并将其加载到了系统中。在使用该转换类进行转换时,调用 convert 方法时出现了 AbstractMethodError 异常。
根据 Java 官方文档,AbstractMethodError 表示一个应该被实现的抽象方法没有被实现。在这里,我们可以猜测是 IsContinueConverter 没有实现 ICopyConverter 接口中的 convert 方法,导致调用该方法时出现了 AbstractMethodError 异常。
但是,根据代码和反编译后的 IsContinueConverter 类的代码,我们可以看到该类已经实现了 ICopyConverter 接口,并且实现了 convert 方法。因此,我们需要进一步分析问题。
在测试用例中,我们可以看到使用反射调用 IsContinueConverter 的 convert 方法时,可以正常执行。这说明 IsContinueConverter 的 convert 方法是可以被正确调用的。
因此,我们可以猜测问题出现在使用 ConverterRegistry 获取 IsContinueConverter 实例时。可能是 ConverterRegistry 在获取 IsContinueConverter 实例时出现了问题,导致获取到的实例没有正确实现 ICopyConverter 接口中的 convert 方法。
解决方案:
针对上述问题,我们可以尝试以下解决方案:
1. 检查 ConverterRegistry 的实现,确保其能够正确获取 IsContinueConverter 实例,并且获取到的实例能够正确实现 ICopyConverter 接口中的 convert 方法。
2. 尝试使用其他方式实现枚举转换,例如使用 Java 8 中的 Stream API 或者使用 Map 等数据结构进行转换。
3. 尝试使用其他字节码生成工具,例如 Javassist 等,看是否能够解决问题。
总结:
在使用 ASM 动态生成类时,需要注意类的实现是否正确,以及类的加载方式是否正确。如果出现问题,可以尝试使用其他方式实现需求,或者使用其他字节码生成工具。

参考GPT和自己的思路:

抛出 AbstractMethodError 异常通常意味着在实际运行时尝试调用抽象方法,因此需要查看代码是否正确实现了所有抽象方法。

根据您提供的代码,问题可能出现在您生成的 ASM 类中的 ICopyConverter 接口的实现上。如果生成的类没有正确实现该接口的所有抽象方法,那么抛出抽象方法错误异常是合理的。

您可以尝试使用调试器检查在运行时生成的类是否实现了 ICopyConverter 接口的所有方法。如果确实存在缺失,请尝试修改 ASM 代码生成以确保生成的代码实现了所有必需的方法。

至于其他实现此类需求的方案,您可以看看代码生成器(例如,JCodeModel)是否可以帮助您以编程方式生成所需的抽象类或转换器类。或者,您可以尝试使用其他字节码工具库(例如,CGLIB)来生成所需的类,以便更轻松地管理和调试它们的实现。

参考GPT和自己的思路,根据问题描述,问题出现在ASM生成的动态类上,特别是在多态转换中遇到了AbstractMethodError异常。这种异常通常发生在尝试调用抽象方法的情况下,即子类没有实现父类中声明的抽象方法。

在这种情况下,可以检查生成的类,看看是否正确实现了抽象方法。但是,从描述来看,生成的类似乎已经被反编译过了,因此我们可以排除代码错误的可能性。考虑到这一点,下一步可以考虑检查类加载器以及其命名空间是否正确。

在这种情况下,我们需要确认以下几个方面:

1.ASM生成的类是否正确实现了抽象方法。

2.类是否在正确的类加载器中加载。

3.是否存在类加载器命名空间不一致的情况。

可以使用以下方式进一步排查:

1.检查生成的类是否正确实现了抽象方法。
可以使用反编译工具,如javap或IntelliJ IDEA的反编译功能,检查生成的类是否正确实现了抽象方法。如果类没有正确实现抽象方法,则需要进一步检查生成的代码,以确定为什么类没有正确实现抽象方法。

2.检查类是否在正确的类加载器中加载。
可以在代码中添加以下语句,以检查生成的类是否由正确的类加载器加载:

Class<?> clazz = Class.forName("com.example.GeneratedClass");
System.out.println(clazz.getClassLoader());

如果输出与预期不符,则说明生成的类可能没有由正确的类加载器加载。在这种情况下,可能需要更改类加载器或重新加载类,以确保生成的类由正确的类加载器加载。

3.检查类加载器命名空间是否一致。
如果类由不同的类加载器加载,则可能会出现类加载器命名空间不一致的情况。在这种情况下,即使生成的类与目标类的签名相同,它们也被视为不同的类。如果类加载器命名空间不一致,则需要更改类加载器,以确保生成的类与目标类在相同的命名空间中加载。

综上所述,建议先排查类加载器和命名空间的问题,检查生成的类是否由正确的类加载器加载,并确保命名空间一致。如果问题仍然存在,可以尝试重新检查生成的类是否正确实现了抽象方法。

该回答引用GPTᴼᴾᴱᴺᴬᴵ
异常 java.lang.AbstractMethodError 是由于抽象方法没有被正确实现所引起的。虽然你已经通过 ASM 技术生成了子类,但是你的子类可能没有正确实现所有父类和接口中定义的抽象方法。
·
在你的情况下,IsContinueConverter 是 AbstractCopyConverter 的子类。 AbstractCopyConverter 是抽象类,它包含一个抽象方法 convert(Object value),需要在子类中实现。
·
确保生成的子类 IsContinueConverter 正确实现了父类中定义的抽象方法 convert(Object value)。另外,您还可以考虑让生成的类实现 ICopyConverter 接口,以确保它们实现了所需的方法。
·
如果你仍然遇到问题,请尝试在生成的代码中打印日志,以确定是否已正确实现抽象方法。另外,您可以尝试使用 ASM 框架提供的工具(例如 CheckClassAdapter)来检查生成的代码是否正确。

以下答案由GPT-3.5大模型与博主波罗歌共同编写:
问题分析:

从异常信息可以看到,错误是在抽象方法的执行时出现了问题,也就是说生成的类没有实现该抽象方法,导致无法正常调用。根据代码可知,抽象方法是在 AbstractCopyConverter 中定义的。而生成的 IsContinueConverter 类继承了 AbstractCopyConverter,但是没有实现其抽象方法。

问题解决:

需要在生成的 IsContinueConverter 类中重写 AbstractCopyConverter 中的抽象方法 convert,具体代码如下:

public class IsContinueConverter extends AbstractCopyConverter {

public IsContinueConverter() {

}

@Override
public IsContinue convert(Object value) {
    return (IsContinue)EnumUtils.toE(value, IsContinue.class, Boolean.FALSE);
}

@Override
public Class<?> getTypeClass() {
    return IsContinue.class;
}

}

需要注意的是,在代码生成过程中,需要确保 IsContinueConverter 类实现了所有父类和父接口中定义的方法和属性。

其他解决方案:

除了使用 ASM 动态生成类的方式外,还可以使用 Java 8 的 Lambda 表达式来实现类似的功能,具体代码如下:

ConverterRegistry.getInstance().putCustom(IsContinue.class, (Converter) value -> EnumUtils.toE(value, IsContinue.class, Boolean.FALSE));

这样就不用手动生成大量的 Converter 类了,直接使用 Lambda 表达式定义转换逻辑即可。

参考资料:

  1. ASM 官方文档:https://asm.ow2.io/