在项目中到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
问题分析:参考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 表达式定义转换逻辑即可。
参考资料: