杨小炀 2023-03-20 14:47 采纳率: 0%
浏览 95
已结题

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

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

  • 写回答

5条回答 默认 最新

  • JoseKe 2023-03-26 21:39
    关注
    问题分析:
    根据问题描述,使用 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 动态生成类时,需要注意类的实现是否正确,以及类的加载方式是否正确。如果出现问题,可以尝试使用其他方式实现需求,或者使用其他字节码生成工具。
    评论

报告相同问题?

问题事件

  • 系统已结题 3月28日
  • 赞助了问题酬金15元 3月20日
  • 创建了问题 3月20日

悬赏问题

  • ¥15 微信会员卡等级和折扣规则
  • ¥15 微信公众平台自制会员卡可以通过收款码收款码收款进行自动积分吗
  • ¥15 随身WiFi网络灯亮但是没有网络,如何解决?
  • ¥15 gdf格式的脑电数据如何处理matlab
  • ¥20 重新写的代码替换了之后运行hbuliderx就这样了
  • ¥100 监控抖音用户作品更新可以微信公众号提醒
  • ¥15 UE5 如何可以不渲染HDRIBackdrop背景
  • ¥70 2048小游戏毕设项目
  • ¥20 mysql架构,按照姓名分表
  • ¥15 MATLAB实现区间[a,b]上的Gauss-Legendre积分