javaspring-bootclassloaderembedded-tomcat-8orika

Orika wrong classloader used in case of using Embedded tomcat


We have problem related to class loaders and orika after moving our spring boot app from embedded jetty to embedded tomcat. Here is two classes:

@Getter
@Builder
public class SettingsModel {
    public final Boolean useSelfSignUp;
    public final Boolean approve;
    public final Boolean verifyData;
    public final Boolean collectMid;
    public final Boolean flowEnabled;
    public final String  partnerName;
    public final String  networkType;
    public final String upc;
}

and

@Getter
@Setter
public class SettingsDto {
    private Boolean useSelfSignUp;
    private Boolean approve;
    private Boolean verifyData;
    private Boolean collectMid;
    private String  partnerName;
    private String  networkType;
    private Boolean flowEnabled;
    private String  upc;
}

and mapping code:

private final MapperFacade mapper;
...
mapper.map(settingsDto, SettingsModel.class)

After moving to embedded tomcat mapping throws exception

Caused by: java.lang.IllegalAccessError: tried to access method 
onboarding.data.models.SettingsModel.<init>(Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V from class onboarding.data.models.SettingsModel_SettingsDto_ObjectFactory1006013014242721698432955$9

I have found that orika use JavassistCompilerStrategy which has next code

Class<?> compiledClass = byteCodeClass.toClass(Thread.currentThread().getContextClassLoader(), this.getClass().getProtectionDomain());

When we used embedded jetty Thread.currentThread().getContextClassLoader() - returns sun.misc.Launcher$AppClassLoader and everything works as expected, but after moving to embedded tomcat it returns TomcatEmbeddedWebappClassLoader and mapping throws exception.

Looks like two class loaders working sun.misc.Launcher$AppClassLoader and TomcatEmbeddedWebappClassLoader and this tomcat classloader can't find all args contructor with default access modifier (generated by lombok) in SettingsModel.

jar packaging is used for app.

I am not sure if this problem related to Orika or spring boot.

Also I have found similar issue https://gitter.im/spring-projects/spring-boot/archives/2016/01/15 but not sure if this same problem or something else and can't apply fix provided there because that classes not available in spring boot 2.0.3.RELEASE version.

I tried to use EclipseJdtCompilerStrategy instead of JavassistCompilerStrategy for Orika it's not helped

spring boot version - 2.0.3.RELEASE

orika version - 1.5.2


Solution

  • The problem appears to be due to a limitation in Orika. It, or your configuration of it, does not appear to handle the situation where MapperFacade.map is called when the thread context class loader is a child of the class loader that loaded the destination class. This class loader arrangement isn't specific to Spring Boot. I believe the same problem would also occur in a non-Spring Boot application deployed to Tomcat with the destination class in Tomcat's shared/lib directory.

    You can work around the limitation by changing the thread context class loader before calling the mapper and then reinstating it afterwards:

    @GetMapping("/test-mapping")
    @ResponseStatus(HttpStatus.OK)
    public void test() {
        SettingsDto settingsDto = new SettingsDto();
        ClassLoader previous = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(TestController.class.getClassLoader());
            SettingsModel model = mapper.map(settingsDto, SettingsModel.class);
        }
        finally {
            Thread.currentThread().setContextClassLoader(previous);
        }
    }
    

    With this change in place, a call to /test-mapping produces a 200 response.