Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NoClassDefFoundError for org.springframework.integration.util.ClassUtils if first called from JMX #3875

Closed
grahamcox-oclc opened this issue Aug 17, 2022 · 5 comments · Fixed by #3876
Assignees
Milestone

Comments

@grahamcox-oclc
Copy link

In what version(s) of Spring Integration are you seeing this issue?

  • Spring Integration - 5.5.11
  • Spring Framework - 5.3.19

Describe the bug

We have a Spring Integration setup in an application, and we have some healthchecks that exercise this and are triggered via JMX. In general this works fine, but in some cases we instead get java.lang.NoClassDefFoundError: Could not initialize class org.springframework.integration.util.ClassUtils errors.

After much digging, it turns out that this is because:

  • org.springframework.integration.util.ClassUtils is entirely static.
    • This means that it is first initialized when some code referencing it is executed, and it is initialized in the thread of the code that referenced it.
  • JMX runs on a thread in the Tomcat server, using the Tomcat common classloader.

This all means that, if the first time org.springframework.integration.util.ClassUtils is referenced is by code executing within an MBean, then it is running in the Tomcat common classloader and not in the webapp classloader. This in turn means that it is unable to see other classes that it expects to see - for example, org.springframework.integration.core.GenericSelector

To Reproduce

I'll see if I can put together a minimal reproducing project, but essentially it will be:

  • Build an app using Spring Integration
  • Write an MBean that triggers Spring Integration
  • Call the MBean before anything in the app is called

Expected behavior

This would all still work correctly and not fail.

Sample

Coming soon.

@grahamcox-oclc grahamcox-oclc added status: waiting-for-triage The issue need to be evaluated and its future decided type: bug labels Aug 17, 2022
@artembilan
Copy link
Member

How do you register that MBean?
Shouldn't it be registered into JMX via Spring as well?
See more info in docs: https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#jmx

@artembilan artembilan added status: waiting-for-reporter Needs a feedback from the reporter and removed status: waiting-for-triage The issue need to be evaluated and its future decided labels Aug 17, 2022
@grahamcox-oclc
Copy link
Author

In our exact case they aren't registered by Spring because of some legacy stuff that we've got around this. However, they are fully constructed Spring Beans that are built in the Spring context, and there's a bean that is also built in the Spring context that is given a reference to both our MBean and the MBeanServer and is just calling mbeanServer.registerMBean on it.

The problem doesn't appear to be with registering the MBeans though. It's that, if the first time the Spring Integration code is exercised is by a call that came in over JMX then this is the first time the ClassUtils is actively referenced, and thus this is the time the static initializers are triggered. This in turn means that they try to run in the context of the JMX Thread, using the context classloader for that thread, which doesn't have visibility of the JAR files in the webapp.

@artembilan
Copy link
Member

OK. So, the idea is to initialize org.springframework.integration.util.ClassUtils as early as possible in the Spring application context?
Then when you got an access to it from your app it will have those static props evaluated.

@grahamcox-oclc
Copy link
Author

That's definitely a solution. And is probably what we'll end up doing in our code - having a Spring bean that triggers something on org.springframework.integration.util.ClassUtils to force it to initialize during Spring startup and before the MBeans are registered.

It could also work though - at least in this case - to ensure that org.springframework.integration.util.ClassUtils is using the correct classloader when calling org.springframework.util.ClassUtils.forName. The org.springframework.util.ClassUtils.getDefaultClassLoader() call defaults to the thread context classloader, but if it were to use the webapp classloader - which it could by instead calling org.springframework.integration.util.ClassUtils.class.getClassLoader() - then that would also fix it. I've no idea if that would break anything else though :(

@artembilan artembilan added this to the 6.0.0-M5 milestone Aug 17, 2022
@artembilan artembilan added in: core backport 5.5.x and removed status: waiting-for-reporter Needs a feedback from the reporter labels Aug 17, 2022
@artembilan artembilan self-assigned this Aug 17, 2022
artembilan added a commit to artembilan/spring-integration that referenced this issue Aug 17, 2022
Fixes spring-projects#3875

The class `static` variables and initialization block are performed on first class access.

* Add "fake" `ClassUtils.resolvePrimitiveType(Integer.class)` call to the `IntegrationRegistrar`
to trigger `ClassUtils` initialization with the current Spring `ClassLoader`

**Cherry-pick to `5.5.x`**
garyrussell pushed a commit that referenced this issue Aug 17, 2022
Fixes #3875

The class `static` variables and initialization block are performed on first class access.

* Add "fake" `ClassUtils.resolvePrimitiveType(Integer.class)` call to the `IntegrationRegistrar`
to trigger `ClassUtils` initialization with the current Spring `ClassLoader`

**Cherry-pick to `5.5.x`**
garyrussell pushed a commit that referenced this issue Aug 17, 2022
Fixes #3875

The class `static` variables and initialization block are performed on first class access.

* Add "fake" `ClassUtils.resolvePrimitiveType(Integer.class)` call to the `IntegrationRegistrar`
to trigger `ClassUtils` initialization with the current Spring `ClassLoader`

**Cherry-pick to `5.5.x`**
@grahamcox-oclc
Copy link
Author

FYI that I've just built and run our service using Spring Integration 5.5.15-SNAPSHOT and the problem no longer reproduces :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants