ModelMapper 中的 ClassCastException:无法强制转换 EnhancerByModelMapper

Posted

技术标签:

【中文标题】ModelMapper 中的 ClassCastException:无法强制转换 EnhancerByModelMapper【英文标题】:ClassCastException in ModelMapper: EnhancerByModelMapper cannot be cast 【发布时间】:2015-08-20 20:09:27 【问题描述】:

我在 Play 2.4.2(最新版本)框架应用程序中使用 ModelMapper 0.7.4(最新版本)。 Play 2.4 内置了一个内部 Google Guice 依赖注入解决方案,我们的应用程序是从 Guice 到 Spring Framework 依赖注入解决方案的手动桥接,以使 Play 2.4 与 Spring 一起工作。所以通信从 Play 到 Guice 再到 Spring。

事情(使用 Spring 进行依赖注入)似乎工作正常,但是当在测试开发环境中更改随机 Java 类时,Play 会自动重新加载该类或 webapp。这种重新加载通常可以正常工作,但是当 ModelMapper 在此 Play 设置中用作 Spring Bean 时,它似乎会导致 ModelMapper 出现问题。 (但通过在设置中手动创建 Spring 容器然后将 ModelMapper 作为 Spring bean 联系来绕过 Guice-Spring 桥时无法重现该问题。)

错误是:

Caused by: org.modelmapper.ConfigurationException: ModelMapper configuration errors:

1) Failed to configure mappings

1 error
        at org.modelmapper.internal.Errors.throwConfigurationExceptionIfErrorsExist(Errors.java:241) ~[modelmapper-0.7.4.jar:na]
        at org.modelmapper.internal.ExplicitMappingBuilder.build(ExplicitMappingBuilder.java:207) ~[modelmapper-0.7.4.jar:na]
        at org.modelmapper.internal.TypeMapImpl.addMappings(TypeMapImpl.java:72) ~[modelmapper-0.7.4.jar:na]
        at org.modelmapper.internal.TypeMapStore.getOrCreate(TypeMapStore.java:101) ~[modelmapper-0.7.4.jar:na]
        at org.modelmapper.ModelMapper.addMappings(ModelMapper.java:93) ~[modelmapper-0.7.4.jar:na]
        at configs.AppConfig.modelMapper(AppConfig.java:109) ~[na:na]
        at configs.AppConfig$$EnhancerBySpringCGLIB$$b19a8688.CGLIB$modelMapper$2(<generated>) ~[na:na]
        at configs.AppConfig$$EnhancerBySpringCGLIB$$b19a8688$$FastClassBySpringCGLIB$$1f1c1728.invoke(<generated>) ~[na:na]
        at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) ~[spring-core-4.2.0.RELEASE.jar:4.2.0.RELEASE]
        at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:318) ~[spring-context-4.2.0.RELEASE.jar:4.2.0.RELEASE]
        at configs.AppConfig$$EnhancerBySpringCGLIB$$b19a8688.modelMapper(<generated>) ~[na:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45]
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_45]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_45]
        at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_45]
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162) ~[spring-beans-4.2.0.RELEASE.jar:4.2.0.RELEASE]
        ... 64 common frames omitted
Caused by: java.lang.ClassCastException: project.entities.User$$EnhancerByModelMapper$$f1b8f0f9 cannot be cast to project.entities.User
        at configs.AppConfig$1.configure(AppConfig.java:106) ~[na:na]
        at org.modelmapper.PropertyMap.configure(PropertyMap.java:383) ~[modelmapper-0.7.4.jar:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45]
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_45]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_45]
        at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_45]
        at org.modelmapper.internal.ExplicitMappingBuilder.build(ExplicitMappingBuilder.java:195) ~[modelmapper-0.7.4.jar:na]
        ... 78 common frames omitted

这只会在类重新加载时发生,而不是在没有重新加载完成时发生。 此外,不使用 modelMapper.addMappings(aPropertyMap) 时也不会出现此问题。 Spring AppConfig 类如下所示:

@Configuration
public class AppConfig 
    @Bean
    public ModelMapper modelMapper() 
        ModelMapper modelMapper = new ModelMapper();

        // BEGIN: WITHOUT THE FOLLWOING CODE, it works fine
        PropertyMap<CreateUserFormDTO, User> userMap = new PropertyMap<CreateUserFormDTO, User>() 
            @Override
            public void configure() 
                map().setPassword(source.getPassword1());
            
        ;
        modelMapper.addMappings(userMap);
        // END


        return modelMapper;
    

使用普通的 Spring @Autowire 注入访问 ModelMapper。 User 和 CreateUserFormDTO 类只是 POJO。

可能是什么问题?

【问题讨论】:

【参考方案1】:

问题正是Jean所说的。 根据 Spring-Dev Tools:

只要类路径上的文件发生变化,使用 spring-boot-devtools 的应用程序就会自动重启。在 IDE 中工作时,这可能是一个有用的功能,因为它为代码更改提供了一个非常快速的反馈循环。

发生代码更改时,由于基类加载器,ModelMapper 库已经加载到类路径中。根据 Spring-Dev 工具:

Spring Boot 提供的重启技术通过使用两个类加载器来工作。不会更改的类(例如,来自第三方 jar 的类)被加载到基类加载器中。您正在积极开发的类被加载到重新启动类加载器中。当应用程序重新启动时,重新启动类加载器被丢弃并创建一个新的。这种方法意味着应用程序重新启动通常比“冷启动”快得多,因为基类加载器已经可用并已填充。

默认情况下,IDE 中任何打开的项目都使用“restart”类加载器加载,任何常规 .jar 文件都使用“base”类加载器加载。

所以,我们需要自定义Restart Classloader,将modelmapper.jar放入restart classloader而不是base classloader中。

为此,

    在您的项目中创建一个META-INF/spring-devtools.properties 文件以将其加载到项目的类路径中。

    在 spring-devtools.properties 文件中添加这一行

    restart.include.modelmapper=/modelmapper-.*.jar
    

    清理并构建完整的项目。现在每次有文件更改时都会重新加载模型映射器库。

链接:

    ModelMapper Issue: 254 Customizing the Restart Classloader

【讨论】:

【参考方案2】:

这里的问题是 ModelMapper 创建了一个映射器的内存缓存(请参阅TypeMapStore.getOrCreate)并创建一个生成器类,该类返回一个与您的模型兼容的类(User$$EnhancerByModelMapper$$f1b8f0f9 扩展User

只要您不重新加载,一切正常。但是,一旦您重新加载 play 应用程序,应用程序类加载器就会被丢弃,因此 User 类的新 instance 会被加载到新的类加载器中。 User$$EnhancerByModelMapper$$f1b8f0f9 仍然扩展了前一个类并且无法转换为新类。

我没有使用 ModelMapper 的解决方案。我想你必须修复 ModelMapper 以便当缓存的映射器抛出 ClassCastException 时,TypeMapstore 会丢弃它并尝试重新创建一个新的映射器。

最后,当我评论 ModelMapper 上的类加载问题时,我曾经从事的项目用 Selma (http://www.selma-java.org/) 替换了 ModelMapper,这在我们的测试中运行良好(我的合同在那时结束,我不知道如果他们最终保留了 selma)

【讨论】:

以上是关于ModelMapper 中的 ClassCastException:无法强制转换 EnhancerByModelMapper的主要内容,如果未能解决你的问题,请参考以下文章

ModelMapper 一文读懂

ModelMapper 中高级使用 java

ModelMapper:在运行时映射抽象类

ModelMapper - 无法将 ArrayList 转换为 List

ModelMapper - 无法将 ArrayList 转换为 List

java~modelMapper需要注意的几点