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
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.