jmapper-framework / jmapper-core

Elegance, high performance and robustness all in one java bean mapper
http://jmapper-framework.github.io/jmapper-core
Apache License 2.0
227 stars 41 forks source link

wrong classLoader usage #60

Closed CodeNinjai closed 7 years ago

CodeNinjai commented 7 years ago

Hello,

we are using JMapper 1.6.1.CR1 in a Spring Boot Environment. We have a controller with a method like this:

public List<CampaignGroupDTO> getByClientId(@RequestParam final long clientId, @RequestParam final boolean withCampaigns) {
                JMapper<CampaignGroupDTO, CampaignGroup> mapper = new JMapper<>(CampaignGroupDTO.class, CampaignGroup.class);
        List<CampaignGroupDTO> dtos = new ArrayList<>();
        this.campaignGroupService.findAllWithCampaigns(clientId).forEach(c -> dtos.add(mapper.getDestination(c)));
        return dtos;
    }

Calling getByClientId() the first time works fine, and also the mapping does what we expect it should. But calling the method twice raises following exception:

com.googlecode.jmapper.exceptions.JMapperException: javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedWebappClassLoader): attempted duplicate class definition for name: "demycompanycampaigngroupdtoCampaignGroupDTOdemycompanycampaigngroupentityCampaignGroup" at com.googlecode.jmapper.config.JmapperLog.ERROR(JmapperLog.java:46) at com.googlecode.jmapper.JMapper.(JMapper.java:437) at com.googlecode.jmapper.JMapper.(JMapper.java:373) at com.googlecode.jmapper.JMapper.(JMapper.java:360) at de.mycompany.campaigngroup.controller.CampaignGroupController.getByClientId(CampaignGroupController.java:116) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:208) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:89) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:108) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:784) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:802) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1410) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) Caused by: javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedWebappClassLoader): attempted duplicate class definition for name: "demycompanycampaigngroupdtoCampaignGroupDTOdemycompanycampaigngroupentityCampaignGroup" at javassist.ClassPool.toClass(ClassPool.java:1170) at javassist.ClassPool.toClass(ClassPool.java:1113) at javassist.ClassPool.toClass(ClassPool.java:1071) at javassist.CtClass.toClass(CtClass.java:1264) at com.googlecode.jmapper.generation.JavassistGenerator.generate(JavassistGenerator.java:90) at com.googlecode.jmapper.generation.MapperGenerator.generateMapperClass(MapperGenerator.java:74) at com.googlecode.jmapper.generation.MapperBuilder.generate(MapperBuilder.java:88) at com.googlecode.jmapper.JMapper.createMapper(JMapper.java:450) at com.googlecode.jmapper.JMapper.(JMapper.java:432) ... 63 more Caused by: java.lang.LinkageError: loader (instance of org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedWebappClassLoader): attempted duplicate class definition for name: "demycompanycampaigngroupdtoCampaignGroupDTOdemycompanycampaigngroupentityCampaignGroup" at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:760) at java.lang.ClassLoader.defineClass(ClassLoader.java:642) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at javassist.ClassPool.toClass2(ClassPool.java:1183) at javassist.ClassPool.toClass(ClassPool.java:1164)

Any help would be appreciated :-)

Jan

avurro commented 7 years ago

hi @CodeNinjai, Which version of Tomcat you use? I suggest you use from 7.0.26 onwards. There is a bug in previous versions of Tomcat on WebClassloader.

avurro commented 7 years ago

please give me a feedback if you resolve this issue, so I can write it in the wiki

CodeNinjai commented 7 years ago

Hi @avurro

thanks for the quick reply. We use Tomcat 8.5.5

"Starting Servlet Engine: Apache Tomcat/8.5.5"

So this might not be the cause of the problem?

Thanks, Jan

avurro commented 7 years ago

Probably due to a management with more ClassLoader. Can you attach a project that recreates the error?

CodeNinjai commented 7 years ago

We also assume that the problem is related to the classloader being used. When JMapper is instantiated for the first time, MapperBuilder.exist( ) returns false as expected. But on further instantiations it is still false, causing javassist to dynamically create a (duplicate) class definition.

Give me a moment to create a project for you...

avurro commented 7 years ago

Try to configure the web application class loader as described here, adding the <Loader delegate="true"/>

CodeNinjai commented 7 years ago

Can you try to build this https://github.com/CodeNinjai/JMapperClassloaderIssue ? Just run the app and go to http://localhost:8080 The first time, it will run successfully, the second call will fail.

CodeNinjai commented 7 years ago

@avurro Ok, we are going to try this <Loader delegate="true"/> thing....

avurro commented 7 years ago

switching on jetty the issue there isn't, so now i have to decide if to load all ClassLoaders and check the class existence or delegate to a Tomcat configuration.

CodeNinjai commented 7 years ago

we checked delegation and it is enabled. Also tried to override the ThreadContextClassLoader:

@Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer( Tomcat tomcat) {
   tomcat.enableNaming();
   TomcatEmbeddedServletContainer container = msuper.getTomcatEmbeddedServletContainer(tomcat);
   for (Container child: container.getTomcat().getHost().findChildren()) {
      System.out.println("------->"+child.getClass().getName());
      if (child instanceof Context) {

         ClassLoader contextClassLoader = ((Context)child).getLoader().getClassLoader();
         System.out.println("------->"+contextClassLoader.getClass().getName());
         ClassUtils.overrideThreadContextClassLoader(contextClassLoader);
         //Thread.currentThread().setContextClassLoader(contextClassLoader);
         break;
      }
   }
  return container;
 }

But didn't help either. What do you suggest?

Thanks, Jan

avurro commented 7 years ago

I will solve the problem tonight and will release a SNAPSHOT with this fix.

CodeNinjai commented 7 years ago

Awesome! Thank you very much for the quick help!

avurro commented 7 years ago

Fixed!

<dependency>
  <groupId>com.googlecode.jmapper-framework</groupId>
  <artifactId>jmapper-core</artifactId>
  <version>1.6.1.CR2-SNAPSHOT</version>
</dependency>

You need to add the Snapshot repository! go to wiki page for this.

good work! 👍

CodeNinjai commented 7 years ago

Alessandro,

just tried out the snapshot version. Now I'm getting an ClassCastException at the mapper.getDestination() call:

//JMapper<CampaignMappingDTO, CampaignMapping> mapper = mapperProvider.getMapper(CampaignMappingDTO.class, CampaignMapping.class);
    JMapper<CampaignMappingDTO, CampaignMapping> mapper = new JMapper<>(CampaignMappingDTO.class, CampaignMapping.class);
        CampaignGroup campaignGroup = this.campaignGroupService.getById(id);

        List<CampaignMappingDTO> dtos = new ArrayList<>();
        campaignGroup.getCampaigns().forEach(c -> {

            // following throws exception with 1.6.1.CR2-SNAPSHOT
            CampaignMappingDTO campaignMappingDTO = mapper.getDestination(c);
            campaignMappingDTO.setCampaignGroupId(id);
            dtos.add(campaignMappingDTO);
        });
        return dtos;

Exception:

com.googlecode.jmapper.exceptions.JMapperException: java.lang.ClassCastException: de.mycompany.campaigngroup.entity.CampaignMapping cannot be cast to de.mycompany.campaigngroup.entity.CampaignMapping
    at com.googlecode.jmapper.config.JmapperLog.ERROR(JmapperLog.java:46)
    at com.googlecode.jmapper.JMapper.getDestination(JMapper.java:100)
    at de.mycompany.campaigngroup.controller.CampaignGroupController.lambda$getCampaignGroupCampaigns$5(CampaignGroupController.java:151)
    at de.mycompany.campaigngroup.controller.CampaignGroupController$$Lambda$7/1147850257.accept(Unknown Source)
    at java.lang.Iterable.forEach(Iterable.java:75)
    at de.mycompany.campaigngroup.controller.CampaignGroupController.getCampaignGroupCampaigns(CampaignGroupController.java:150)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:208)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:89)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:108)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:784)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:802)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1410)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ClassCastException: de.mycompany.campaigngroup.entity.CampaignMapping cannot be cast to de.mycompany.campaigngroup.entity.CampaignMapping
    at demycompanycampaigngroupdtoCampaignMappingDTOdemycompanycampaigngroupentityCampaignMapping.nullVSouAllAll(demycompanycampaigngroupdtoCampaignMappingDTOdemycompanycampaigngroupentityCampaignMapping.java)
    at com.googlecode.jmapper.JMapper.getDestination(JMapper.java:98)
    ... 64 more
avurro commented 7 years ago

Curious, the application that you sent to me work perfectly. There are differences between the demo application and the one you use? Destination Class is loaded two times... i'm sorry for the time that I'm doing lose to you, i will solve as soon as possible

CodeNinjai commented 7 years ago

We found the difference! spring-boot-devtools causes the issue..

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

So, we'll exclude this for the moment. We might investigate the class cast issue in this context further, but you may too, if you have spare time for that ;-)

Thanks, Jan

avurro commented 7 years ago

ok Jan, the important thing is that you are not locked. In the meantime i will find a solution for that too :+1:

CodeNinjai commented 7 years ago

Thank you very much for you efforts!

avurro commented 7 years ago

I'm trying with dependency you said but the error does not recur. There's probably something else. If you can recreate the error on your project, I will take it back

abweg commented 7 years ago

demo.zip

Hi avurro, I'm a collegue of CodeNinjai. We both work on the same project. With the 'new' demo Project we can reproduce the error (using 1.6.1.CR2-SNAPSHOT and spring-boot-devtools). Without devtools all is working fine, without not. (We also use hibernate-handling now, to reproduce our main project realistically)

abweg commented 7 years ago

Thanks!

abweg commented 7 years ago

First step: Insert one row in hsqldb "http://localhost:8080/insert". Second step : Fetch the inserted row by "http://localhost:8080/1". (It's craching with devtools included)

avurro commented 7 years ago

thanks @abweg! I work on it as soon as possible.

avurro commented 7 years ago

Guys i have fixed the issue, i have deployed the snapshot with the same name. Let me know if it works (be sure to load the right one).

abweg commented 7 years ago

Hi @avurro. I have just tried out your new version. The method exists() (in jmapper.class (Line 449)) seems to work, but mapper. get... returns null. Let's wait until monday, so my colleagues can have a look about that too. Calling "final JMapper<XDTO, X> mapper = new JMapper<>(XDTO.class, X.class);" twice, is now throwing a Nullpointerexception...

Many thanks for your efforts! And have a nice weekend!

avurro commented 7 years ago

Try again now, i don't understand why i haven't your errors, however i have modified the get method, in this way the same classLoader will be used. I hope this is the right time :sunglasses:

CodeNinjai commented 7 years ago

Alessandro, we just had success with your latest snapshot.. On my machine everything worked just fine even with spring-devtools enabled.

We keep you updated.

avurro commented 7 years ago

good news! JMapper is more robust thanks to you! close the issue when you are confident :+1: