spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.17k stars 40.68k forks source link

Give option to disable registration of TomcatURLStreamHandlerFactory #10529

Closed raphw closed 7 years ago

raphw commented 7 years ago

When running Tomcat embedded, it registers its TomcatURLStreamHandlerFactory in the URL::setURLStreamHandlerFactory. This factory can only be set once globally on the VM. Therefore, this can raise conflicts with other libraries that do the same.

Would you consider adding a property such that I can simply disable this in the startup? It is difficult to hook in that early, I do now use a SpringApplicationRunListener but it would be nice if there was something ready-made.

Disabling it is as easy as calling TomcatURLStreamHandlerFactory.disable() before starting the server for the first time.

Thanks!

wilkinsona commented 7 years ago

I am rather wary of making this too easy to do as, without it, parts of Tomcat will break. Unless you know what you're doing, the chance of shooting yourself in the foot could be quite high. That said, I believe it's only of relevance when using Tomcat embedded in a .war file.

Out of interest, what sort of app are your building where it isn't needed? Also, have you considered registering a user factory with TomcatURLStreamHandlerFactory?

raphw commented 7 years ago

For testing a bunch of microservices, I want to avoid spinning up a seperate process for every service but rather isolate those services by class loader. The services are rather simple and short-lived, therefore, they work just fine this way.

However, I need to disable this to avoid clashes in the URL registration. I find it generally a bad pattern to rely on this VM global, maybe this should be addressed in Tomcat but I would still appreciate the option.

wilkinsona commented 7 years ago

Given that disabling this is a single line of code, I'm not sure what better option we could provide. It doesn't feel like the sort of thing that would generally benefit from being externally configurable (via application.properties, for example). You said above that it's "difficult to hook in that early". Can you not call disable in your application's main method (prior to calling run) or in the code that's creating each ClassLoader that you're using for isolation?

raphw commented 7 years ago

We have quite a bunch of services which import a custom configuration that handles some general configuration like token security and so on. It would rather heavy for us to maintain this in the application start method whereas it would be easy to add this to a profile-specific yaml file in the common module.

You are however right about that I could probably just locate the class in the class loader that isolates the application. I was however struggeling to understand if this is compatible with Spring Boot's fat jar concept. I will try it, though! Thanks for the hint.

wilkinsona commented 7 years ago

I will try it, though! Thanks for the hint.

Great, thanks. Please let us know how it works out. If it doesn't work, we can look at alternatives.

spring-projects-issues commented 7 years ago

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

raphw commented 7 years ago

I finally tried this and unfortunately, it is not that easy. The application is started via the Spring Boot Launcher such that Tomcat ends up on another class loader. From a generic starter, I cannot interact with any classes of the application before running Spring Boot where I would need to inject some class into the application jar to modify the application.

wilkinsona commented 7 years ago

That's a shame. Thanks for following up.

I'm still of the opinion that this is an edge case that doesn't warrant a configuration property. For this to be a problem you need to be running multiple instances of Tomcat in the same JVM with a ClassLoader arrangement that means that Tomcat's classes are loaded on two or more different ClassLoaders.

As you've noted, disabling Tomcat's TomcatURLStreamHandlerFactory is a matter of making single method call at an appropriate time. Spring Boot provides more than one mechanism for doing so as part of an application being launched. You can use SpringApplicationRunListener as you've already noted. You could also use an ApplicationListener registered in spring.factories that listens for the ApplicationEnvironmentPreparedEvent.