Honor ProtocolResolvers in GenericApplicationContext

When the ProtocolResolver SPI was introduced in Spring Framework 4.3,
support for protocol resolvers was added in DefaultResourceLoader's
getResource() implementation; however, GenericApplicationContext's
overridden getResource() implementation was not updated accordingly.

Prior to this commit, if a GenericApplicationContext was configured
with a custom ResourceLoader, registered protocol resolvers were
ignored.

This commit ensures that protocol resolvers are honored in
GenericApplicationContext even if a custom ResourceLoader is used.

Closes gh-28703
This commit is contained in:
Sam Brannen 2022-06-27 18:00:29 +02:00
parent a970516080
commit 9868c28c73
3 changed files with 78 additions and 7 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@ -33,6 +33,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ProtocolResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
@ -86,6 +87,7 @@ import org.springframework.util.Assert;
*
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
* @since 1.1.2
* @see #registerBeanDefinition
* @see #refresh()
@ -216,13 +218,23 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
//---------------------------------------------------------------------
/**
* This implementation delegates to this context's ResourceLoader if set,
* falling back to the default superclass behavior else.
* @see #setResourceLoader
* This implementation delegates to this context's {@code ResourceLoader} if set,
* falling back to the default superclass behavior otherwise.
* <p>As of Spring Framework 5.3.22, this method also honors registered
* {@linkplain #getProtocolResolvers() protocol resolvers} when a custom
* {@code ResourceLoader} has been set.
* @see #setResourceLoader(ResourceLoader)
* @see #addProtocolResolver(ProtocolResolver)
*/
@Override
public Resource getResource(String location) {
if (this.resourceLoader != null) {
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
return this.resourceLoader.getResource(location);
}
return super.getResource(location);
@ -231,7 +243,7 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
/**
* This implementation delegates to this context's ResourceLoader if it
* implements the ResourcePatternResolver interface, falling back to the
* default superclass behavior else.
* default superclass behavior otherwise.
* @see #setResourceLoader
*/
@Override

View File

@ -24,15 +24,28 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.ProtocolResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.metrics.jfr.FlightRecorderApplicationStartup;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
/**
* Tests for {@link GenericApplicationContext}.
*
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
*/
class GenericApplicationContextTests {
@ -209,6 +222,39 @@ class GenericApplicationContextTests {
assertThat(context.getBeanFactory().getApplicationStartup()).isEqualTo(applicationStartup);
}
@Test
void getResourceWithDefaultResourceLoader() {
assertGetResourceSemantics(null, ClassPathResource.class);
}
@Test
void getResourceWithCustomResourceLoader() {
assertGetResourceSemantics(new FileSystemResourceLoader(), FileSystemResource.class);
}
private void assertGetResourceSemantics(ResourceLoader resourceLoader, Class<? extends Resource> defaultResouceType) {
if (resourceLoader != null) {
context.setResourceLoader(resourceLoader);
}
String pingLocation = "ping:foo";
String fileLocation = "file:foo";
Resource resource = context.getResource(pingLocation);
assertThat(resource).isInstanceOf(defaultResouceType);
resource = context.getResource(fileLocation);
assertThat(resource).isInstanceOf(FileUrlResource.class);
context.addProtocolResolver(new PingPongProtocolResolver());
resource = context.getResource(pingLocation);
assertThat(resource).asInstanceOf(type(ByteArrayResource.class))
.extracting(bar -> new String(bar.getByteArray(), UTF_8))
.isEqualTo("pong:foo");
resource = context.getResource(fileLocation);
assertThat(resource).isInstanceOf(FileUrlResource.class);
}
static class BeanA {
@ -236,4 +282,15 @@ class GenericApplicationContextTests {
static class BeanC {}
static class PingPongProtocolResolver implements ProtocolResolver {
@Override
public Resource resolve(String location, ResourceLoader resourceLoader) {
if (location.startsWith("ping:")) {
return new ByteArrayResource(("pong:" + location.substring(5)).getBytes(UTF_8));
}
return null;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@ -32,7 +32,8 @@ import org.springframework.util.StringUtils;
/**
* Default implementation of the {@link ResourceLoader} interface.
* Used by {@link ResourceEditor}, and serves as base class for
*
* <p>Used by {@link ResourceEditor}, and serves as base class for
* {@link org.springframework.context.support.AbstractApplicationContext}.
* Can also be used standalone.
*
@ -114,6 +115,7 @@ public class DefaultResourceLoader implements ResourceLoader {
* Return the collection of currently registered protocol resolvers,
* allowing for introspection as well as modification.
* @since 4.3
* @see #addProtocolResolver(ProtocolResolver)
*/
public Collection<ProtocolResolver> getProtocolResolvers() {
return this.protocolResolvers;