Why did Spring Boot 1.4 change its jar layout to locate application classes under BOOT-INF? - spring-boot

(This is primarily a history question. Pivotal recommended that all forum discussions take place on StackOverflow, which is why I am asking it here.)
What are the reasons that the Spring Boot project used to justify moving an application's classes and dependencies from the "top level" of the executable jar format to be underneath BOOT-INF/?
Just trying to guess, it seems that this makes it easy to extract only the application-related classes and jars from the fat jar with a simple java -xf the-jar.jar BOOT-INF/classes command. Was that it? Was there some other reason?

TL;DR
Packaging application classes in the root of the jar required Spring Boot's class loader to use an unconventional delegation model and also caused problems with Java agents.
Detailed explanation
When a jar file is launched with java -jar all of the classes in the root of the jar are on the class path of the system class loader. In a Spring Boot fat jar, this includes the classes for the launcher which is responsible for creating a class loader that can load the application's classes and their dependencies that are nested inside the fat jar.
In Spring Boot 1.3 and earlier, application classes are packaged in the root of a fat jar file. This means that they are on the class path of the system class loader. With a standard, parent first delegation model this would mean that application classes would be loaded by the system class loader rather than Spring Boot's class loader. This is problematic as it's only Spring Boot's class loader that can load the classes from the dependencies that are nested inside the fat jar. The result being that the application cannot load the classes of any of its dependencies.
Spring Boot 1.3 overcame this problem by using an unconventional delegation model for its class loader. It created a new class loader using the URLs from the system class loader but not using the system class loader as a parent – the system class loader's parent was used instead. This meant that Spring Boot's class loader would be used to load the application's classes in the root of the jar and the classes of the application's dependencies in the nested jars.
This approach had some drawbacks. The first was that it made Spring Boot's class loader rather complex. The second was that it broke a number of assumptions that Java agents make with regards to how their classes will be loaded. We worked around a couple of these but it became clear that we were fighting a losing battle.
Spring Boot 1.4 rearranges a fat jar to place application classes in BOOT-INF/classes (it also moves nested jars to BOOT-INF/lib but that has no effect from a class loading perspective). Moving the application classes into BOOT-INF/classes means that they are no longer on the class path of the system class loader. This means that Spring Boot's class loader can be configured to load classes from BOOT-INF/classes and from within the jars in BOOT-INF/lib and use the system class loader as its parent. Java agents can be packaged in the root of the jar from where they'll be loaded by the system class loader as usual.
For further reading you may be interested in the message on the commit that introduced the change and the other issues to which it links.

Related

Spring Boot JarFileSystem for wrapped JARs

I've got a Spring Boot application packaged as an Uber JAR. One of the wrapped JARs has logic within it to utilize a Java FileSystem for walking a resource tree that is embedded in that particular JAR file.
Unfortunately, this statement appears to be true:
Now opening jar files isn't a big deal. Esp with the introduction of the JarFileSystem. However Spring Boot includes jar files inside their jars. There is currently no support for that. You can't create a JarFileSystem inside a JarFileSystem.
With that being said, Spring Boot has a fair amount of handling surrounding it's wrapped JARs:
JarURLConnection
Everything else under org/springframework/boot/loader/jar
However, there doesn't appear to be any mechanism for registering Spring Boot's approach as a FileSystemProvider...
org.springframework.boot.loader.LaunchedURLClassLoader unable to load 3rd party FileSystemProvider implementation from spring boot executable JAR seemed like a similar problem, but didn't appear to be directly related.
Has anyone had any luck creating a FileSystem for one of Spring Boot's wrapped JARs?

how can I reflect classes in a springboot jar's BOOT-INF/classes

I need to read a Spring Boot jar and load all the clases on a ClassLoader.
My problem,in spring boot classes are on "/BOOT-INF/classes" directory and not on the root directory.
Anybody knows how i can load this classes in my ClassLoader?
to load the classes inside /BOOT-INF/classes,no the root jar 'org.springframework.*'
As Javassist only looks up classes from the class path and Spring Boot uses its own class loader hierarchy when bundling it in a self-executable. If you want to perform bytecode analysis on springboot user classes then only way is to extract the springboot jar and add each and every class to the classPath so that javassist can perform bytecode analysis on it.

Dynamic loading of spring bean from jar along with dependent beans

I am running a spring application.
My requirement is user will be placing a plugin jar file at run time at designated lib folder location.
This plugin jar file will have spring application context file as well. I want to load this jar, means all the classes - spring beans
and all its dependent beans/components(this is important), from this jar file at run time.
I do not want to create new/child application context and want to use the existing spring bean context loaded at application start up.
I reffered to few other similar threads/questions on SO and could resolve the issue of dynamically loading of spring beans.
But i am not able to resolve issue of loading all the dependent beans for the spring beans.
Could you please provide any pointers/hints to dynamically load all the dependent beans of spring bean(which is also)loaded at run time?
Thanks in advance,
Picku
If you want to be able to load the plugin after startup you are not going to get away with not creating another application context as a child.
I'd suggest you do exactly this and then create some hooks in parent context whereby your plugin will integrate itself.
The alternative is to include that plugin.jar in the main classpath and then restart the application to include the plugin.

Java Preferences API Classloading Challenges

Problem:
I have a custom implementation of java.util.prefs in a maven jar module.
I am providing the system property to -djava.util.prefs.PreferencesFactory=com.blah.CustomePrefsFactory
to my Spring boot app that is using the custom preferences.
When the Spring boot app uses the Preferences for the first time, the Java.util.Preferences.java tries to load the CustomePrefsFactory using the
ClassLoader.getSystemClassLoader().
This results in errors as nested jar classes are not available to a system class loader of the spring boot app.
Is there a way to make nested jar classes available to the system class loader?
The short answer is no.
System ClassLoader delegates to the Bootstrap ClassLoader and Extension ClassLoader and then tries to load classes from classpath, but not from nested JARs.
You have to implement or use an existing ClassLoader that supports that functionality.

Spring with maven-shade-plugin

I am trying to use to versions of spring in the same application: the first one is a webapp with spring 2.6 and the second it a jar client, with spring 4.0.2. The client communicates with another application and will be a dependency for the webapp. The problem is that the classloader will just load one time the common classes from spring and it will certainly fail.
I tried to use ElasticSearch aproach of using shaded dependencies(maven shade plugin) and relocate spring from the client to a different package (from org.springframework to my.springframework) and the uber jar seems to be constructed fine.
The issue is that Spring is based on spring.schemas and spring.handlers for validating xml config files and loads this files from classpath (META-INF folder and this paths are hardcoded in Spring code - e.q. PluggableSchemaResolver). I modified this files to point from org.srpingframework to my.springframework.
At runtime it seems that the classloader reads these files from the webapp, which has this files but with the real spring path and the exception is something like
org.realsearch.springframework.beans.FatalBeanException: Class [org.springframework.context.config.ContextNamespaceHandler] for namespace [http://www.springframework.org/schema/context] does not implement the [my.springframework.beans.factory.xml.NamespaceHandler] interface.
To me it seems that is impossible to achieve what I am trying (use tho spring versions in the same application with one of them relocated). Any ideas here? Am I wright?:d

Resources