2

我在 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。

问题可能是什么?

4

2 回答 2

4

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

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

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

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

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

因此,我们需要自定义 Restart Classloader 并将 modelmapper.jar 放入 restart classloader 而不是 base classloader。

为此,

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

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

    restart.include.modelmapper=/modelmapper-.*.jar
    
  3. 清理并构建完整的项目。现在,每次有文件更改时,都会重新加载 modelmapper 库。

链接:

  1. 模型映射器问题:254
  2. 自定义重启类加载器
于 2018-05-15T10:16:17.967 回答
3

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

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

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

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

于 2015-09-15T07:50:28.070 回答