MapStruct映射Map时运行时抛出ClassCastException异常排查
MapStruct映射Map时抛出ClassCastException问题
问题场景
我正在使用MapStruct实现POJO之间的模型转换,对应映射方法项目构建可正常通过,但运行时会抛出类型转换异常。
以下是MapStruct自动生成的映射方法代码:
protected Map<String, GenericAttributeData> headerAttributeGenericDataTypeMapToStringGenericAttributeDataMap(Map<HeaderAttribute, GenericDataType> map) { if ( map == null ) { return null; } Map<String, GenericAttributeData> map1 = new HashMap<String, GenericAttributeData>( Math.max( (int) ( map.size() / .75f ) + 1, 16 ) ); for ( java.util.Map.Entry<HeaderAttribute, GenericDataType> entry : map.entrySet() ) { String key = entry.getKey().name(); // 异常抛出所在行 GenericAttributeData value = genericDataTypeToGenericAttributeData( entry.getValue() ); map1.put( key, value ); } return map1; } protected GenericAttributeData genericDataTypeToGenericAttributeData(GenericDataType genericDataType) { if ( genericDataType == null ) { return null; } GenericAttributeData genericAttributeData = new GenericAttributeData(); if ( genericDataType.getType() != null ) { genericAttributeData.setType( genericDataType.getType().name() ); } genericAttributeData.setValue( genericDataType.getValue() ); return genericAttributeData; }
该方法预期将键为HeaderAttribute枚举、值为GenericDataType的源Map,转换为键为String、值为GenericAttributeData的目标Map。
异常信息
运行时异常固定抛出在上述代码标注的遍历Map键的行,异常栈核心信息如下:
23 Jun 2022 09:34:46,218 [ERROR] dd6af157-4089-4b37-aa9b-225ed4b4394b (Bobcat-0) com.service.myActivity: class java.lang.String cannot be cast to class com.service.dao.dynamodb.entity.header.HeaderAttribute (java.lang.String is in module java.base of loader 'bootstrap'; com.service.dao.dynamodb.entity.header.HeaderAttribute is in unnamed module of loader com.company.cloud9.launcher.BootstrapClassLoader @75bd9247) java.lang.ClassCastException: class java.lang.String cannot be cast to class com.service.dao.dynamodb.entity.header.HeaderAttribute (java.lang.String is in module java.base of loader 'bootstrap'; com.service.dao.dynamodb.entity.header.HeaderAttribute is in unnamed module of loader com.company.cloud9.launcher.BootstrapClassLoader @75bd9247) at com.service.adapters.mapper.InvoiceHeaderDoMapperImpl.headerAttributeGenericDataTypeMapToStringGenericAttributeDataMap(InvoiceHeaderDoMapperImpl.java:187) ~[myService-1.0.jar:?] at com.service.adapters.mapper.InvoiceHeaderDoMapperImpl.mapDOToInvoiceHeader(InvoiceHeaderDoMapperImpl.java:46) ~[myService-1.0.jar:?] at com.service.adapters.impl.InvoiceHeaderAdapterImpl.adaptDBInvoiceHeaderToCoralModel(InvoiceHeaderAdapterImpl.java:29) ~[myService-1.0.jar:?] at com.service.handler.SubmitAdditionalLinesHandler.buildInventoryInvoiceFromStagingDb(SubmitAdditionalLinesHandler.java:129) ~[myService-1.0.jar:?] at com.service.handler.SubmitAdditionalLinesHandler.handleAdditionalLineItemsSubmissionRequest(SubmitAdditionalLinesHandler.java:83) ~[myService-1.0.jar:?] at com.service.component.SubmitAdditionalLinesComponent.submitAdditionalLines(SubmitAdditionalLinesComponent.java:30) ~[myService-1.0.jar:?] at com.service.service.SubmitInvoiceActivity.submitAdditionalLines_aroundBody2(SubmitInvoiceActivity.java:84) ~[myService-1.0.jar:?] at com.service.service.SubmitInvoiceActivity$AjcClosure3.run(SubmitInvoiceActivity.java:1) ~[myService-1.0.jar:?] at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167) ~[aspectjweaver-1.9.6.jar:?] at com.company.metrics.declarative.aspectj.JoinpointInvocationHandle.proceed(JoinpointInvocationHandle.java:60) ~[DeclarativeCoralMetricsAspectJ-2.2.jar:?] at com.company.metrics.declarative.AbstractMethodMetricInterceptor.handleInvocation(AbstractMethodMetricInterceptor.java:283) ~[DeclarativeCoralMetrics-2.2.jar:?] at com.company.metrics.declarative.aspectj.MetricMethodAspect$ConfiguredMethodAspect.invoke(MetricMethodAspect.java:108) ~[DeclarativeCoralMetricsAspectJ-2.2.jar:?] at com.company.metrics.declarative.aspectj.MetricMethodAspect.captureMethodMetrics(MetricMethodAspect.java:59) ~[DeclarativeCoralMetricsAspectJ-2.2.jar:?] at com.service.service.SubmitInvoiceActivity.submitAdditionalLines(SubmitInvoiceActivity.java:82) ~[myService-1.0.jar:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?] at com.company.coral.reflect.invoke.ReflectiveInvokerFactory$1.invoke(ReflectiveInvokerFactory.java:77) ~[CoralReflect-1.1.jar:?] at com.company.coralx.exception.ExceptionTranslationInterceptor.intercept(ExceptionTranslationInterceptor.java:213) ~[companyCoralExceptionTranslation-1.1.jar:?] at com.company.coral.reflect.invoke.InterceptedInvoker.invoke(InterceptedInvoker.java:74) ~[CoralReflect-1.1.jar:?] at com.company.coral.validate.ValidationInterceptor.intercept(ValidationInterceptor.java:106) ~[CoralValidate-1.1.jar:?] at com.company.coral.reflect.invoke.InterceptedInvoker.invoke(InterceptedInvoker.java:74) ~[CoralReflect-1.1.jar:?] at com.company.coral.service.DefaultActivityInvoker.invoke(DefaultActivityInvoker.java:29) ~[CoralActivity-1.1.jar:?] at com.company.coral.service.ContinuationActivityInvokerFactory$1.invoke(ContinuationActivityInvokerFactory.java:89) ~[CoralActivity-1.1.jar:?] at com.company.coral.service.ActivityInterceptors.invoke(ActivityInterceptors.java:74) ~[CoralActivity-1.1.jar:?] at com.company.coral.service.ActivityHandler.invoke(ActivityHandler.java:186) ~[CoralActivity-1.1.jar:?] at com.company.coral.service.ActivityHandler.before(ActivityHandler.java:145) ~[CoralActivity-1.1.jar:?] at com.company.coral.service.TransmutingContinuationHandler.before(TransmutingContinuationHandler.java:50) ~[CoralOrchestrator-1.1.jar:?] at com.company.coral.service.Chain.componentBefore(Chain.java:94) ~[CoralOrchestrator-1.1.jar:?] at com.company.coral.service.Chain.before(Chain.java:72) ~[CoralOrchestrator-1.1.jar:?] at com.company.coral.service.helper.ChainHelper.before(ChainHelper.java:94) ~[CoralOrchestrator-1.1.jar:?] at com.company.coral.service.AbstractOrchestrator.doWork(AbstractOrchestrator.java:84) ~[CoralOrchestrator-1.1.jar:?] at com.company.coral.service.PassiveOrchestrator.enqueue(PassiveOrchestrator.java:93) ~[CoralOrchestrator-1.1.jar:?] at com.company.coral.service.GracefulShutdownOrchestrator.enqueue(GracefulShutdownOrchestrator.java:301) ~[CoralOrchestrator-1.1.jar:?] at com.company.coral.service.helper.OrchestratorHelper.enqueue(OrchestratorHelper.java:72) ~[CoralOrchestrator-1.1.jar:?] at com.company.coral.bobcat.CoralServletRequestHandler.processRequestOrThrow(CoralServletRequestHandler.java:172) ~[Bobcat-3.0.jar:?] at com.company.coral.bobcat.CoralServletRequestHandler.processRequest(CoralServletRequestHandler.java:150) ~[Bobcat-3.0.jar:?] at com.company.coral.bobcat.CoralServletRequestHandler.service(CoralServletRequestHandler.java:115) ~[Bobcat-3.0.jar:?]
此前尝试打印参数取值、加断点观测时,未发现数据值有明显异常。
根因分析
- 核心原因是Java泛型类型擦除+数据源反序列化不符合预期:
从异常栈可以看到项目使用DynamoDB作为存储,DynamoDB默认反序列化Map类型字段时,如果没有配置专门的枚举转换器,会将枚举类型的键直接反序列化为String类型。由于Java泛型只在编译期做类型检查,编译后Map的泛型参数会被擦除,运行时实际传入MapStruct方法的是一个键为String的原始类型Map,不会提前触发编译错误。 - 断点观测未发现异常的原因:
枚举HeaderAttribute的name()返回值和反序列化得到的String键文本完全一致,仅观测值的文本内容无法发现类型差异,实际断点查看entry.getKey().getClass()会发现其类型为java.lang.String,并非声明的HeaderAttribute枚举。 - MapStruct默认生成的代码逻辑无错误:它按照编译期的泛型声明,直接将entry的key作为
HeaderAttribute枚举调用name()方法,遇到实际类型为String的key时就会触发强转异常。
解决方案
- 修复数据源反序列化逻辑:给DynamoDB实体类中对应Map字段配置枚举类型转换器,指定Map的键反序列化时转换为
HeaderAttribute枚举,从源头保证传入MapStruct的参数类型和声明一致。 - 自定义MapStruct映射逻辑:如果暂时无法修改数据源反序列化规则,可以编写自定义Map映射方法替代MapStruct默认生成的实现,手动兼容键为String的场景:
之后在Mapper接口的对应映射方法上通过default Map<String, GenericAttributeData> customMapConvert(Map<?, GenericDataType> source) { if (source == null) return null; Map<String, GenericAttributeData> result = new HashMap<>(); for (Map.Entry<?, GenericDataType> entry : source.entrySet()) { String key; Object rawKey = entry.getKey(); if (rawKey instanceof HeaderAttribute) { key = ((HeaderAttribute) rawKey).name(); } else if (rawKey instanceof String) { // 校验String是否对应合法枚举,避免非法值 key = HeaderAttribute.valueOf((String) rawKey).name(); } else { throw new IllegalArgumentException("不支持的Map键类型:" + rawKey.getClass()); } result.put(key, genericDataTypeToGenericAttributeData(entry.getValue())); } return result; }@Mapping注解指定使用该自定义方法做Map转换即可。 - 调试时注意检查对象实际运行时类型,不要仅通过文本值判断类型是否匹配。
内容的提问来源于stack exchange,提问作者shubham tambere
相关产品推荐
相关产品推荐

