-
Notifications
You must be signed in to change notification settings - Fork 41.6k
Description
Introduction
In SPR-12857 a feature was implemented for registering custom resource protocol resolvers on DefaultResourceLoader, including a setter on ConfigurableApplicationContext to expose this feature to application initializers. When enabling DevTools however a custom ResourceLoader is injected into the application context, hiding the custom protocol resolvers.
Analysis
The issue arrises from code in org.springframework.boot.devtools.restart.ClassLoaderFilesResourcePatternResolver.
Non-web Applications
For non-web applications, a PathMatchingResourcePatternResolver is used with a reference to either the ApplicationContext's resourceLoader, or a new DefaultResourceLoader if that is null:
private static class ResourcePatternResolverFactory {
public ResourcePatternResolver getResourcePatternResolver(
ApplicationContext applicationContext, ResourceLoader resourceLoader) {
return new PathMatchingResourcePatternResolver(resourceLoader == null
? new DefaultResourceLoader() : resourceLoader);
}
}Hence the protocol resolvers that are registered onto the ApplicationContext itself are no longer considered. I prepared a PR to change this but unfortunately, using applicationContext itself instead of a new DefaultResourceLoader fails the tests, as circular calls occur when requesting the ApplicationContext's classloader.
Web Applications
For web applications, a custom code path is followed. Again, when a ResourceLoader has been explicitly set onto the ApplicationContext it is used, otherwise a WebApplicationContextResourceLoader is created instead:
private static class WebResourcePatternResolverFactory
extends ResourcePatternResolverFactory {
@Override
public ResourcePatternResolver getResourcePatternResolver(
ApplicationContext applicationContext, ResourceLoader resourceLoader) {
if (applicationContext instanceof WebApplicationContext) {
return new ServletContextResourcePatternResolver(resourceLoader == null
? new WebApplicationContextResourceLoader(
(WebApplicationContext) applicationContext)
: resourceLoader);
}
return super.getResourcePatternResolver(applicationContext, resourceLoader);
}
}This may seem to work as applicationContext is used this time, however it is only used for configuring a ServletContextResource:
private static class WebApplicationContextResourceLoader
extends DefaultResourceLoader {
private final WebApplicationContext applicationContext;
WebApplicationContextResourceLoader(WebApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
protected Resource getResourceByPath(String path) {
if (this.applicationContext.getServletContext() != null) {
return new ServletContextResource(
this.applicationContext.getServletContext(), path);
}
return super.getResourceByPath(path);
}
}As can be seen, the above implementation returns a ServletContextResource without ever consulting the ApplicationContext's custom protocol resolvers. Somehow that feels incorrect, as its parent implementation may have returned an UrlResource as well, for example.
Workaround
I realize now that I can circumvent the problem by setting a new DefaultResourceLoader with the GenericApplicationContext (not ConfigurableApplicationContext unfortunately) from within the initializer and register my custom protocol resolver onto that, as then this resource loader is supposedly kept in the delegation chain:
public class ResourceLoaderInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
@Override
public void initialize(GenericApplicationContext context) {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
resourceLoader.addProtocolResolver(...);
context.setResourceLoader(resourceLoader);
}
}This potentially introduces another problem however, in that an AbstractApplicationContext registers itself as ResourceLoader in the main BeanFactory and this registration is not reset when a custom resourceLoader is assigned, therefore DI of ResourceLoader will continue to resolve the ApplicationContext itself. I need to file an issue with Spring Context for this, as this is not a Spring Boot problem.
@snicoll I am indeed the one who asked you about this at Spring I/O 😃 Only now did I get around to investigate some more and file this report.