Move spring.resources.* properties to spring.web.resources.*

Closes gh-23917
pull/23986/head
Andy Wilkinson 4 years ago
parent 929d8fcd3c
commit c22e655848

@ -19,9 +19,13 @@ package org.springframework.boot.autoconfigure.web;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources.Chain;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
@ -41,10 +45,11 @@ class OnEnabledResourceChainCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getEnvironment();
boolean fixed = getEnabledProperty(environment, "strategy.fixed.", false);
boolean content = getEnabledProperty(environment, "strategy.content.", false);
Boolean chain = getEnabledProperty(environment, "", null);
Boolean match = ResourceProperties.Chain.getEnabled(fixed, content, chain);
String prefix = determineResourcePropertiesPrefix(environment);
boolean fixed = getEnabledProperty(environment, prefix, "strategy.fixed.", false);
boolean content = getEnabledProperty(environment, prefix, "strategy.content.", false);
Boolean chain = getEnabledProperty(environment, prefix, "", null);
Boolean match = Chain.getEnabled(fixed, content, chain);
ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnEnabledResourceChain.class);
if (match == null) {
if (ClassUtils.isPresent(WEBJAR_ASSET_LOCATOR, getClass().getClassLoader())) {
@ -58,8 +63,19 @@ class OnEnabledResourceChainCondition extends SpringBootCondition {
return ConditionOutcome.noMatch(message.because("disabled"));
}
private Boolean getEnabledProperty(ConfigurableEnvironment environment, String key, Boolean defaultValue) {
String name = "spring.resources.chain." + key + "enabled";
@SuppressWarnings("deprecation")
private String determineResourcePropertiesPrefix(Environment environment) {
BindResult<org.springframework.boot.autoconfigure.web.ResourceProperties> result = Binder.get(environment)
.bind("spring.resources", org.springframework.boot.autoconfigure.web.ResourceProperties.class);
if (result.isBound() && result.get().hasBeenCustomized()) {
return "spring.resources.chain.";
}
return "spring.web.resources.chain.";
}
private Boolean getEnabledProperty(ConfigurableEnvironment environment, String prefix, String key,
Boolean defaultValue) {
String name = prefix + key + "enabled";
return environment.getProperty(name, Boolean.class, defaultValue);
}

@ -17,14 +17,10 @@
package org.springframework.boot.autoconfigure.web;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.http.CacheControl;
/**
* Properties used to configure resource handling.
@ -35,137 +31,79 @@ import org.springframework.http.CacheControl;
* @author Venil Noronha
* @author Kristine Jetzke
* @since 1.1.0
* @deprecated since 2.4.0 in favor of {@link WebProperties.Resources}
*/
@Deprecated
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
/**
* Whether to enable default resource handling.
*/
private boolean addMappings = true;
public class ResourceProperties extends Resources {
private final Chain chain = new Chain();
private final Cache cache = new Cache();
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.static-locations")
public String[] getStaticLocations() {
return this.staticLocations;
}
public void setStaticLocations(String[] staticLocations) {
this.staticLocations = appendSlashIfNecessary(staticLocations);
}
private String[] appendSlashIfNecessary(String[] staticLocations) {
String[] normalized = new String[staticLocations.length];
for (int i = 0; i < staticLocations.length; i++) {
String location = staticLocations[i];
normalized[i] = location.endsWith("/") ? location : location + "/";
}
return normalized;
return super.getStaticLocations();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.add-mappings")
public boolean isAddMappings() {
return this.addMappings;
}
public void setAddMappings(boolean addMappings) {
this.addMappings = addMappings;
return super.isAddMappings();
}
@Override
public Chain getChain() {
return this.chain;
}
@Override
public Cache getCache() {
return this.cache;
}
/**
* Configuration for the Spring Resource Handling chain.
*/
public static class Chain {
/**
* Whether to enable the Spring Resource Handling chain. By default, disabled
* unless at least one strategy has been enabled.
*/
private Boolean enabled;
@Deprecated
public static class Chain extends Resources.Chain {
/**
* Whether to enable caching in the Resource chain.
*/
private boolean cache = true;
private final org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy strategy = new org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy();
/**
* Whether to enable HTML5 application cache manifest rewriting.
*/
private boolean htmlApplicationCache = false;
/**
* Whether to enable resolution of already compressed resources (gzip, brotli).
* Checks for a resource name with the '.gz' or '.br' file extensions.
*/
private boolean compressed = false;
private final Strategy strategy = new Strategy();
/**
* Return whether the resource chain is enabled. Return {@code null} if no
* specific settings are present.
* @return whether the resource chain is enabled or {@code null} if no specified
* settings are present.
*/
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.enabled")
public Boolean getEnabled() {
return getEnabled(getStrategy().getFixed().isEnabled(), getStrategy().getContent().isEnabled(),
this.enabled);
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
return super.getEnabled();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.cache")
public boolean isCache() {
return this.cache;
}
public void setCache(boolean cache) {
this.cache = cache;
}
public Strategy getStrategy() {
return this.strategy;
return super.isCache();
}
@DeprecatedConfigurationProperty(reason = "The appcache manifest feature is being removed from browsers.")
@Deprecated
public boolean isHtmlApplicationCache() {
return this.htmlApplicationCache;
}
public void setHtmlApplicationCache(boolean htmlApplicationCache) {
this.htmlApplicationCache = htmlApplicationCache;
this.customized = true;
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.compressed")
public boolean isCompressed() {
return this.compressed;
}
public void setCompressed(boolean compressed) {
this.compressed = compressed;
return super.isCompressed();
}
static Boolean getEnabled(boolean fixedEnabled, boolean contentEnabled, Boolean chainEnabled) {
return (fixedEnabled || contentEnabled) ? Boolean.TRUE : chainEnabled;
@Override
public org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy getStrategy() {
return this.strategy;
}
}
@ -173,17 +111,20 @@ public class ResourceProperties {
/**
* Strategies for extracting and embedding a resource version in its URL path.
*/
public static class Strategy {
@Deprecated
public static class Strategy extends Resources.Chain.Strategy {
private final Fixed fixed = new Fixed();
private final org.springframework.boot.autoconfigure.web.ResourceProperties.Fixed fixed = new org.springframework.boot.autoconfigure.web.ResourceProperties.Fixed();
private final Content content = new Content();
private final org.springframework.boot.autoconfigure.web.ResourceProperties.Content content = new org.springframework.boot.autoconfigure.web.ResourceProperties.Content();
public Fixed getFixed() {
@Override
public org.springframework.boot.autoconfigure.web.ResourceProperties.Fixed getFixed() {
return this.fixed;
}
public Content getContent() {
@Override
public org.springframework.boot.autoconfigure.web.ResourceProperties.Content getContent() {
return this.content;
}
@ -192,32 +133,19 @@ public class ResourceProperties {
/**
* Version Strategy based on content hashing.
*/
public static class Content {
/**
* Whether to enable the content Version Strategy.
*/
private boolean enabled;
/**
* Comma-separated list of patterns to apply to the content Version Strategy.
*/
private String[] paths = new String[] { "/**" };
@Deprecated
public static class Content extends Resources.Chain.Strategy.Content {
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.strategy.content.enabled")
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
return super.isEnabled();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.strategy.content.paths")
public String[] getPaths() {
return this.paths;
}
public void setPaths(String[] paths) {
this.paths = paths;
return super.getPaths();
}
}
@ -225,45 +153,25 @@ public class ResourceProperties {
/**
* Version Strategy based on a fixed version string.
*/
public static class Fixed {
/**
* Whether to enable the fixed Version Strategy.
*/
private boolean enabled;
/**
* Comma-separated list of patterns to apply to the fixed Version Strategy.
*/
private String[] paths = new String[] { "/**" };
/**
* Version string to use for the fixed Version Strategy.
*/
private String version;
@Deprecated
public static class Fixed extends Resources.Chain.Strategy.Fixed {
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.strategy.fixed.enabled")
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
return super.isEnabled();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.strategy.fixed.paths")
public String[] getPaths() {
return this.paths;
}
public void setPaths(String[] paths) {
this.paths = paths;
return super.getPaths();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.strategy.fixed.version")
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
return super.getVersion();
}
}
@ -271,30 +179,18 @@ public class ResourceProperties {
/**
* Cache configuration.
*/
public static class Cache {
/**
* Cache period for the resources served by the resource handler. If a duration
* suffix is not specified, seconds will be used. Can be overridden by the
* 'spring.resources.cache.cachecontrol' properties.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration period;
@Deprecated
public static class Cache extends Resources.Cache {
/**
* Cache control HTTP headers, only allows valid directive combinations. Overrides
* the 'spring.resources.cache.period' property.
*/
private final Cachecontrol cachecontrol = new Cachecontrol();
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.period")
public Duration getPeriod() {
return this.period;
}
public void setPeriod(Duration period) {
this.period = period;
return super.getPeriod();
}
@Override
public Cachecontrol getCachecontrol() {
return this.cachecontrol;
}
@ -302,196 +198,74 @@ public class ResourceProperties {
/**
* Cache Control HTTP header configuration.
*/
public static class Cachecontrol {
/**
* Maximum time the response should be cached, in seconds if no duration
* suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration maxAge;
/**
* Indicate that the cached response can be reused only if re-validated with
* the server.
*/
private Boolean noCache;
/**
* Indicate to not cache the response in any case.
*/
private Boolean noStore;
/**
* Indicate that once it has become stale, a cache must not use the response
* without re-validating it with the server.
*/
private Boolean mustRevalidate;
/**
* Indicate intermediaries (caches and others) that they should not transform
* the response content.
*/
private Boolean noTransform;
/**
* Indicate that any cache may store the response.
*/
private Boolean cachePublic;
/**
* Indicate that the response message is intended for a single user and must
* not be stored by a shared cache.
*/
private Boolean cachePrivate;
/**
* Same meaning as the "must-revalidate" directive, except that it does not
* apply to private caches.
*/
private Boolean proxyRevalidate;
/**
* Maximum time the response can be served after it becomes stale, in seconds
* if no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration staleWhileRevalidate;
/**
* Maximum time the response may be used when errors are encountered, in
* seconds if no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration staleIfError;
/**
* Maximum time the response should be cached by shared caches, in seconds if
* no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration sMaxAge;
@Deprecated
public static class Cachecontrol extends Resources.Cache.Cachecontrol {
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.max-age")
public Duration getMaxAge() {
return this.maxAge;
}
public void setMaxAge(Duration maxAge) {
this.maxAge = maxAge;
return super.getMaxAge();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.no-cache")
public Boolean getNoCache() {
return this.noCache;
}
public void setNoCache(Boolean noCache) {
this.noCache = noCache;
return super.getNoCache();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.no-store")
public Boolean getNoStore() {
return this.noStore;
}
public void setNoStore(Boolean noStore) {
this.noStore = noStore;
return super.getNoStore();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.must-revalidate")
public Boolean getMustRevalidate() {
return this.mustRevalidate;
}
public void setMustRevalidate(Boolean mustRevalidate) {
this.mustRevalidate = mustRevalidate;
return super.getMustRevalidate();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.no-transform")
public Boolean getNoTransform() {
return this.noTransform;
}
public void setNoTransform(Boolean noTransform) {
this.noTransform = noTransform;
return super.getNoTransform();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.cache-public")
public Boolean getCachePublic() {
return this.cachePublic;
}
public void setCachePublic(Boolean cachePublic) {
this.cachePublic = cachePublic;
return super.getCachePublic();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.cache-private")
public Boolean getCachePrivate() {
return this.cachePrivate;
}
public void setCachePrivate(Boolean cachePrivate) {
this.cachePrivate = cachePrivate;
return super.getCachePrivate();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.proxy-revalidate")
public Boolean getProxyRevalidate() {
return this.proxyRevalidate;
}
public void setProxyRevalidate(Boolean proxyRevalidate) {
this.proxyRevalidate = proxyRevalidate;
return super.getProxyRevalidate();
}
@Override
@DeprecatedConfigurationProperty(
replacement = "spring.web.resources.cache.cachecontrol.stale-while-revaliate")
public Duration getStaleWhileRevalidate() {
return this.staleWhileRevalidate;
}
public void setStaleWhileRevalidate(Duration staleWhileRevalidate) {
this.staleWhileRevalidate = staleWhileRevalidate;
return super.getStaleWhileRevalidate();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.stale-if-error")
public Duration getStaleIfError() {
return this.staleIfError;
}
public void setStaleIfError(Duration staleIfError) {
this.staleIfError = staleIfError;
return super.getStaleIfError();
}
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.s-max-age")
public Duration getSMaxAge() {
return this.sMaxAge;
}
public void setSMaxAge(Duration sMaxAge) {
this.sMaxAge = sMaxAge;
}
public CacheControl toHttpCacheControl() {
PropertyMapper map = PropertyMapper.get();
CacheControl control = createCacheControl();
map.from(this::getMustRevalidate).whenTrue().toCall(control::mustRevalidate);
map.from(this::getNoTransform).whenTrue().toCall(control::noTransform);
map.from(this::getCachePublic).whenTrue().toCall(control::cachePublic);
map.from(this::getCachePrivate).whenTrue().toCall(control::cachePrivate);
map.from(this::getProxyRevalidate).whenTrue().toCall(control::proxyRevalidate);
map.from(this::getStaleWhileRevalidate).whenNonNull()
.to((duration) -> control.staleWhileRevalidate(duration.getSeconds(), TimeUnit.SECONDS));
map.from(this::getStaleIfError).whenNonNull()
.to((duration) -> control.staleIfError(duration.getSeconds(), TimeUnit.SECONDS));
map.from(this::getSMaxAge).whenNonNull()
.to((duration) -> control.sMaxAge(duration.getSeconds(), TimeUnit.SECONDS));
// check if cacheControl remained untouched
if (control.getHeaderValue() == null) {
return null;
}
return control;
}
private CacheControl createCacheControl() {
if (Boolean.TRUE.equals(this.noStore)) {
return CacheControl.noStore();
}
if (Boolean.TRUE.equals(this.noCache)) {
return CacheControl.noCache();
}
if (this.maxAge != null) {
return CacheControl.maxAge(this.maxAge.getSeconds(), TimeUnit.SECONDS);
}
return CacheControl.empty();
return super.getSMaxAge();
}
}

@ -16,9 +16,15 @@
package org.springframework.boot.autoconfigure.web;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.http.CacheControl;
/**
* {@link ConfigurationProperties Configuration properties} for general web concerns.
@ -40,6 +46,8 @@ public class WebProperties {
*/
private LocaleResolver localeResolver = LocaleResolver.ACCEPT_HEADER;
private final Resources resources = new Resources();
public Locale getLocale() {
return this.locale;
}
@ -56,6 +64,10 @@ public class WebProperties {
this.localeResolver = localeResolver;
}
public Resources getResources() {
return this.resources;
}
public enum LocaleResolver {
/**
@ -71,4 +83,517 @@ public class WebProperties {
}
public static class Resources {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
/**
* Whether to enable default resource handling.
*/
private boolean addMappings = true;
private boolean customized = false;
private final Chain chain = new Chain();
private final Cache cache = new Cache();
public String[] getStaticLocations() {
return this.staticLocations;
}
public void setStaticLocations(String[] staticLocations) {
this.staticLocations = appendSlashIfNecessary(staticLocations);
this.customized = true;
}
private String[] appendSlashIfNecessary(String[] staticLocations) {
String[] normalized = new String[staticLocations.length];
for (int i = 0; i < staticLocations.length; i++) {
String location = staticLocations[i];
normalized[i] = location.endsWith("/") ? location : location + "/";
}
return normalized;
}
public boolean isAddMappings() {
return this.addMappings;
}
public void setAddMappings(boolean addMappings) {
this.customized = true;
this.addMappings = addMappings;
}
public Chain getChain() {
return this.chain;
}
public Cache getCache() {
return this.cache;
}
public boolean hasBeenCustomized() {
return this.customized || getChain().hasBeenCustomized() || getCache().hasBeenCustomized();
}
/**
* Configuration for the Spring Resource Handling chain.
*/
public static class Chain {
boolean customized = false;
/**
* Whether to enable the Spring Resource Handling chain. By default, disabled
* unless at least one strategy has been enabled.
*/
private Boolean enabled;
/**
* Whether to enable caching in the Resource chain.
*/
private boolean cache = true;
/**
* Whether to enable resolution of already compressed resources (gzip,
* brotli). Checks for a resource name with the '.gz' or '.br' file
* extensions.
*/
private boolean compressed = false;
private final Strategy strategy = new Strategy();
/**
* Return whether the resource chain is enabled. Return {@code null} if no
* specific settings are present.
* @return whether the resource chain is enabled or {@code null} if no
* specified settings are present.
*/
public Boolean getEnabled() {
return getEnabled(getStrategy().getFixed().isEnabled(), getStrategy().getContent().isEnabled(),
this.enabled);
}
private boolean hasBeenCustomized() {
return this.customized || getStrategy().hasBeenCustomized();
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
this.customized = true;
}
public boolean isCache() {
return this.cache;
}
public void setCache(boolean cache) {
this.cache = cache;
this.customized = true;
}
public Strategy getStrategy() {
return this.strategy;
}
public boolean isCompressed() {
return this.compressed;
}
public void setCompressed(boolean compressed) {
this.compressed = compressed;
this.customized = true;
}
static Boolean getEnabled(boolean fixedEnabled, boolean contentEnabled, Boolean chainEnabled) {
return (fixedEnabled || contentEnabled) ? Boolean.TRUE : chainEnabled;
}
/**
* Strategies for extracting and embedding a resource version in its URL path.
*/
public static class Strategy {
private final Fixed fixed = new Fixed();
private final Content content = new Content();
public Fixed getFixed() {
return this.fixed;
}
public Content getContent() {
return this.content;
}
private boolean hasBeenCustomized() {
return getFixed().hasBeenCustomized() || getContent().hasBeenCustomized();
}
/**
* Version Strategy based on content hashing.
*/
public static class Content {
private boolean customized = false;
/**
* Whether to enable the content Version Strategy.
*/
private boolean enabled;
/**
* Comma-separated list of patterns to apply to the content Version
* Strategy.
*/
private String[] paths = new String[] { "/**" };
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.customized = true;
this.enabled = enabled;
}
public String[] getPaths() {
return this.paths;
}
public void setPaths(String[] paths) {
this.customized = true;
this.paths = paths;
}
private boolean hasBeenCustomized() {
return this.customized;
}
}
/**
* Version Strategy based on a fixed version string.
*/
public static class Fixed {
private boolean customized = false;
/**
* Whether to enable the fixed Version Strategy.
*/
private boolean enabled;
/**
* Comma-separated list of patterns to apply to the fixed Version
* Strategy.
*/
private String[] paths = new String[] { "/**" };
/**
* Version string to use for the fixed Version Strategy.
*/
private String version;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.customized = true;
this.enabled = enabled;
}
public String[] getPaths() {
return this.paths;
}
public void setPaths(String[] paths) {
this.customized = true;
this.paths = paths;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.customized = true;
this.version = version;
}
private boolean hasBeenCustomized() {
return this.customized;
}
}
}
}
/**
* Cache configuration.
*/
public static class Cache {
private boolean customized = false;
/**
* Cache period for the resources served by the resource handler. If a
* duration suffix is not specified, seconds will be used. Can be overridden
* by the 'spring.web.resources.cache.cachecontrol' properties.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration period;
/**
* Cache control HTTP headers, only allows valid directive combinations.
* Overrides the 'spring.web.resources.cache.period' property.
*/
private final Cachecontrol cachecontrol = new Cachecontrol();
public Duration getPeriod() {
return this.period;
}
public void setPeriod(Duration period) {
this.customized = true;
this.period = period;
}
public Cachecontrol getCachecontrol() {
return this.cachecontrol;
}
private boolean hasBeenCustomized() {
return this.customized || getCachecontrol().hasBeenCustomized();
}
/**
* Cache Control HTTP header configuration.
*/
public static class Cachecontrol {
private boolean customized = false;
/**
* Maximum time the response should be cached, in seconds if no duration
* suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration maxAge;
/**
* Indicate that the cached response can be reused only if re-validated
* with the server.
*/
private Boolean noCache;
/**
* Indicate to not cache the response in any case.
*/
private Boolean noStore;
/**
* Indicate that once it has become stale, a cache must not use the
* response without re-validating it with the server.
*/
private Boolean mustRevalidate;
/**
* Indicate intermediaries (caches and others) that they should not
* transform the response content.
*/
private Boolean noTransform;
/**
* Indicate that any cache may store the response.
*/
private Boolean cachePublic;
/**
* Indicate that the response message is intended for a single user and
* must not be stored by a shared cache.
*/
private Boolean cachePrivate;
/**
* Same meaning as the "must-revalidate" directive, except that it does
* not apply to private caches.
*/
private Boolean proxyRevalidate;
/**
* Maximum time the response can be served after it becomes stale, in
* seconds if no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration staleWhileRevalidate;
/**
* Maximum time the response may be used when errors are encountered, in
* seconds if no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration staleIfError;
/**
* Maximum time the response should be cached by shared caches, in seconds
* if no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration sMaxAge;
public Duration getMaxAge() {
return this.maxAge;
}
public void setMaxAge(Duration maxAge) {
this.customized = true;
this.maxAge = maxAge;
}
public Boolean getNoCache() {
return this.noCache;
}
public void setNoCache(Boolean noCache) {
this.customized = true;
this.noCache = noCache;
}
public Boolean getNoStore() {
return this.noStore;
}
public void setNoStore(Boolean noStore) {
this.customized = true;
this.noStore = noStore;
}
public Boolean getMustRevalidate() {
return this.mustRevalidate;
}
public void setMustRevalidate(Boolean mustRevalidate) {
this.customized = true;
this.mustRevalidate = mustRevalidate;
}
public Boolean getNoTransform() {
return this.noTransform;
}
public void setNoTransform(Boolean noTransform) {
this.customized = true;
this.noTransform = noTransform;
}
public Boolean getCachePublic() {
return this.cachePublic;
}
public void setCachePublic(Boolean cachePublic) {
this.customized = true;
this.cachePublic = cachePublic;
}
public Boolean getCachePrivate() {
return this.cachePrivate;
}
public void setCachePrivate(Boolean cachePrivate) {
this.customized = true;
this.cachePrivate = cachePrivate;
}
public Boolean getProxyRevalidate() {
return this.proxyRevalidate;
}
public void setProxyRevalidate(Boolean proxyRevalidate) {
this.customized = true;
this.proxyRevalidate = proxyRevalidate;
}
public Duration getStaleWhileRevalidate() {
return this.staleWhileRevalidate;
}
public void setStaleWhileRevalidate(Duration staleWhileRevalidate) {
this.customized = true;
this.staleWhileRevalidate = staleWhileRevalidate;
}
public Duration getStaleIfError() {
return this.staleIfError;
}
public void setStaleIfError(Duration staleIfError) {
this.customized = true;
this.staleIfError = staleIfError;
}
public Duration getSMaxAge() {
return this.sMaxAge;
}
public void setSMaxAge(Duration sMaxAge) {
this.customized = true;
this.sMaxAge = sMaxAge;
}
public CacheControl toHttpCacheControl() {
PropertyMapper map = PropertyMapper.get();
CacheControl control = createCacheControl();
map.from(this::getMustRevalidate).whenTrue().toCall(control::mustRevalidate);
map.from(this::getNoTransform).whenTrue().toCall(control::noTransform);
map.from(this::getCachePublic).whenTrue().toCall(control::cachePublic);
map.from(this::getCachePrivate).whenTrue().toCall(control::cachePrivate);
map.from(this::getProxyRevalidate).whenTrue().toCall(control::proxyRevalidate);
map.from(this::getStaleWhileRevalidate).whenNonNull()
.to((duration) -> control.staleWhileRevalidate(duration.getSeconds(), TimeUnit.SECONDS));
map.from(this::getStaleIfError).whenNonNull()
.to((duration) -> control.staleIfError(duration.getSeconds(), TimeUnit.SECONDS));
map.from(this::getSMaxAge).whenNonNull()
.to((duration) -> control.sMaxAge(duration.getSeconds(), TimeUnit.SECONDS));
// check if cacheControl remained untouched
if (control.getHeaderValue() == null) {
return null;
}
return control;
}
private CacheControl createCacheControl() {
if (Boolean.TRUE.equals(this.noStore)) {
return CacheControl.noStore();
}
if (Boolean.TRUE.equals(this.noCache)) {
return CacheControl.noCache();
}
if (this.maxAge != null) {
return CacheControl.maxAge(this.maxAge.getSeconds(), TimeUnit.SECONDS);
}
return CacheControl.empty();
}
private boolean hasBeenCustomized() {
return this.customized;
}
}
}
}
}

@ -16,8 +16,7 @@
package org.springframework.boot.autoconfigure.web.reactive;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.web.reactive.config.ResourceChainRegistration;
import org.springframework.web.reactive.config.ResourceHandlerRegistration;
import org.springframework.web.reactive.resource.EncodedResourceResolver;
@ -32,30 +31,35 @@ import org.springframework.web.reactive.resource.VersionResourceResolver;
*/
class ResourceChainResourceHandlerRegistrationCustomizer implements ResourceHandlerRegistrationCustomizer {
@Autowired
private ResourceProperties resourceProperties = new ResourceProperties();
private final Resources resourceProperties;
ResourceChainResourceHandlerRegistrationCustomizer(Resources resources) {
this.resourceProperties = resources;
}
@Override
public void customize(ResourceHandlerRegistration registration) {
ResourceProperties.Chain properties = this.resourceProperties.getChain();
Resources.Chain properties = this.resourceProperties.getChain();
configureResourceChain(properties, registration.resourceChain(properties.isCache()));
}
@SuppressWarnings("deprecation")
private void configureResourceChain(ResourceProperties.Chain properties, ResourceChainRegistration chain) {
ResourceProperties.Strategy strategy = properties.getStrategy();
private void configureResourceChain(Resources.Chain properties, ResourceChainRegistration chain) {
Resources.Chain.Strategy strategy = properties.getStrategy();
if (properties.isCompressed()) {
chain.addResolver(new EncodedResourceResolver());
}
if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) {
chain.addResolver(getVersionResourceResolver(strategy));
}
if (properties.isHtmlApplicationCache()) {
if ((properties instanceof org.springframework.boot.autoconfigure.web.ResourceProperties.Chain)
&& ((org.springframework.boot.autoconfigure.web.ResourceProperties.Chain) properties)
.isHtmlApplicationCache()) {
chain.addTransformer(new org.springframework.web.reactive.resource.AppCacheManifestTransformer());
}
}
private ResourceResolver getVersionResourceResolver(ResourceProperties.Strategy properties) {
private ResourceResolver getVersionResourceResolver(Resources.Chain.Strategy properties) {
VersionResourceResolver resolver = new VersionResourceResolver();
if (properties.getFixed().isEnabled()) {
String version = properties.getFixed().getVersion();

@ -35,8 +35,8 @@ import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvi
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties.Format;
@ -106,11 +106,16 @@ public class WebFluxAutoConfiguration {
public static class WelcomePageConfiguration {
@Bean
@SuppressWarnings("deprecation")
public RouterFunctionMapping welcomePageRouterFunctionMapping(ApplicationContext applicationContext,
WebFluxProperties webFluxProperties, ResourceProperties resourceProperties) {
WebFluxProperties webFluxProperties,
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebProperties webProperties) {
String[] staticLocations = resourceProperties.hasBeenCustomized() ? resourceProperties.getStaticLocations()
: webProperties.getResources().getStaticLocations();
WelcomePageRouterFunctionFactory factory = new WelcomePageRouterFunctionFactory(
new TemplateAvailabilityProviders(applicationContext), applicationContext,
resourceProperties.getStaticLocations(), webFluxProperties.getStaticPathPattern());
new TemplateAvailabilityProviders(applicationContext), applicationContext, staticLocations,
webFluxProperties.getStaticPathPattern());
RouterFunction<ServerResponse> routerFunction = factory.createRouterFunction();
if (routerFunction != null) {
RouterFunctionMapping routerFunctionMapping = new RouterFunctionMapping(routerFunction);
@ -122,14 +127,16 @@ public class WebFluxAutoConfiguration {
}
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
@EnableConfigurationProperties({ org.springframework.boot.autoconfigure.web.ResourceProperties.class,
WebProperties.class, WebFluxProperties.class })
@Import({ EnableWebFluxConfiguration.class })
public static class WebFluxConfig implements WebFluxConfigurer {
private static final Log logger = LogFactory.getLog(WebFluxConfig.class);
private final ResourceProperties resourceProperties;
private final Resources resourceProperties;
private final WebFluxProperties webFluxProperties;
@ -143,12 +150,14 @@ public class WebFluxAutoConfiguration {
private final ObjectProvider<ViewResolver> viewResolvers;
public WebFluxConfig(ResourceProperties resourceProperties, WebFluxProperties webFluxProperties,
ListableBeanFactory beanFactory, ObjectProvider<HandlerMethodArgumentResolver> resolvers,
public WebFluxConfig(org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebProperties webProperties, WebFluxProperties webFluxProperties, ListableBeanFactory beanFactory,
ObjectProvider<HandlerMethodArgumentResolver> resolvers,
ObjectProvider<CodecCustomizer> codecCustomizers,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizer,
ObjectProvider<ViewResolver> viewResolvers) {
this.resourceProperties = resourceProperties;
this.resourceProperties = resourceProperties.hasBeenCustomized() ? resourceProperties
: webProperties.getResources();
this.webFluxProperties = webFluxProperties;
this.beanFactory = beanFactory;
this.argumentResolvers = resolvers;
@ -190,7 +199,8 @@ public class WebFluxAutoConfiguration {
private void configureResourceCaching(ResourceHandlerRegistration registration) {
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
ResourceProperties.Cache.Cachecontrol cacheControl = this.resourceProperties.getCache().getCachecontrol();
WebProperties.Resources.Cache.Cachecontrol cacheControl = this.resourceProperties.getCache()
.getCachecontrol();
if (cachePeriod != null && cacheControl.getMaxAge() == null) {
cacheControl.setMaxAge(cachePeriod);
}
@ -291,8 +301,13 @@ public class WebFluxAutoConfiguration {
static class ResourceChainCustomizerConfiguration {
@Bean
ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {
return new ResourceChainResourceHandlerRegistrationCustomizer();
@SuppressWarnings("deprecation")
ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer(
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebProperties webProperties) {
Resources resources = resourceProperties.hasBeenCustomized() ? resourceProperties
: webProperties.getResources();
return new ResourceChainResourceHandlerRegistrationCustomizer(resources);
}
}

@ -28,7 +28,7 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
@ -82,7 +82,7 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
private final ErrorAttributes errorAttributes;
private final ResourceProperties resourceProperties;
private final Resources resources;
private final TemplateAvailabilityProviders templateAvailabilityProviders;
@ -92,13 +92,35 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
private List<ViewResolver> viewResolvers = Collections.emptyList();
public AbstractErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
/**
* Create a new {@code AbstractErrorWebExceptionHandler}.
* @param errorAttributes the error attributes
* @param resourceProperties the resource properties
* @param applicationContext the application context
* @deprecated since 2.4.0 in favor of
* {@link #AbstractErrorWebExceptionHandler(ErrorAttributes, Resources, ApplicationContext)}
*/
@Deprecated
public AbstractErrorWebExceptionHandler(ErrorAttributes errorAttributes,
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
ApplicationContext applicationContext) {
this(errorAttributes, (Resources) resourceProperties, applicationContext);
}
/**
* Create a new {@code AbstractErrorWebExceptionHandler}.
* @param errorAttributes the error attributes
* @param resources the resources configuration properties
* @param applicationContext the application context
* @since 2.4.0
*/
public AbstractErrorWebExceptionHandler(ErrorAttributes errorAttributes, Resources resources,
ApplicationContext applicationContext) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
Assert.notNull(resourceProperties, "ResourceProperties must not be null");
Assert.notNull(resources, "Resources must not be null");
Assert.notNull(applicationContext, "ApplicationContext must not be null");
this.errorAttributes = errorAttributes;
this.resourceProperties = resourceProperties;
this.resources = resources;
this.applicationContext = applicationContext;
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
}
@ -224,7 +246,7 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
}
private Resource resolveResource(String viewName) {
for (String location : this.resourceProperties.getStaticLocations()) {
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");

@ -27,7 +27,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
@ -97,10 +97,27 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
* @param resourceProperties the resources configuration properties
* @param errorProperties the error configuration properties
* @param applicationContext the current application context
* @deprecated since 2.4.0 in favor of
* {@link #DefaultErrorWebExceptionHandler(ErrorAttributes, Resources, ErrorProperties, ApplicationContext)}
*/
public DefaultErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
@Deprecated
public DefaultErrorWebExceptionHandler(ErrorAttributes errorAttributes,
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, applicationContext);
this(errorAttributes, (Resources) resourceProperties, errorProperties, applicationContext);
}
/**
* Create a new {@code DefaultErrorWebExceptionHandler} instance.
* @param errorAttributes the error attributes
* @param resources the resources configuration properties
* @param errorProperties the error configuration properties
* @param applicationContext the current application context
* @since 2.4.0
*/
public DefaultErrorWebExceptionHandler(ErrorAttributes errorAttributes, Resources resources,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resources, applicationContext);
this.errorProperties = errorProperties;
}

@ -25,8 +25,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
@ -48,11 +48,13 @@ import org.springframework.web.reactive.result.view.ViewResolver;
* @author Scott Frederick
* @since 2.0.0
*/
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })
@EnableConfigurationProperties({ ServerProperties.class,
org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
public class ErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
@ -65,10 +67,12 @@ public class ErrorWebFluxAutoConfiguration {
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties, ObjectProvider<ViewResolver> viewResolvers,
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebProperties webProperties, ObjectProvider<ViewResolver> viewResolvers,
ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) {
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
resourceProperties, this.serverProperties.getError(), applicationContext);
resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources(),
this.serverProperties.getError(), applicationContext);
exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());

@ -33,7 +33,6 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
@ -50,9 +49,9 @@ import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvi
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources.Chain.Strategy;
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.Format;
@ -175,15 +174,17 @@ public class WebMvcAutoConfiguration {
// Defined as a nested config to ensure WebMvcConfigurer is not read when not
// on the classpath
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@EnableConfigurationProperties({ WebMvcProperties.class,
org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
private final ResourceProperties resourceProperties;
private final Resources resourceProperties;
private final WebMvcProperties mvcProperties;
@ -197,12 +198,15 @@ public class WebMvcAutoConfiguration {
final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
public WebMvcAutoConfigurationAdapter(
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = resourceProperties;
this.resourceProperties = resourceProperties.hasBeenCustomized() ? resourceProperties
: webProperties.getResources();
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
@ -234,7 +238,6 @@ public class WebMvcAutoConfiguration {
}
@Override
@SuppressWarnings("deprecation")
public void configurePathMatch(PathMatchConfigurer configurer) {
if (this.mvcProperties.getPathmatch()
.getMatchingStrategy() == WebMvcProperties.MatchingStrategy.PATH_PATTERN_PARSER) {
@ -259,7 +262,6 @@ public class WebMvcAutoConfiguration {
}
@Override
@SuppressWarnings("deprecation")
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties.getContentnegotiation();
configurer.favorPathExtension(contentnegotiation.isFavorPathExtension());
@ -363,7 +365,7 @@ public class WebMvcAutoConfiguration {
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
private final ResourceProperties resourceProperties;
private final Resources resourceProperties;
private final WebMvcProperties mvcProperties;
@ -375,10 +377,13 @@ public class WebMvcAutoConfiguration {
private ResourceLoader resourceLoader;
public EnableWebMvcConfiguration(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
WebProperties webProperties, ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
ListableBeanFactory beanFactory) {
this.resourceProperties = resourceProperties;
@SuppressWarnings("deprecation")
public EnableWebMvcConfiguration(
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebMvcProperties mvcProperties, WebProperties webProperties,
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
this.resourceProperties = resourceProperties.hasBeenCustomized() ? resourceProperties
: webProperties.getResources();
this.mvcProperties = mvcProperties;
this.webProperties = webProperties;
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
@ -551,8 +556,12 @@ public class WebMvcAutoConfiguration {
static class ResourceChainCustomizerConfiguration {
@Bean
ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {
return new ResourceChainResourceHandlerRegistrationCustomizer();
@SuppressWarnings("deprecation")
ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer(
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebProperties webProperties) {
return new ResourceChainResourceHandlerRegistrationCustomizer(
resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
}
}
@ -565,17 +574,20 @@ public class WebMvcAutoConfiguration {
static class ResourceChainResourceHandlerRegistrationCustomizer implements ResourceHandlerRegistrationCustomizer {
@Autowired
private ResourceProperties resourceProperties = new ResourceProperties();
private final Resources resourceProperties;
ResourceChainResourceHandlerRegistrationCustomizer(Resources resourceProperties) {
this.resourceProperties = resourceProperties;
}
@Override
public void customize(ResourceHandlerRegistration registration) {
ResourceProperties.Chain properties = this.resourceProperties.getChain();
Resources.Chain properties = this.resourceProperties.getChain();
configureResourceChain(properties, registration.resourceChain(properties.isCache()));
}
@SuppressWarnings("deprecation")
private void configureResourceChain(ResourceProperties.Chain properties, ResourceChainRegistration chain) {
private void configureResourceChain(Resources.Chain properties, ResourceChainRegistration chain) {
Strategy strategy = properties.getStrategy();
if (properties.isCompressed()) {
chain.addResolver(new EncodedResourceResolver());
@ -583,12 +595,14 @@ public class WebMvcAutoConfiguration {
if (strategy.getFixed().isEnabled() || strategy.getContent().isEnabled()) {
chain.addResolver(getVersionResourceResolver(strategy));
}
if (properties.isHtmlApplicationCache()) {
if (properties instanceof org.springframework.boot.autoconfigure.web.ResourceProperties.Chain
&& ((org.springframework.boot.autoconfigure.web.ResourceProperties.Chain) properties)
.isHtmlApplicationCache()) {
chain.addTransformer(new org.springframework.web.servlet.resource.AppCacheManifestTransformer());
}
}
private ResourceResolver getVersionResourceResolver(ResourceProperties.Strategy properties) {
private ResourceResolver getVersionResourceResolver(Strategy properties) {
VersionResourceResolver resolver = new VersionResourceResolver();
if (properties.getFixed().isEnabled()) {
String version = properties.getFixed().getVersion();

@ -25,7 +25,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
@ -68,7 +68,7 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final Resources resources;
private final TemplateAvailabilityProviders templateAvailabilityProviders;
@ -78,21 +78,35 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
* Create a new {@link DefaultErrorViewResolver} instance.
* @param applicationContext the source application context
* @param resourceProperties resource properties
* @deprecated since 2.4.0 in favour of
* {@link #DefaultErrorViewResolver(ApplicationContext, Resources)}
*/
public DefaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
@Deprecated
public DefaultErrorViewResolver(ApplicationContext applicationContext,
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties) {
this(applicationContext, (Resources) resourceProperties);
}
/**
* Create a new {@link DefaultErrorViewResolver} instance.
* @param applicationContext the source application context
* @param resources resource properties
* @since 2.4.0
*/
public DefaultErrorViewResolver(ApplicationContext applicationContext, Resources resources) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
Assert.notNull(resourceProperties, "ResourceProperties must not be null");
Assert.notNull(resources, "Resources must not be null");
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.resources = resources;
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
}
DefaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties,
DefaultErrorViewResolver(ApplicationContext applicationContext, Resources resourceProperties,
TemplateAvailabilityProviders templateAvailabilityProviders) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
Assert.notNull(resourceProperties, "ResourceProperties must not be null");
Assert.notNull(resourceProperties, "Resources must not be null");
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.resources = resourceProperties;
this.templateAvailabilityProviders = templateAvailabilityProviders;
}
@ -116,7 +130,7 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resourceProperties.getStaticLocations()) {
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");

@ -46,8 +46,9 @@ import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
@ -88,7 +89,7 @@ import org.springframework.web.util.HtmlUtils;
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class, WebMvcProperties.class })
@EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {
private final ServerProperties serverProperties;
@ -121,24 +122,29 @@ public class ErrorMvcAutoConfiguration {
return new PreserveErrorControllerTargetClassPostProcessor();
}
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ org.springframework.boot.autoconfigure.web.ResourceProperties.class,
WebProperties.class, WebMvcProperties.class })
static class DefaultErrorViewResolverConfiguration {
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final Resources resources;
DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
ResourceProperties resourceProperties) {
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebProperties webProperties) {
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.resources = webProperties.getResources().hasBeenCustomized() ? webProperties.getResources()
: resourceProperties;
}
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
}

@ -18,6 +18,8 @@ package org.springframework.boot.autoconfigure.web;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -46,27 +48,31 @@ class ConditionalOnEnabledResourceChainTests {
assertThat(this.context.containsBean("foo")).isFalse();
}
@Test
void disabledExplicitly() {
load("spring.resources.chain.enabled:false");
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void disabledExplicitly(String prefix) {
load(prefix + "chain.enabled:false");
assertThat(this.context.containsBean("foo")).isFalse();
}
@Test
void enabledViaMainEnabledFlag() {
load("spring.resources.chain.enabled:true");
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void enabledViaMainEnabledFlag(String prefix) {
load(prefix + "chain.enabled:true");
assertThat(this.context.containsBean("foo")).isTrue();
}
@Test
void enabledViaFixedStrategyFlag() {
load("spring.resources.chain.strategy.fixed.enabled:true");
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void enabledViaFixedStrategyFlag(String prefix) {
load(prefix + "chain.strategy.fixed.enabled:true");
assertThat(this.context.containsBean("foo")).isTrue();
}
@Test
void enabledViaContentStrategyFlag() {
load("spring.resources.chain.strategy.content.enabled:true");
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void enabledViaContentStrategyFlag(String prefix) {
load(prefix + "chain.strategy.content.enabled:true");
assertThat(this.context.containsBean("foo")).isTrue();
}

@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Stephane Nicoll
*/
@Deprecated
class ResourcePropertiesBindingTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
@ -60,6 +61,7 @@ class ResourcePropertiesBindingTests {
};
}
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ResourceProperties.class)
static class TestConfiguration {

@ -20,7 +20,6 @@ import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.ResourceProperties.Cache;
import org.springframework.http.CacheControl;
import static org.assertj.core.api.Assertions.assertThat;
@ -31,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Stephane Nicoll
* @author Kristine Jetzke
*/
@Deprecated
class ResourcePropertiesTests {
private final ResourceProperties properties = new ResourceProperties();
@ -78,7 +78,7 @@ class ResourcePropertiesTests {
@Test
void cacheControlAllPropertiesSet() {
Cache.Cachecontrol properties = this.properties.getCache().getCachecontrol();
ResourceProperties.Cache.Cachecontrol properties = this.properties.getCache().getCachecontrol();
properties.setMaxAge(Duration.ofSeconds(4));
properties.setCachePrivate(true);
properties.setCachePublic(true);
@ -96,7 +96,7 @@ class ResourcePropertiesTests {
@Test
void invalidCacheControlCombination() {
Cache.Cachecontrol properties = this.properties.getCache().getCachecontrol();
ResourceProperties.Cache.Cachecontrol properties = this.properties.getCache().getCachecontrol();
properties.setMaxAge(Duration.ofSeconds(4));
properties.setNoStore(true);
CacheControl cacheControl = properties.toHttpCacheControl();

@ -0,0 +1,69 @@
/*
* Copyright 2012-2020 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Binding tests for {@link WebProperties.Resources}.
*
* @author Stephane Nicoll
*/
class WebPropertiesResourcesBindingTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(TestConfiguration.class);
@Test
void staticLocationsExpandArray() {
this.contextRunner
.withPropertyValues("spring.web.resources.static-locations[0]=classpath:/one/",
"spring.web.resources.static-locations[1]=classpath:/two",
"spring.web.resources.static-locations[2]=classpath:/three/",
"spring.web.resources.static-locations[3]=classpath:/four",
"spring.web.resources.static-locations[4]=classpath:/five/",
"spring.web.resources.static-locations[5]=classpath:/six")
.run(assertResourceProperties((properties) -> assertThat(properties.getStaticLocations()).contains(
"classpath:/one/", "classpath:/two/", "classpath:/three/", "classpath:/four/",
"classpath:/five/", "classpath:/six/")));
}
private ContextConsumer<AssertableApplicationContext> assertResourceProperties(Consumer<Resources> consumer) {
return (context) -> {
assertThat(context).hasSingleBean(WebProperties.class);
consumer.accept(context.getBean(WebProperties.class).getResources());
};
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
static class TestConfiguration {
}
}

@ -0,0 +1,108 @@
/*
* Copyright 2012-2020 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources.Cache;
import org.springframework.http.CacheControl;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link WebProperties.Resources}.
*
* @author Stephane Nicoll
* @author Kristine Jetzke
*/
@Deprecated
class WebPropertiesResourcesTests {
private final Resources properties = new WebProperties().getResources();
@Test
void resourceChainNoCustomization() {
assertThat(this.properties.getChain().getEnabled()).isNull();
}
@Test
void resourceChainStrategyEnabled() {
this.properties.getChain().getStrategy().getFixed().setEnabled(true);
assertThat(this.properties.getChain().getEnabled()).isTrue();
}
@Test
void resourceChainEnabled() {
this.properties.getChain().setEnabled(true);
assertThat(this.properties.getChain().getEnabled()).isTrue();
}
@Test
void resourceChainDisabled() {
this.properties.getChain().setEnabled(false);
assertThat(this.properties.getChain().getEnabled()).isFalse();
}
@Test
void defaultStaticLocationsAllEndWithTrailingSlash() {
assertThat(this.properties.getStaticLocations()).allMatch((location) -> location.endsWith("/"));
}
@Test
void customStaticLocationsAreNormalizedToEndWithTrailingSlash() {
this.properties.setStaticLocations(new String[] { "/foo", "/bar", "/baz/" });
String[] actual = this.properties.getStaticLocations();
assertThat(actual).containsExactly("/foo/", "/bar/", "/baz/");
}
@Test
void emptyCacheControl() {
CacheControl cacheControl = this.properties.getCache().getCachecontrol().toHttpCacheControl();
assertThat(cacheControl).isNull();
}
@Test
void cacheControlAllPropertiesSet() {
Cache.Cachecontrol properties = this.properties.getCache().getCachecontrol();
properties.setMaxAge(Duration.ofSeconds(4));
properties.setCachePrivate(true);
properties.setCachePublic(true);
properties.setMustRevalidate(true);
properties.setNoTransform(true);
properties.setProxyRevalidate(true);
properties.setSMaxAge(Duration.ofSeconds(5));
properties.setStaleIfError(Duration.ofSeconds(6));
properties.setStaleWhileRevalidate(Duration.ofSeconds(7));
CacheControl cacheControl = properties.toHttpCacheControl();
assertThat(cacheControl.getHeaderValue())
.isEqualTo("max-age=4, must-revalidate, no-transform, public, private, proxy-revalidate,"
+ " s-maxage=5, stale-if-error=6, stale-while-revalidate=7");
}
@Test
void invalidCacheControlCombination() {
Cache.Cachecontrol properties = this.properties.getCache().getCachecontrol();
properties.setMaxAge(Duration.ofSeconds(4));
properties.setNoStore(true);
CacheControl cacheControl = properties.toHttpCacheControl();
assertThat(cacheControl.getHeaderValue()).isEqualTo("no-store");
}
}

@ -33,6 +33,8 @@ import javax.validation.ValidatorFactory;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
@ -167,16 +169,18 @@ class WebFluxAutoConfigurationTests {
});
}
@Test
void shouldNotMapResourcesWhenDisabled() {
this.contextRunner.withPropertyValues("spring.resources.add-mappings:false")
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void shouldNotMapResourcesWhenDisabled(String prefix) {
this.contextRunner.withPropertyValues(prefix + ".add-mappings:false")
.run((context) -> assertThat(context.getBean("resourceHandlerMapping"))
.isNotInstanceOf(SimpleUrlHandlerMapping.class));
}
@Test
void resourceHandlerChainEnabled() {
this.contextRunner.withPropertyValues("spring.resources.chain.enabled:true").run((context) -> {
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void resourceHandlerChainEnabled(String prefix) {
this.contextRunner.withPropertyValues(prefix + "chain.enabled:true").run((context) -> {
SimpleUrlHandlerMapping hm = context.getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class);
assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class);
ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**");
@ -406,10 +410,11 @@ class WebFluxAutoConfigurationTests {
});
}
@Test
void cachePeriod() {
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void cachePeriod(String prefix) {
Assertions.setExtractBareNamePropertyMethods(false);
this.contextRunner.withPropertyValues("spring.resources.cache.period:5").run((context) -> {
this.contextRunner.withPropertyValues(prefix + "cache.period:5").run((context) -> {
Map<PathPattern, Object> handlerMap = getHandlerMap(context);
assertThat(handlerMap).hasSize(2);
for (Object handler : handlerMap.values()) {
@ -422,11 +427,12 @@ class WebFluxAutoConfigurationTests {
Assertions.setExtractBareNamePropertyMethods(true);
}
@Test
void cacheControl() {
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void cacheControl(String prefix) {
Assertions.setExtractBareNamePropertyMethods(false);
this.contextRunner.withPropertyValues("spring.resources.cache.cachecontrol.max-age:5",
"spring.resources.cache.cachecontrol.proxy-revalidate:true").run((context) -> {
this.contextRunner.withPropertyValues(prefix + "cache.cachecontrol.max-age:5",
prefix + "cache.cachecontrol.proxy-revalidate:true").run((context) -> {
Map<PathPattern, Object> handlerMap = getHandlerMap(context);
assertThat(handlerMap).hasSize(2);
for (Object handler : handlerMap.values()) {
@ -449,14 +455,14 @@ class WebFluxAutoConfigurationTests {
});
}
@Test
void welcomePageHandlerMapping() {
this.contextRunner.withPropertyValues("spring.resources.static-locations=classpath:/welcome-page/")
.run((context) -> {
assertThat(context).getBeans(RouterFunctionMapping.class).hasSize(2);
assertThat(context.getBean("welcomePageRouterFunctionMapping", HandlerMapping.class)).isNotNull()
.extracting("order").isEqualTo(1);
});
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void welcomePageHandlerMapping(String prefix) {
this.contextRunner.withPropertyValues(prefix + "static-locations=classpath:/welcome-page/").run((context) -> {
assertThat(context).getBeans(RouterFunctionMapping.class).hasSize(2);
assertThat(context.getBean("welcomePageRouterFunctionMapping", HandlerMapping.class)).isNotNull()
.extracting("order").isEqualTo(1);
});
}
@Test

@ -23,7 +23,7 @@ import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
@ -62,7 +62,7 @@ class DefaultErrorWebExceptionHandlerTests {
@Test
void nonStandardErrorStatusCodeShouldNotFail() {
ErrorAttributes errorAttributes = mock(ErrorAttributes.class);
ResourceProperties resourceProperties = new ResourceProperties();
Resources resourceProperties = new Resources();
ErrorProperties errorProperties = new ErrorProperties();
ApplicationContext context = new AnnotationConfigReactiveWebApplicationContext();
given(errorAttributes.getErrorAttributes(any(), any())).willReturn(getErrorAttributes());

@ -204,15 +204,17 @@ class WebMvcAutoConfigurationTests {
});
}
@Test
void resourceHandlerMappingDisabled() {
this.contextRunner.withPropertyValues("spring.resources.add-mappings:false")
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void resourceHandlerMappingDisabled(String prefix) {
this.contextRunner.withPropertyValues(prefix + "add-mappings:false")
.run((context) -> assertThat(getResourceMappingLocations(context)).hasSize(0));
}
@Test
void resourceHandlerChainEnabled() {
this.contextRunner.withPropertyValues("spring.resources.chain.enabled:true").run((context) -> {
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void resourceHandlerChainEnabled(String prefix) {
this.contextRunner.withPropertyValues(prefix + "chain.enabled:true").run((context) -> {
assertThat(getResourceResolvers(context, "/webjars/**")).hasSize(2);
assertThat(getResourceTransformers(context, "/webjars/**")).hasSize(1);
assertThat(getResourceResolvers(context, "/**")).extractingResultOf("getClass")
@ -222,11 +224,13 @@ class WebMvcAutoConfigurationTests {
});
}
@Test
void resourceHandlerFixedStrategyEnabled() {
this.contextRunner.withPropertyValues("spring.resources.chain.strategy.fixed.enabled:true",
"spring.resources.chain.strategy.fixed.version:test",
"spring.resources.chain.strategy.fixed.paths:/**/*.js").run((context) -> {
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void resourceHandlerFixedStrategyEnabled(String prefix) {
this.contextRunner
.withPropertyValues(prefix + "chain.strategy.fixed.enabled:true",
prefix + "chain.strategy.fixed.version:test", prefix + "chain.strategy.fixed.paths:/**/*.js")
.run((context) -> {
assertThat(getResourceResolvers(context, "/webjars/**")).hasSize(3);
assertThat(getResourceTransformers(context, "/webjars/**")).hasSize(2);
assertThat(getResourceResolvers(context, "/**")).extractingResultOf("getClass").containsOnly(
@ -239,10 +243,11 @@ class WebMvcAutoConfigurationTests {
});
}
@Test
void resourceHandlerContentStrategyEnabled() {
this.contextRunner.withPropertyValues("spring.resources.chain.strategy.content.enabled:true",
"spring.resources.chain.strategy.content.paths:/**,/*.png").run((context) -> {
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void resourceHandlerContentStrategyEnabled(String prefix) {
this.contextRunner.withPropertyValues(prefix + "chain.strategy.content.enabled:true",
prefix + "chain.strategy.content.paths:/**,/*.png").run((context) -> {
assertThat(getResourceResolvers(context, "/webjars/**")).hasSize(3);
assertThat(getResourceTransformers(context, "/webjars/**")).hasSize(2);
assertThat(getResourceResolvers(context, "/**")).extractingResultOf("getClass").containsOnly(
@ -255,25 +260,25 @@ class WebMvcAutoConfigurationTests {
});
}
@Test
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
@SuppressWarnings("deprecation")
void resourceHandlerChainCustomized() {
this.contextRunner
.withPropertyValues("spring.resources.chain.enabled:true", "spring.resources.chain.cache:false",
"spring.resources.chain.strategy.content.enabled:true",
"spring.resources.chain.strategy.content.paths:/**,/*.png",
"spring.resources.chain.strategy.fixed.enabled:true",
"spring.resources.chain.strategy.fixed.version:test",
"spring.resources.chain.strategy.fixed.paths:/**/*.js",
"spring.resources.chain.html-application-cache:true", "spring.resources.chain.compressed:true")
.run((context) -> {
void resourceHandlerChainCustomized(String prefix) {
this.contextRunner.withPropertyValues(prefix + "chain.enabled:true", prefix + "chain.cache:false",
prefix + "chain.strategy.content.enabled:true", prefix + "chain.strategy.content.paths:/**,/*.png",
prefix + "chain.strategy.fixed.enabled:true", prefix + "chain.strategy.fixed.version:test",
prefix + "chain.strategy.fixed.paths:/**/*.js", prefix + "chain.html-application-cache:true",
prefix + "chain.compressed:true").run((context) -> {
assertThat(getResourceResolvers(context, "/webjars/**")).hasSize(3);
assertThat(getResourceTransformers(context, "/webjars/**")).hasSize(2);
assertThat(getResourceTransformers(context, "/webjars/**"))
.hasSize(prefix.equals("spring.resources.") ? 2 : 1);
assertThat(getResourceResolvers(context, "/**")).extractingResultOf("getClass").containsOnly(
EncodedResourceResolver.class, VersionResourceResolver.class, PathResourceResolver.class);
assertThat(getResourceTransformers(context, "/**")).extractingResultOf("getClass").containsOnly(
CssLinkResourceTransformer.class,
org.springframework.web.servlet.resource.AppCacheManifestTransformer.class);
assertThat(getResourceTransformers(context, "/**")).extractingResultOf("getClass")
.containsOnly(prefix.equals("spring.resources.")
? new Class<?>[] { CssLinkResourceTransformer.class,
org.springframework.web.servlet.resource.AppCacheManifestTransformer.class }
: new Class<?>[] { CssLinkResourceTransformer.class });
VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers(context, "/**")
.get(1);
Map<String, VersionStrategy> strategyMap = resolver.getStrategyMap();
@ -597,19 +602,20 @@ class WebMvcAutoConfigurationTests {
};
}
@Test
void welcomePageHandlerMappingIsAutoConfigured() {
this.contextRunner.withPropertyValues("spring.resources.static-locations:classpath:/welcome-page/")
.run((context) -> {
assertThat(context).hasSingleBean(WelcomePageHandlerMapping.class);
WelcomePageHandlerMapping bean = context.getBean(WelcomePageHandlerMapping.class);
assertThat(bean.getRootHandler()).isNotNull();
});
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void welcomePageHandlerMappingIsAutoConfigured(String prefix) {
this.contextRunner.withPropertyValues(prefix + "static-locations:classpath:/welcome-page/").run((context) -> {
assertThat(context).hasSingleBean(WelcomePageHandlerMapping.class);
WelcomePageHandlerMapping bean = context.getBean(WelcomePageHandlerMapping.class);
assertThat(bean.getRootHandler()).isNotNull();
});
}
@Test
void welcomePageHandlerIncludesCorsConfiguration() {
this.contextRunner.withPropertyValues("spring.resources.static-locations:classpath:/welcome-page/")
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void welcomePageHandlerIncludesCorsConfiguration(String prefix) {
this.contextRunner.withPropertyValues(prefix + "static-locations:classpath:/welcome-page/")
.withUserConfiguration(CorsConfigurer.class).run((context) -> {
WelcomePageHandlerMapping bean = context.getBean(WelcomePageHandlerMapping.class);
UrlBasedCorsConfigurationSource source = (UrlBasedCorsConfigurationSource) ReflectionTestUtils
@ -732,9 +738,10 @@ class WebMvcAutoConfigurationTests {
.run((context) -> assertThat(context).hasNotFailed());
}
@Test
void cachePeriod() {
this.contextRunner.withPropertyValues("spring.resources.cache.period:5").run(this::assertCachePeriod);
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void cachePeriod(String prefix) {
this.contextRunner.withPropertyValues(prefix + "cache.period:5").run(this::assertCachePeriod);
}
private void assertCachePeriod(AssertableWebApplicationContext context) {
@ -749,10 +756,11 @@ class WebMvcAutoConfigurationTests {
}
}
@Test
void cacheControl() {
this.contextRunner.withPropertyValues("spring.resources.cache.cachecontrol.max-age:5",
"spring.resources.cache.cachecontrol.proxy-revalidate:true").run(this::assertCacheControl);
@ParameterizedTest
@ValueSource(strings = { "spring.resources.", "spring.web.resources." })
void cacheControl(String prefix) {
this.contextRunner.withPropertyValues(prefix + "cache.cachecontrol.max-age:5",
prefix + "cache.cachecontrol.proxy-revalidate:true").run(this::assertCacheControl);
}
@Test

@ -30,7 +30,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.Ordered;
@ -65,7 +65,7 @@ class DefaultErrorViewResolverTests {
@Mock
private TemplateAvailabilityProvider templateAvailabilityProvider;
private ResourceProperties resourceProperties;
private Resources resourcesProperties;
private Map<String, Object> model = new HashMap<>();
@ -75,25 +75,24 @@ class DefaultErrorViewResolverTests {
void setup() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.refresh();
this.resourceProperties = new ResourceProperties();
this.resourcesProperties = new Resources();
TemplateAvailabilityProviders templateAvailabilityProviders = new TestTemplateAvailabilityProviders(
this.templateAvailabilityProvider);
this.resolver = new DefaultErrorViewResolver(applicationContext, this.resourceProperties,
this.resolver = new DefaultErrorViewResolver(applicationContext, this.resourcesProperties,
templateAvailabilityProviders);
}
@Test
void createWhenApplicationContextIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new DefaultErrorViewResolver(null, new ResourceProperties()))
assertThatIllegalArgumentException().isThrownBy(() -> new DefaultErrorViewResolver(null, new Resources()))
.withMessageContaining("ApplicationContext must not be null");
}
@Test
void createWhenResourcePropertiesIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new DefaultErrorViewResolver(mock(ApplicationContext.class), null))
.withMessageContaining("ResourceProperties must not be null");
.isThrownBy(() -> new DefaultErrorViewResolver(mock(ApplicationContext.class), (Resources) null))
.withMessageContaining("Resources must not be null");
}
@Test
@ -197,7 +196,7 @@ class DefaultErrorViewResolverTests {
private void setResourceLocation(String path) {
String packageName = getClass().getPackage().getName();
this.resourceProperties
this.resourcesProperties
.setStaticLocations(new String[] { "classpath:" + packageName.replace('.', '/') + path + "/" });
}

@ -23,6 +23,8 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.devtools.logger.DevToolsLogFactory;
import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.boot.devtools.system.DevToolsEnablementDeducer;
@ -85,7 +87,9 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
if (canAddProperties(environment)) {
logger.info(LogMessage.format("Devtools property defaults active! Set '%s' to 'false' to disable",
ENABLED));
environment.getPropertySources().addLast(new MapPropertySource("devtools", PROPERTIES));
Map<String, Object> properties = new HashMap<>(PROPERTIES);
properties.putAll(getResourceProperties(environment));
environment.getPropertySources().addLast(new MapPropertySource("devtools", properties));
}
if (isWebApplication(environment) && !environment.containsProperty(WEB_LOGGING)) {
logger.info(LogMessage.format(
@ -95,6 +99,28 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
}
}
private Map<String, String> getResourceProperties(Environment environment) {
Map<String, String> resourceProperties = new HashMap<>();
String prefix = determineResourcePropertiesPrefix(environment);
resourceProperties.put(prefix + "cache.period", "0");
resourceProperties.put(prefix + "chain.cache", "false");
System.out.println(resourceProperties);
return resourceProperties;
}
@SuppressWarnings("deprecation")
private String determineResourcePropertiesPrefix(Environment environment) {
if (ClassUtils.isPresent("org.springframework.boot.autoconfigure.web.ResourceProperties",
getClass().getClassLoader())) {
BindResult<org.springframework.boot.autoconfigure.web.ResourceProperties> result = Binder.get(environment)
.bind("spring.resources", org.springframework.boot.autoconfigure.web.ResourceProperties.class);
if (result.isBound() && result.get().hasBeenCustomized()) {
return "spring.resources.";
}
}
return "spring.web.resources.";
}
private boolean isLocalApplication(ConfigurableEnvironment environment) {
return environment.getPropertySources().get("remoteUrl") == null;
}

@ -35,7 +35,8 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher;
@ -113,7 +114,17 @@ class LocalDevToolsAutoConfigurationTests {
@Test
void resourceCachePeriodIsZero() throws Exception {
this.context = getContext(() -> initializeAndRun(WebResourcesConfig.class));
ResourceProperties properties = this.context.getBean(ResourceProperties.class);
Resources properties = this.context.getBean(WebProperties.class).getResources();
assertThat(properties.getCache().getPeriod()).isZero();
}
@SuppressWarnings("deprecation")
@Test
void deprecatedResourceCachePeriodIsZeroWhenDeprecatedResourcePropertiesAreInUse() throws Exception {
this.context = getContext(() -> initializeAndRun(WebResourcesConfig.class,
Collections.singletonMap("spring.resources.add-mappings", false)));
Resources properties = this.context
.getBean(org.springframework.boot.autoconfigure.web.ResourceProperties.class);
assertThat(properties.getCache().getPeriod()).isZero();
}
@ -282,9 +293,10 @@ class LocalDevToolsAutoConfigurationTests {
}
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@Import({ ServletWebServerFactoryAutoConfiguration.class, LocalDevToolsAutoConfiguration.class,
ResourceProperties.class })
@Import({ ServletWebServerFactoryAutoConfiguration.class, LocalDevToolsAutoConfiguration.class, WebProperties.class,
org.springframework.boot.autoconfigure.web.ResourceProperties.class })
static class WebResourcesConfig {
}

@ -2608,7 +2608,7 @@ For instance, relocating all resources to `/resources/**` can be achieved as fol
static-path-pattern: "/resources/**"
----
You can also customize the static resource locations by using the configprop:spring.resources.static-locations[] property (replacing the default values with a list of directory locations).
You can also customize the static resource locations by using the configprop:spring.web.resources.static-locations[] property (replacing the default values with a list of directory locations).
The root Servlet context path, `"/"`, is automatically added as a location as well.
In addition to the "`standard`" static resource locations mentioned earlier, a special case is made for https://www.webjars.org/[Webjars content].
@ -2631,12 +2631,13 @@ To use cache busting, the following configuration configures a cache busting sol
[source,yaml,indent=0,subs="verbatim,quotes,attributes",configprops,configblocks]
----
spring:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
----
NOTE: Links to resources are rewritten in templates at runtime, thanks to a `ResourceUrlEncodingFilter` that is auto-configured for Thymeleaf and FreeMarker.
@ -2650,16 +2651,17 @@ A "fixed" strategy adds a static version string in the URL without changing the
[source,yaml,indent=0,subs="verbatim,quotes,attributes",configprops,configblocks]
----
spring:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
fixed:
enabled: true
paths: "/js/lib/"
version: "v12"
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
fixed:
enabled: true
paths: "/js/lib/"
version: "v12"
----
With this configuration, JavaScript modules located under `"/js/lib/"` use a fixed versioning strategy (`"/v12/js/lib/mymodule.js"`), while other resources still use the content one (`<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>`).
@ -3134,7 +3136,7 @@ For instance, relocating all resources to `/resources/**` can be achieved as fol
static-path-pattern: "/resources/**"
----
You can also customize the static resource locations by using `spring.resources.static-locations`.
You can also customize the static resource locations by using `spring.web.resources.static-locations`.
Doing so replaces the default values with a list of directory locations.
If you do so, the default welcome page detection switches to your custom locations.
So, if there is an `index.html` in any of your locations on startup, it is the home page of the application.

Loading…
Cancel
Save