https://github.com/danielfernandez/test-spring-boot-tomcat
Test repository for https://jira.spring.io/browse/SPR-14932
https://github.com/danielfernandez/test-spring-boot-tomcat
Last synced: 11 months ago
JSON representation
Test repository for https://jira.spring.io/browse/SPR-14932
- Host: GitHub
- URL: https://github.com/danielfernandez/test-spring-boot-tomcat
- Owner: danielfernandez
- License: apache-2.0
- Created: 2016-11-21T20:50:32.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2016-11-21T22:27:57.000Z (over 9 years ago)
- Last Synced: 2025-03-25T22:35:39.763Z (about 1 year ago)
- Language: Shell
- Homepage:
- Size: 66.4 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.markdown
- License: LICENSE
Awesome Lists containing this project
README
Test repository for SPR-14932
-----------------------------
**Link**: [SPR-14932](https://jira.spring.io/browse/SPR-14932)
**Title**: Class loading issues due to thread context classloader hierarchy (Spring Web Reactive + Tomcat)
**Tag**: Use tag `SPR-14932`
## Scenario
The scenario tested in this repository is the following:
* A web application uses a library.
* That library needs to dynamically load a class that implements a specific interface, for example, in
order to select the best implementation for the specific environment in which it is being run.
## Repository Contents
This repository consists of:
* A library (`-lib`) including two classes called `SomeIntefaceClassClassLoaderUtils` and
`SomeInterfaceThreadContextClassLoaderUtils`, which upon initialization (static block) try to
dynamically load and instance an implementation of an interface called `SomeInterface`. The former
utility class uses `SomeInterface.class.getClassLoader()` to obtain the class loader to be used
for this, and the latter uses `Thread.currentThread().getContextClassLoader()`.
* Four example web applications that provide two URLs: `/class` and `/threadcontext`, which respectively
call the two `*Utils` classes in the library.
* **[142mvc]** A Spring Boot `1.4.2.RELEASE` webapp using Spring MVC `4.3.3.RELEASE` on Tomcat.
* **[200mvc]** A Spring Boot `2.0.0.BUILD-SNAPSHOT` webapp using Spring Web MVC `5.0.0.BUILD-SNAPSHOT` on Tomcat.
* **[200reactive-tomcat]** A Spring Boot `2.0.0.BUILD-SNAPSHOT` webapp using Spring Web Reactive `5.0.0.BUILD-SNAPSHOT` on Tomcat.
* **[200reactive-netty]** A Spring Boot `2.0.0.BUILD-SNAPSHOT` webapp using Spring Web Reactive `5.0.0.BUILD-SNAPSHOT` on Netty.
All Spring Boot applications were generated using [http://start.spring.io](http://start.spring.io), with minor changes to
their `pom.xml` to add the library dependency and/or enable or disable Tomcat/Netty.
## Observed Results
The observed results are:
* Web applications [142mvc], [200mvc] and [200reactive-netty] work OK, and are able to call the library and
let it load the required interface implementation without issues.
* **Web application [200reactive-tomcat] fails** when using the *thread context* class loader, throwing a
`ClassNotFoundException` on the desired interface implementation class. When using the *class* class loader it works OK.
This is the detail of what is happening at the [200reactive-tomcat] application:
* The *thread-context* class loader is an `org.apache.catalina.loader.ParallelWebappClassLoader` instance
with no class path.
* The *class* class loader is an `org.springframework.boot.loader.LaunchedURLClassLoader` instance with a
class path including all the inner `.jar` files inside the Spring Boot *über jar*.
* The `ParallelWebappClassLoader` **does not delegate** to the `LaunchedURLClassLoader`, but instead
directly delegates to the `sun.misc.Launcher$AppClassLoader` that the `LaunchedURLClassLoader` delegates too.
The above situation is what causes the `ClassNotFoundException` when executing [200reactive-tomcat] and the
*thread-context* class loader is used to load the desired class at the library.
## Class Loader Hierarchy Comparison
This is the *thread-context* class loader hierarchy for [200mvc] — **OK**:
```
+-> sun.misc.Launcher$ExtClassLoader - [sun.misc.Launcher$ExtClassLoader@198ff037]
+-> sun.misc.Launcher$AppClassLoader - [sun.misc.Launcher$AppClassLoader@330bedb4]
+-> org.springframework.boot.loader.LaunchedURLClassLoader - [org.springframework.boot.loader.LaunchedURLClassLoader@65ab7765]
+-> org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader - [TomcatEmbeddedWebappClassLoader\n\n context: ROOT\n\n delegate: true\n\n----------> Parent Classloader:\n\norg.springframework.boot.loader.LaunchedURLClassLoader@65ab7765\n\n]
```
This is the *thread-context* class loader hierarchy for [200reactive-netty] — **OK**:
```
+-> sun.misc.Launcher$ExtClassLoader - [sun.misc.Launcher$ExtClassLoader@67449f2]
+-> sun.misc.Launcher$AppClassLoader - [sun.misc.Launcher$AppClassLoader@330bedb4]
+-> org.springframework.boot.loader.LaunchedURLClassLoader - [org.springframework.boot.loader.LaunchedURLClassLoader@65ab7765]
```
This is the *thread-context* class loader hierarchy for [200reactive-tomcat] — **FAIL**:
```
+-> sun.misc.Launcher$ExtClassLoader - [sun.misc.Launcher$ExtClassLoader@32e33c96]
+-> sun.misc.Launcher$AppClassLoader - [sun.misc.Launcher$AppClassLoader@330bedb4]
+-> org.apache.catalina.loader.ParallelWebappClassLoader - [ParallelWebappClassLoader\n\n context: ROOT\n\n delegate: false\n\n----------> Parent Classloader:\n\nsun.misc.Launcher$AppClassLoader@330bedb4\n\n]
```
## Possible Diagnosis
The `org.apache.catalina.loader.ParallelWebappClassLoader` being used as thread context class loader in [reactive200-tomcat]
**should delegate to `org.springframework.boot.loader.LaunchedURLClassLoader`, but it doesn't**.
## How to test
First, the library has to be compiled, packaged and put into the local maven repository:
```
cd test-spring-boot-tomcat-lib
mvn clean compile package install
```
Then, in order to run any of the web applications, the Spring Boot applications have to be packaged and then
executed. **Running from an IDE such as IntelliJ or Eclipse does not correctly test the scenario** given the
different class path organizations applied by the IDEs.
```
mvn -U clean compile package
cd target/
java -jar test-spring-boot-[app].jar
```
Then a browser can be used to access `/threadcontext` or `/class` and see the results. The web applications
will output debug information about the class loaders being used through the log.
By default, when accessing `/threadcontext` or `/class` only the hierarchy of class loaders will be output through
the log. In order to see the full class path for each class loader in the hierarchy (very verbose), use:
```
mvn -U clean compile package
cd target/
java -Dlogging.level.classloaderlog=TRACE -jar test-spring-boot-[app].jar
```