Set appropriate ClassLoaderProvider in Spring Boot integrations#518
Conversation
|
This also might mean |
| ClassLoader classLoader = applicationContext.getClassLoader(); | ||
| // Fall back to the current ClassLoader if null | ||
| if (classLoader == null) | ||
| classLoader = this.getClass().getClassLoader(); |
There was a problem hiding this comment.
https://docs.spring.io/spring-boot/docs/1.5.16.RELEASE/reference/html/using-boot-devtools.html
20.2.6 Known limitations
Question: Wouldn't it be better to use: Thread.currentThread().getContextClassLoader() ?
There was a problem hiding this comment.
(I might have misunderstood your comment, this reply assumed you meant replacing the entire ClassLoader search with Thread.currentThread().getContextClassLoader() alone and ignoring the ApplicationContext)
I'm a bit wary of forcing users to use the current thread's context ClassLoader, rather than using the application-context-supplied one. ResourceLoader#getClassLoader (supertype of ApplicationContext) states:
Clients which need to access the
ClassLoaderdirectly can do so in a uniform manner with theResourceLoader, rather than relying on the thread contextClassLoader.
which I think is a more reliable technique in case a user wants to configure some Spring property or callback that changes the application context ClassLoader.
Additional context:
This answer on StackOverflow is quite vocal about the use of Thread.currentThread().getContextClassLoader() https://stackoverflow.com/a/36228195/9073728:
If a library uses hacks such as Thread.getContextClassLoader(), sun.misc.VM.latestUserDefinedLoader(), or sun.reflect.Reflection.getCallerClass() it is a bug caused by a deficiency in the API. Basically, Thread.getContextClassLoader() exists only because whoever designed the ObjectInputStream API forgot to accept the ClassLoader as a parameter, and this mistake has haunted the Java community to this day.
By default, getting the application context's ClassLoader delegates from AnnotationConfigServletWebServerApplicationContext to ClassLoaderFilesResourcePatternResolver to ServletContextResourcePatternResolver to WebApplicationContextResourceLoader to DefaultResourceLoader which finally checks its provided classLoader (null by default) and falls back to ClassUtils.getDefaultClassLoader() which calls Thread.currentThread().getContextClassLoader() first, so by default the two sources of context ClassLoaders should be the same.
There was a problem hiding this comment.
I actually think the fallback can be removed entirely and replaced with an exception, since ResourceLoader#getClassLoader's documentation also states:
Returns: the ClassLoader (only null if even the system ClassLoader isn't accessible)
There was a problem hiding this comment.
Okay, my comment was only about the fallback.
I generally like your solution, I'm just trying to see if it might have some other implications that I haven't thought of at the moment.
|
This PR doesn't solve the issue with the DevTools completely in my test application. With the changes, the application starts (and does not complain about the missing bean) but when making a change in the application and with the restart, I still get the Class cast exception because of the different classloaders that are in use. The solution is to put this
within spring-devtools.properties file, just as you need to do with version 7.1 of MicroStream. What am I missing? |
|
@rdebusscher I am able to reproduce that issue, and it occurs because the |
|
I also ran into another issue, unrelated to Spring Boot. When redefining classes using DCEVM (only Java 8, use TravaOpenJDK for Java 11, or JetBrainsRuntime for Java 11 or 17), a This doesn't seem easily fixable though. If you want to keep caching reflective accesses (like
private transient volatile int classRedefinedCount; |
|
The usage of And the usage of the spring-devtools.properties file solves this by making sure that the MicroStream classes are part of the restart classloader. But the usage of the restart classloader is not causing the recreation of the |
|
I disagree with adding MicroStream's classes to the restart classloader. The restart classloader is only supposed to contain classes that are possible to change - i.e. the end-user's classes. MicroStream internal classes do not change, and thus should not be included in the restart classloader. As it stands now, in the current release of MicroStream, reloading the Spring application does destroy the Since Additionally, I think I have a workaround for my class redefinition issue using Spring's I will fixup+rebase my initial commit to make StorageManagerProvider's static field an instance field, and add a new commit attempting to prevent stored classes from being redefined. |
The component is already a singleton, so having a static field to store `EmbeddedStorageManager`s just leaks them between restarts. They already get shutdown on restart since they are Beans, so the leaked `EmbeddedStorageManager`s aren't usable.
…ss `StorageMetaData`
5f31c0d to
1e8433b
Compare
|
My technique to prevent storage classes from being redefined did not work because Java Agent self-Instrumentation is not supported, and that's the only way to receive notification of class redefinition events. If you want resilience against class redefinition, I think the only way is to check the |
|
From within IDE, the Spring Boot app is working, with reload. But when running through 'java -jar', I get an null Root (when dev-tools as optional as in documentation, without this dependency, it also works). So I need more time to test out what is going on. |
The Spring application context's class loader is now always used.
|
@rdebusscher I've tried to reproduce the issue you mentioned while running the Spring Boot jar directly, but I couldn't using the Gradle starter project. I've shared my reproduction here: https://github.com/magneticflux-/microstream-issue517-demo, it requires the snapshot to be Using that reproduction:
Details:When running inside IntelliJ, changing While in debug mode inside IntelliJ, changing a class and clicking "Build Project" causes the class to be reloaded near-instantly without restarting the application. Like I mentioned before, this can cause issues with serialization when the reloaded class has already had a |
|
I found the problem, asynchronous initialisation which means you can operate on a null Root. Not related to this PR. #530 |
|
We have a look what is needed (regarding intellectual Property flow) to accept your PR as we are moving the code to the Eclipse Foundation. |
rdebusscher
left a comment
There was a problem hiding this comment.
Since integrations are not donated to Eclipse Foundation, no additional paperwork is needed.
Closes #517
I wasn't able to add failing integration tests because Spring Boot DevTools checks if it's running in a test environment or in GraalVM multiple times, and deactivates itself.