Improve ApplicationResourceLoader `preferFileResolution` support

Update `ApplicationResourceLoader` so that the `preferFileResolution`
flag now also supports `FilteredReactiveWebContextResource`.

See gh-44535

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
Dmytro Nosan 2025-02-21 19:05:40 +02:00 committed by Phillip Webb
parent 80632bd41c
commit 98b2b5f32a
2 changed files with 66 additions and 19 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -132,8 +132,8 @@ public class ApplicationResourceLoader extends DefaultResourceLoader {
* class loader at the time this call is made.
* @param resourceLoader the delegate resource loader
* @param preferFileResolution if file based resolution is preferred over
* {@code ServletContextResource} or {@link ClassPathResource} when no resource prefix
* is provided.
* {@code ServletContextResource}, {@code FilteredReactiveWebContextResource} or
* {@link ClassPathResource} when no resource prefix is provided.
* @return a {@link ResourceLoader} instance
* @since 3.4.1
*/
@ -212,28 +212,31 @@ public class ApplicationResourceLoader extends DefaultResourceLoader {
private static final String SERVLET_CONTEXT_RESOURCE_CLASS_NAME = "org.springframework.web.context.support.ServletContextResource";
private static final String FILTERED_REACTIVE_WEB_CONTEXT_RESOURCE_CLASS_NAME = "org.springframework.boot.web.reactive.context.FilteredReactiveWebContextResource";
private final ResourceLoader resourceLoader;
private final List<ProtocolResolver> protocolResolvers;
private final boolean preferFileResolution;
private Class<?> servletContextResourceClass;
private final Class<?> servletContextResourceClass;
private final Class<?> filteredReactiveWebContextResourceClass;
ProtocolResolvingResourceLoader(ResourceLoader resourceLoader, List<ProtocolResolver> protocolResolvers,
boolean preferFileResolution) {
this.resourceLoader = resourceLoader;
this.protocolResolvers = protocolResolvers;
this.preferFileResolution = preferFileResolution;
this.servletContextResourceClass = resolveServletContextResourceClass(
resourceLoader.getClass().getClassLoader());
this.servletContextResourceClass = resolveServletContextResourceClass(resourceLoader);
this.filteredReactiveWebContextResourceClass = resolveFilteredReactiveWebContextResourceClass(
resourceLoader);
}
private static Class<?> resolveServletContextResourceClass(ClassLoader classLoader) {
if (!ClassUtils.isPresent(SERVLET_CONTEXT_RESOURCE_CLASS_NAME, classLoader)) {
return null;
}
return ClassUtils.resolveClassName(SERVLET_CONTEXT_RESOURCE_CLASS_NAME, classLoader);
@Override
public ClassLoader getClassLoader() {
return this.resourceLoader.getClassLoader();
}
@Override
@ -247,24 +250,47 @@ public class ApplicationResourceLoader extends DefaultResourceLoader {
}
}
Resource resource = this.resourceLoader.getResource(location);
if (this.preferFileResolution
&& (isClassPathResourceByPath(location, resource) || isServletResource(resource))) {
resource = new ApplicationResource(location);
if (shouldUseFileResolution(location, resource)) {
return new ApplicationResource(location);
}
return resource;
}
private boolean shouldUseFileResolution(String location, Resource resource) {
if (!this.preferFileResolution) {
return false;
}
return isClassPathResourceByPath(location, resource) || isServletContextResource(resource)
|| isFilteredReactiveWebContextResource(resource);
}
private boolean isClassPathResourceByPath(String location, Resource resource) {
return (resource instanceof ClassPathResource) && !location.startsWith(CLASSPATH_URL_PREFIX);
}
private boolean isServletResource(Resource resource) {
return this.servletContextResourceClass != null && this.servletContextResourceClass.isInstance(resource);
private boolean isServletContextResource(Resource resource) {
return (this.servletContextResourceClass != null) && this.servletContextResourceClass.isInstance(resource);
}
@Override
public ClassLoader getClassLoader() {
return this.resourceLoader.getClassLoader();
private boolean isFilteredReactiveWebContextResource(Resource resource) {
return (this.filteredReactiveWebContextResourceClass != null)
&& this.filteredReactiveWebContextResourceClass.isInstance(resource);
}
private static Class<?> resolveServletContextResourceClass(ResourceLoader resourceLoader) {
return resolveClassName(SERVLET_CONTEXT_RESOURCE_CLASS_NAME, resourceLoader.getClass().getClassLoader());
}
private static Class<?> resolveFilteredReactiveWebContextResourceClass(ResourceLoader resourceLoader) {
return resolveClassName(FILTERED_REACTIVE_WEB_CONTEXT_RESOURCE_CLASS_NAME,
resourceLoader.getClass().getClassLoader());
}
private static Class<?> resolveClassName(String clazz, ClassLoader classLoader) {
if (!ClassUtils.isPresent(clazz, classLoader)) {
return null;
}
return ClassUtils.resolveClassName(clazz, classLoader);
}
}

View File

@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.testsupport.classpath.resources.ResourcePath;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
@ -37,6 +38,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.mock.web.MockServletContext;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.support.ServletContextResource;
import org.springframework.web.context.support.ServletContextResourceLoader;
@ -209,6 +211,25 @@ class ApplicationResourceLoaderTests {
assertThat(regularLoader.getResource("src/main/resources/a-file")).isInstanceOf(ServletContextResource.class);
}
@Test
void getResourceWhenFilteredReactiveWebContextResourceWithPreferFileResolution() throws Exception {
ResourceLoader resourceLoader = ApplicationResourceLoader
.get(new AnnotationConfigReactiveWebApplicationContext(), true);
Resource resource = resourceLoader.getResource("src/main/resources/a-file");
assertThat(resource).isInstanceOf(FileSystemResource.class);
assertThat(resource.getFile().getAbsoluteFile())
.isEqualTo(new File("src/main/resources/a-file").getAbsoluteFile());
}
@Test
void getResourceWhenFilteredReactiveWebContextResource() {
ResourceLoader resourceLoader = ApplicationResourceLoader
.get(new AnnotationConfigReactiveWebApplicationContext(), false);
Resource resource = resourceLoader.getResource("src/main/resources/a-file");
assertThat(resource).isInstanceOf(ClassUtils.resolveClassName(
"org.springframework.boot.web.reactive.context.FilteredReactiveWebContextResource", null));
}
@Test
void getClassLoaderReturnsDelegateClassLoader() {
ClassLoader classLoader = new TestClassLoader(this::useTestProtocolResolversFactories);