使用ModelMapper映射泛型继承字段时触发ClassCastException异常
我有如下类结构(完整代码见文末):
abstract class AbstractThing:包含不同事物公共字段的抽象类class Foo extends AbstractThing:继承自AbstractThing的具体实现类Fooabstract class AbstractReq <T extends AbstractThing>:关联事物的请求抽象类,含字段T subjectFooReq extends AbstractReq<Foo>:针对Foo的具体请求类FooReqDto:扁平化包含FooReq所有字段的DTO类
尝试用ModelMapper将FooReq实例映射为FooReqDto,无需自定义类型映射时一切正常,但添加自定义映射(比如把subject.fooField映射为FooReqDto的somethingDifferent字段)后,运行测试抛出如下错误:
java.lang.ClassCastException: class com.example.test.TempTest$AbstractThing$ByteBuddy$PsTTXfw5 cannot be cast to class com.example.test.TempTest$Foo (com.example.test.TempTest$AbstractThing$ByteBuddy$PsTTXfw5 and com.example.test.TempTest$Foo are in unnamed module of loader 'app')
创建类型映射时我遗漏了什么?涉及继承和泛型的类映射本身没问题,无自定义映射时可正常运行,且这些映射在提取抽象类前也能正常工作(提取抽象类是为了减少重复代码)。
完整代码:
package com.example.test; import org.junit.Test; import org.modelmapper.ModelMapper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class TempTest { @Test public void testStuff() { final ModelMapper mapper = new ModelMapper(); mapper.createTypeMap(FooReq.class, FooReqDto.class) .addMappings(map -> { map.map(req -> req.getSubject().getFooField(), FooReqDto::setSomethingDifferent); }); final FooReq req = new FooReq(); req.setSubmitted(true); req.getSubject().setName("testFoo"); req.getSubject().setFooField("bar"); final FooReqDto dto = mapper.map(req, FooReqDto.class); assertTrue(dto.isSubmitted()); assertEquals("testFoo", dto.getSubjectName()); assertEquals("bar", dto.getSomethingDifferent()); } public static abstract class AbstractThing { private String name; // getters and setters omitted public String getName() { return name; } public void setName(String name) { this.name = name; } } public static class Foo extends AbstractThing { private String fooField; // getters and setters omitted public String getFooField() { return fooField; } public void setFooField(String fooField) { this.fooField = fooField; } } public static abstract class AbstractReq <T extends AbstractThing> { protected T subject; private boolean submitted; // getters and setters omitted public T getSubject() { return subject; } public void setSubject(T subject) { this.subject = subject; } public boolean isSubmitted() { return submitted; } public void setSubmitted(boolean submitted) { this.submitted = submitted; } } public static class FooReq extends AbstractReq<Foo> { public FooReq() { super(); subject = new Foo(); } } public static class FooReqDto { private boolean submitted; private String subjectName; private String somethingDifferent; public FooReqDto() { } // getters and setters omitted public boolean isSubmitted() { return submitted; } public void setSubmitted(boolean submitted) { this.submitted = submitted; } public String getSubjectName() { return subjectName; } public void setSubjectName(String subjectName) { this.subjectName = subjectName; } public String getSomethingDifferent() { return somethingDifferent; } public void setSomethingDifferent(String somethingDifferent) { this.somethingDifferent = somethingDifferent; } } }
这个问题的核心是ModelMapper处理泛型抽象类时,无法正确推断AbstractReq<T>中subject的实际类型,生成了AbstractThing的代理子类(ByteBuddy生成)而非Foo,进而触发类型转换异常。
解决方法1:显式强制转换类型
在自定义映射的Lambda表达式中,明确将subject转换为Foo类型后再访问字段:
@Test public void testStuff() { final ModelMapper mapper = new ModelMapper(); mapper.createTypeMap(FooReq.class, FooReqDto.class) .addMappings(map -> { // 显式转换为Foo类型,避免类型推断错误 map.map(req -> ((Foo) req.getSubject()).getFooField(), FooReqDto::setSomethingDifferent); }); final FooReq req = new FooReq(); req.setSubmitted(true); req.getSubject().setName("testFoo"); req.getSubject().setFooField("bar"); final FooReqDto dto = mapper.map(req, FooReqDto.class); assertTrue(dto.isSubmitted()); assertEquals("testFoo", dto.getSubjectName()); assertEquals("bar", dto.getSomethingDifferent()); }
解决方法2:配置ModelMapper的泛型解析策略
调整ModelMapper的配置,让它能正确识别泛型的实际类型,无需强制转换即可正常工作:
@Test public void testStuff() { final ModelMapper mapper = new ModelMapper(); // 配置类型解析策略,启用泛型类型推断 mapper.getConfiguration() .setMatchingStrategy(MatchingStrategies.STANDARD) .setFieldMatchingEnabled(true) .setFieldAccessLevel(org.modelmapper.config.AccessLevel.PRIVATE); mapper.createTypeMap(FooReq.class, FooReqDto.class) .addMappings(map -> { map.map(req -> req.getSubject().getFooField(), FooReqDto::setSomethingDifferent); }); final FooReq req = new FooReq(); req.setSubmitted(true); req.getSubject().setName("testFoo"); req.getSubject().setFooField("bar"); final FooReqDto dto = mapper.map(req, FooReqDto.class); assertTrue(dto.isSubmitted()); assertEquals("testFoo", dto.getSubjectName()); assertEquals("bar", dto.getSomethingDifferent()); }
原因说明
添加自定义映射时,ModelMapper默认无法准确解析泛型抽象类AbstractReq<T>中T的实际类型(Foo),会用AbstractThing的代理类处理字段,导致后续类型转换失败。显式指定类型或调整配置让ModelMapper正确识别泛型实际类型,就能解决该问题。
内容的提问来源于stack exchange,提问作者Michelle

