Skip to content

Commit b360164

Browse files
committed
Add hints for web resource default locations
Closes gh-31278
1 parent 063e56d commit b360164

File tree

5 files changed

+141
-0
lines changed

5 files changed

+141
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.web;
18+
19+
import java.util.List;
20+
21+
import org.springframework.aot.hint.RuntimeHints;
22+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
23+
24+
/**
25+
* {@link RuntimeHintsRegistrar} for default locations of web resources.
26+
*
27+
* @author Stephane Nicoll
28+
* @since 3.0
29+
*/
30+
public class WebResourcesRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
31+
32+
private static final List<String> DEFAULT_LOCATIONS = List.of("META-INF/resources/", "resources/", "static/",
33+
"public/");
34+
35+
@Override
36+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
37+
ClassLoader classLoaderToUse = (classLoader != null) ? classLoader : getClass().getClassLoader();
38+
String[] locations = DEFAULT_LOCATIONS.stream()
39+
.filter((candidate) -> classLoaderToUse.getResource(candidate) != null)
40+
.map((location) -> location + "*").toArray(String[]::new);
41+
if (locations.length > 0) {
42+
hints.resources().registerPattern((hint) -> hint.includes(locations));
43+
}
44+
}
45+
46+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.boot.autoconfigure.web.ServerProperties;
4040
import org.springframework.boot.autoconfigure.web.WebProperties;
4141
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
42+
import org.springframework.boot.autoconfigure.web.WebResourcesRuntimeHintsRegistrar;
4243
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
4344
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
4445
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties.Format;
@@ -50,6 +51,7 @@
5051
import org.springframework.context.annotation.Bean;
5152
import org.springframework.context.annotation.Configuration;
5253
import org.springframework.context.annotation.Import;
54+
import org.springframework.context.annotation.ImportRuntimeHints;
5355
import org.springframework.core.Ordered;
5456
import org.springframework.core.annotation.Order;
5557
import org.springframework.format.FormatterRegistry;
@@ -104,6 +106,7 @@
104106
@ConditionalOnClass(WebFluxConfigurer.class)
105107
@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class })
106108
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
109+
@ImportRuntimeHints(WebResourcesRuntimeHintsRegistrar.class)
107110
public class WebFluxAutoConfiguration {
108111

109112
@Bean

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.springframework.boot.autoconfigure.web.WebProperties;
5151
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
5252
import org.springframework.boot.autoconfigure.web.WebProperties.Resources.Chain.Strategy;
53+
import org.springframework.boot.autoconfigure.web.WebResourcesRuntimeHintsRegistrar;
5354
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
5455
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
5556
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.Format;
@@ -64,6 +65,7 @@
6465
import org.springframework.context.annotation.Bean;
6566
import org.springframework.context.annotation.Configuration;
6667
import org.springframework.context.annotation.Import;
68+
import org.springframework.context.annotation.ImportRuntimeHints;
6769
import org.springframework.core.Ordered;
6870
import org.springframework.core.annotation.Order;
6971
import org.springframework.core.io.Resource;
@@ -143,6 +145,7 @@
143145
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
144146
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
145147
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
148+
@ImportRuntimeHints(WebResourcesRuntimeHintsRegistrar.class)
146149
public class WebMvcAutoConfiguration {
147150

148151
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.web;
18+
19+
import java.net.URL;
20+
import java.net.URLClassLoader;
21+
import java.util.List;
22+
import java.util.function.Consumer;
23+
24+
import org.junit.jupiter.api.Test;
25+
26+
import org.springframework.aot.hint.ResourcePatternHint;
27+
import org.springframework.aot.hint.ResourcePatternHints;
28+
import org.springframework.aot.hint.RuntimeHints;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
/**
33+
* Tests for {@link WebResourcesRuntimeHintsRegistrar}.
34+
*
35+
* @author Stephane Nicoll
36+
*/
37+
class WebResourcesRuntimeHintsRegistrarTests {
38+
39+
@Test
40+
void registerHintsWithAllLocations() {
41+
RuntimeHints hints = register(
42+
new TestClassLoader(List.of("META-INF/resources/", "resources/", "static/", "public/")));
43+
assertThat(hints.resources().resourcePatterns()).singleElement()
44+
.satisfies(include("META-INF/resources/*", "resources/*", "static/*", "public/*"));
45+
}
46+
47+
@Test
48+
void registerHintsWithOnlyStaticLocations() {
49+
RuntimeHints hints = register(new TestClassLoader(List.of("static/")));
50+
assertThat(hints.resources().resourcePatterns()).singleElement().satisfies(include("static/*"));
51+
}
52+
53+
@Test
54+
void registerHintsWithNoLocation() {
55+
RuntimeHints hints = register(new TestClassLoader(List.of()));
56+
assertThat(hints.resources().resourcePatterns()).isEmpty();
57+
}
58+
59+
RuntimeHints register(ClassLoader classLoader) {
60+
RuntimeHints hints = new RuntimeHints();
61+
WebResourcesRuntimeHintsRegistrar registrar = new WebResourcesRuntimeHintsRegistrar();
62+
registrar.registerHints(hints, classLoader);
63+
return hints;
64+
}
65+
66+
private Consumer<ResourcePatternHints> include(String... patterns) {
67+
return (hint) -> {
68+
assertThat(hint.getIncludes()).map(ResourcePatternHint::getPattern).containsExactly(patterns);
69+
assertThat(hint.getExcludes()).isEmpty();
70+
};
71+
}
72+
73+
private static class TestClassLoader extends URLClassLoader {
74+
75+
private final List<String> availableResources;
76+
77+
TestClassLoader(List<String> availableResources) {
78+
super(new URL[0], TestClassLoader.class.getClassLoader());
79+
this.availableResources = availableResources;
80+
}
81+
82+
@Override
83+
public URL getResource(String name) {
84+
return (this.availableResources.contains(name)) ? super.getResource("web/custom-resource.txt") : null;
85+
}
86+
87+
}
88+
89+
}

spring-boot-project/spring-boot-autoconfigure/src/test/resources/web/custom-resource.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)