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

Closes gh-23917
This commit is contained in:
Andy Wilkinson 2020-10-29 12:46:24 +00:00
parent 929d8fcd3c
commit c22e655848
23 changed files with 1168 additions and 519 deletions

View File

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

View File

@ -17,14 +17,10 @@
package org.springframework.boot.autoconfigure.web; package org.springframework.boot.autoconfigure.web;
import java.time.Duration; 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.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; 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. * Properties used to configure resource handling.
@ -35,137 +31,79 @@ import org.springframework.http.CacheControl;
* @author Venil Noronha * @author Venil Noronha
* @author Kristine Jetzke * @author Kristine Jetzke
* @since 1.1.0 * @since 1.1.0
* @deprecated since 2.4.0 in favor of {@link WebProperties.Resources}
*/ */
@Deprecated
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false) @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties { public class ResourceProperties extends 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 final Chain chain = new Chain(); private final Chain chain = new Chain();
private final Cache cache = new Cache(); private final Cache cache = new Cache();
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.static-locations")
public String[] getStaticLocations() { public String[] getStaticLocations() {
return this.staticLocations; return super.getStaticLocations();
}
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;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.add-mappings")
public boolean isAddMappings() { public boolean isAddMappings() {
return this.addMappings; return super.isAddMappings();
}
public void setAddMappings(boolean addMappings) {
this.addMappings = addMappings;
} }
@Override
public Chain getChain() { public Chain getChain() {
return this.chain; return this.chain;
} }
@Override
public Cache getCache() { public Cache getCache() {
return this.cache; return this.cache;
} }
/** @Deprecated
* Configuration for the Spring Resource Handling chain. public static class Chain extends Resources.Chain {
*/
public static class Chain {
/** private final org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy strategy = new org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy();
* 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 HTML5 application cache manifest rewriting. * Whether to enable HTML5 application cache manifest rewriting.
*/ */
private boolean htmlApplicationCache = false; private boolean htmlApplicationCache = false;
/** @Override
* Whether to enable resolution of already compressed resources (gzip, brotli). @DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.enabled")
* 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() { public Boolean getEnabled() {
return getEnabled(getStrategy().getFixed().isEnabled(), getStrategy().getContent().isEnabled(), return super.getEnabled();
this.enabled);
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.cache")
public boolean isCache() { public boolean isCache() {
return this.cache; return super.isCache();
}
public void setCache(boolean cache) {
this.cache = cache;
}
public Strategy getStrategy() {
return this.strategy;
} }
@DeprecatedConfigurationProperty(reason = "The appcache manifest feature is being removed from browsers.") @DeprecatedConfigurationProperty(reason = "The appcache manifest feature is being removed from browsers.")
@Deprecated
public boolean isHtmlApplicationCache() { public boolean isHtmlApplicationCache() {
return this.htmlApplicationCache; return this.htmlApplicationCache;
} }
public void setHtmlApplicationCache(boolean htmlApplicationCache) { public void setHtmlApplicationCache(boolean htmlApplicationCache) {
this.htmlApplicationCache = htmlApplicationCache; this.htmlApplicationCache = htmlApplicationCache;
this.customized = true;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.compressed")
public boolean isCompressed() { public boolean isCompressed() {
return this.compressed; return super.isCompressed();
} }
public void setCompressed(boolean compressed) { @Override
this.compressed = compressed; public org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy getStrategy() {
} return this.strategy;
static Boolean getEnabled(boolean fixedEnabled, boolean contentEnabled, Boolean chainEnabled) {
return (fixedEnabled || contentEnabled) ? Boolean.TRUE : chainEnabled;
} }
} }
@ -173,17 +111,20 @@ public class ResourceProperties {
/** /**
* Strategies for extracting and embedding a resource version in its URL path. * 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; return this.fixed;
} }
public Content getContent() { @Override
public org.springframework.boot.autoconfigure.web.ResourceProperties.Content getContent() {
return this.content; return this.content;
} }
@ -192,32 +133,19 @@ public class ResourceProperties {
/** /**
* Version Strategy based on content hashing. * Version Strategy based on content hashing.
*/ */
public static class Content { @Deprecated
public static class Content extends Resources.Chain.Strategy.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[] { "/**" };
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.strategy.content.enabled")
public boolean isEnabled() { public boolean isEnabled() {
return this.enabled; return super.isEnabled();
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.strategy.content.paths")
public String[] getPaths() { public String[] getPaths() {
return this.paths; return super.getPaths();
}
public void setPaths(String[] paths) {
this.paths = paths;
} }
} }
@ -225,45 +153,25 @@ public class ResourceProperties {
/** /**
* Version Strategy based on a fixed version string. * Version Strategy based on a fixed version string.
*/ */
public static class Fixed { @Deprecated
public static class Fixed extends Resources.Chain.Strategy.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;
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.strategy.fixed.enabled")
public boolean isEnabled() { public boolean isEnabled() {
return this.enabled; return super.isEnabled();
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.strategy.fixed.paths")
public String[] getPaths() { public String[] getPaths() {
return this.paths; return super.getPaths();
}
public void setPaths(String[] paths) {
this.paths = paths;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.chain.strategy.fixed.version")
public String getVersion() { public String getVersion() {
return this.version; return super.getVersion();
}
public void setVersion(String version) {
this.version = version;
} }
} }
@ -271,30 +179,18 @@ public class ResourceProperties {
/** /**
* Cache configuration. * Cache configuration.
*/ */
public static class Cache { @Deprecated
public static class Cache extends Resources.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;
/**
* Cache control HTTP headers, only allows valid directive combinations. Overrides
* the 'spring.resources.cache.period' property.
*/
private final Cachecontrol cachecontrol = new Cachecontrol(); private final Cachecontrol cachecontrol = new Cachecontrol();
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.period")
public Duration getPeriod() { public Duration getPeriod() {
return this.period; return super.getPeriod();
}
public void setPeriod(Duration period) {
this.period = period;
} }
@Override
public Cachecontrol getCachecontrol() { public Cachecontrol getCachecontrol() {
return this.cachecontrol; return this.cachecontrol;
} }
@ -302,196 +198,74 @@ public class ResourceProperties {
/** /**
* Cache Control HTTP header configuration. * Cache Control HTTP header configuration.
*/ */
public static class Cachecontrol { @Deprecated
public static class Cachecontrol extends Resources.Cache.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;
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.max-age")
public Duration getMaxAge() { public Duration getMaxAge() {
return this.maxAge; return super.getMaxAge();
}
public void setMaxAge(Duration maxAge) {
this.maxAge = maxAge;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.no-cache")
public Boolean getNoCache() { public Boolean getNoCache() {
return this.noCache; return super.getNoCache();
}
public void setNoCache(Boolean noCache) {
this.noCache = noCache;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.no-store")
public Boolean getNoStore() { public Boolean getNoStore() {
return this.noStore; return super.getNoStore();
}
public void setNoStore(Boolean noStore) {
this.noStore = noStore;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.must-revalidate")
public Boolean getMustRevalidate() { public Boolean getMustRevalidate() {
return this.mustRevalidate; return super.getMustRevalidate();
}
public void setMustRevalidate(Boolean mustRevalidate) {
this.mustRevalidate = mustRevalidate;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.no-transform")
public Boolean getNoTransform() { public Boolean getNoTransform() {
return this.noTransform; return super.getNoTransform();
}
public void setNoTransform(Boolean noTransform) {
this.noTransform = noTransform;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.cache-public")
public Boolean getCachePublic() { public Boolean getCachePublic() {
return this.cachePublic; return super.getCachePublic();
}
public void setCachePublic(Boolean cachePublic) {
this.cachePublic = cachePublic;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.cache-private")
public Boolean getCachePrivate() { public Boolean getCachePrivate() {
return this.cachePrivate; return super.getCachePrivate();
}
public void setCachePrivate(Boolean cachePrivate) {
this.cachePrivate = cachePrivate;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.proxy-revalidate")
public Boolean getProxyRevalidate() { public Boolean getProxyRevalidate() {
return this.proxyRevalidate; return super.getProxyRevalidate();
}
public void setProxyRevalidate(Boolean proxyRevalidate) {
this.proxyRevalidate = proxyRevalidate;
} }
@Override
@DeprecatedConfigurationProperty(
replacement = "spring.web.resources.cache.cachecontrol.stale-while-revaliate")
public Duration getStaleWhileRevalidate() { public Duration getStaleWhileRevalidate() {
return this.staleWhileRevalidate; return super.getStaleWhileRevalidate();
}
public void setStaleWhileRevalidate(Duration staleWhileRevalidate) {
this.staleWhileRevalidate = staleWhileRevalidate;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.stale-if-error")
public Duration getStaleIfError() { public Duration getStaleIfError() {
return this.staleIfError; return super.getStaleIfError();
}
public void setStaleIfError(Duration staleIfError) {
this.staleIfError = staleIfError;
} }
@Override
@DeprecatedConfigurationProperty(replacement = "spring.web.resources.cache.cachecontrol.s-max-age")
public Duration getSMaxAge() { public Duration getSMaxAge() {
return this.sMaxAge; return super.getSMaxAge();
}
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();
} }
} }

View File

@ -16,9 +16,15 @@
package org.springframework.boot.autoconfigure.web; package org.springframework.boot.autoconfigure.web;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.context.properties.ConfigurationProperties; 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. * {@link ConfigurationProperties Configuration properties} for general web concerns.
@ -40,6 +46,8 @@ public class WebProperties {
*/ */
private LocaleResolver localeResolver = LocaleResolver.ACCEPT_HEADER; private LocaleResolver localeResolver = LocaleResolver.ACCEPT_HEADER;
private final Resources resources = new Resources();
public Locale getLocale() { public Locale getLocale() {
return this.locale; return this.locale;
} }
@ -56,6 +64,10 @@ public class WebProperties {
this.localeResolver = localeResolver; this.localeResolver = localeResolver;
} }
public Resources getResources() {
return this.resources;
}
public enum LocaleResolver { 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;
}
}
}
}
} }

View File

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

View File

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

View File

@ -28,7 +28,7 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; 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;
import org.springframework.boot.web.error.ErrorAttributeOptions.Include; import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
import org.springframework.boot.web.reactive.error.ErrorAttributes; import org.springframework.boot.web.reactive.error.ErrorAttributes;
@ -82,7 +82,7 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
private final ErrorAttributes errorAttributes; private final ErrorAttributes errorAttributes;
private final ResourceProperties resourceProperties; private final Resources resources;
private final TemplateAvailabilityProviders templateAvailabilityProviders; private final TemplateAvailabilityProviders templateAvailabilityProviders;
@ -92,13 +92,35 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
private List<ViewResolver> viewResolvers = Collections.emptyList(); 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) { ApplicationContext applicationContext) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null"); 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"); Assert.notNull(applicationContext, "ApplicationContext must not be null");
this.errorAttributes = errorAttributes; this.errorAttributes = errorAttributes;
this.resourceProperties = resourceProperties; this.resources = resources;
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext); this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
} }
@ -224,7 +246,7 @@ public abstract class AbstractErrorWebExceptionHandler implements ErrorWebExcept
} }
private Resource resolveResource(String viewName) { private Resource resolveResource(String viewName) {
for (String location : this.resourceProperties.getStaticLocations()) { for (String location : this.resources.getStaticLocations()) {
try { try {
Resource resource = this.applicationContext.getResource(location); Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html"); resource = resource.createRelative(viewName + ".html");

View File

@ -27,7 +27,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.web.ErrorProperties; 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;
import org.springframework.boot.web.error.ErrorAttributeOptions.Include; import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
import org.springframework.boot.web.reactive.error.ErrorAttributes; import org.springframework.boot.web.reactive.error.ErrorAttributes;
@ -97,10 +97,27 @@ public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHa
* @param resourceProperties the resources configuration properties * @param resourceProperties the resources configuration properties
* @param errorProperties the error configuration properties * @param errorProperties the error configuration properties
* @param applicationContext the current application context * @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) { 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; this.errorProperties = errorProperties;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {
}
}

View File

@ -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");
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -23,6 +23,8 @@ import java.util.Map;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication; 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.logger.DevToolsLogFactory;
import org.springframework.boot.devtools.restart.Restarter; import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.boot.devtools.system.DevToolsEnablementDeducer; import org.springframework.boot.devtools.system.DevToolsEnablementDeducer;
@ -85,7 +87,9 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
if (canAddProperties(environment)) { if (canAddProperties(environment)) {
logger.info(LogMessage.format("Devtools property defaults active! Set '%s' to 'false' to disable", logger.info(LogMessage.format("Devtools property defaults active! Set '%s' to 'false' to disable",
ENABLED)); 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)) { if (isWebApplication(environment) && !environment.containsProperty(WEB_LOGGING)) {
logger.info(LogMessage.format( 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) { private boolean isLocalApplication(ConfigurableEnvironment environment) {
return environment.getPropertySources().get("remoteUrl") == null; return environment.getPropertySources().get("remoteUrl") == null;
} }

View File

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

View File

@ -2608,7 +2608,7 @@ For instance, relocating all resources to `/resources/**` can be achieved as fol
static-path-pattern: "/resources/**" 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. 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]. 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] [source,yaml,indent=0,subs="verbatim,quotes,attributes",configprops,configblocks]
---- ----
spring: spring:
resources: web:
chain: resources:
strategy: chain:
content: strategy:
enabled: true content:
paths: "/**" 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. 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] [source,yaml,indent=0,subs="verbatim,quotes,attributes",configprops,configblocks]
---- ----
spring: spring:
resources: web:
chain: resources:
strategy: chain:
content: strategy:
enabled: true content:
paths: "/**" enabled: true
fixed: paths: "/**"
enabled: true fixed:
paths: "/js/lib/" enabled: true
version: "v12" 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"/>`). 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/**" 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. 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. 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. So, if there is an `index.html` in any of your locations on startup, it is the home page of the application.