Polishing

(cherry picked from commit 1687d90a8c)
This commit is contained in:
Juergen Hoeller
2026-04-08 15:09:33 +02:00
parent d675132ac0
commit 241a7dca02
5 changed files with 53 additions and 32 deletions
@@ -18,10 +18,9 @@ package org.springframework.validation;
import java.io.Serializable;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.PropertyAccessException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -32,9 +32,11 @@ import org.springframework.lang.Nullable;
/**
* Simple LRU (Least Recently Used) cache, bounded by a specified cache capacity.
*
* <p>This is a simplified, opinionated implementation of an LRU cache for internal
* use in Spring Framework. It is inspired from
* <a href="https://github.com/ben-manes/concurrentlinkedhashmap">ConcurrentLinkedHashMap</a>.
*
* <p>Read and write operations are internally recorded in dedicated buffers,
* then drained at chosen times to avoid contention.
*
@@ -70,6 +72,7 @@ public final class ConcurrentLruCache<K, V> {
private final AtomicReference<DrainStatus> drainStatus = new AtomicReference<>(DrainStatus.IDLE);
/**
* Create a new cache instance with the given capacity and generator function.
* @param capacity the maximum number of entries in the cache
@@ -89,6 +92,7 @@ public final class ConcurrentLruCache<K, V> {
this.writeOperations = new WriteOperations();
}
/**
* Retrieve an entry from the cache, potentially triggering generation of the value.
* @param key the key to retrieve the entry for
@@ -345,10 +349,13 @@ public final class ConcurrentLruCache<K, V> {
abstract boolean shouldDrainBuffers(boolean delayable);
}
private enum CacheEntryState {
ACTIVE, PENDING_REMOVAL, REMOVED
}
private record CacheEntry<V>(V value, CacheEntryState state) {
boolean isActive() {
@@ -356,6 +363,7 @@ public final class ConcurrentLruCache<K, V> {
}
}
private static final class ReadOperations<K, V> {
private static final int BUFFER_COUNT = detectNumberOfBuffers();
@@ -453,6 +461,7 @@ public final class ConcurrentLruCache<K, V> {
}
}
private static final class WriteOperations {
private static final int DRAIN_THRESHOLD = 16;
@@ -479,11 +488,12 @@ public final class ConcurrentLruCache<K, V> {
task.run();
}
}
}
@SuppressWarnings("serial")
private static final class Node<K, V> extends AtomicReference<CacheEntry<V>> {
final K key;
@Nullable
@@ -529,7 +539,6 @@ public final class ConcurrentLruCache<K, V> {
@Nullable
Node<K, V> last;
@Nullable
Node<K, V> poll() {
if (this.first == null) {
@@ -604,7 +613,6 @@ public final class ConcurrentLruCache<K, V> {
unlink(e);
}
}
}
}
@@ -28,15 +28,15 @@ import org.springframework.lang.Nullable;
/**
* Representation of a complete RFC 9457 error response including status,
* headers, and an RFC 9457 formatted {@link ProblemDetail} body. Allows any
* exception to expose HTTP error response information.
* headers, and an RFC 9457 formatted {@link ProblemDetail} body.
* Allows any exception to expose HTTP error response information.
*
* <p>{@link ErrorResponseException} is a default implementation of this
* interface and a convenient base class for other exceptions to use.
*
* <p>{@code ErrorResponse} is supported as a return value from
* {@code @ExceptionHandler} methods that render directly to the response, for example,
* by being marked {@code @ResponseBody}, or declared in an
* {@code @ExceptionHandler} methods that render directly to the response,
* for example, by being marked {@code @ResponseBody}, or declared in an
* {@code @RestController} or {@code RestControllerAdvice} class.
*
* @author Rossen Stoyanchev
@@ -96,7 +96,7 @@ public class HandlerMethod extends AnnotatedMethod {
@Nullable
private HandlerMethod resolvedFromHandlerMethod;
private @Nullable HandlerMethod resolvedBeanHandlerMethod;
private volatile @Nullable HandlerMethod resolvedBeanHandlerMethod;
private final String description;
@@ -196,16 +196,21 @@ public class HandlerMethod extends AnnotatedMethod {
this.beanFactory = handlerMethod.beanFactory;
this.messageSource = handlerMethod.messageSource;
this.beanType = handlerMethod.beanType;
this.validateArguments = (initValidateFlags ?
MethodValidationInitializer.checkArguments(this.beanType, getMethodParameters()) :
handlerMethod.validateArguments);
this.validateReturnValue = (initValidateFlags ?
MethodValidationInitializer.checkReturnValue(this.beanType, getBridgedMethod()) :
handlerMethod.validateReturnValue);
this.responseStatus = handlerMethod.responseStatus;
this.responseStatusReason = handlerMethod.responseStatusReason;
this.resolvedFromHandlerMethod = (handlerMethod.resolvedFromHandlerMethod != null ?
handlerMethod.resolvedFromHandlerMethod : handlerMethod);
this.description = handlerMethod.toString();
}
@@ -224,7 +229,10 @@ public class HandlerMethod extends AnnotatedMethod {
this.responseStatus = annotation.code();
this.responseStatusReason = resolvedReason;
if (StringUtils.hasText(this.responseStatusReason) && getMethod().getReturnType() != void.class) {
logger.warn("Return value of [" + getMethod() + "] will be ignored since @ResponseStatus 'reason' attribute is set.");
if (logger.isWarnEnabled()) {
logger.warn("Return value of [" + getMethod() +
"] will be ignored since @ResponseStatus 'reason' attribute is set.");
}
}
}
}
@@ -334,23 +342,22 @@ public class HandlerMethod extends AnnotatedMethod {
* <p>If the {@link #getBean() handler} is not String, return the same instance.
*/
public HandlerMethod createWithResolvedBean() {
if (this.resolvedBeanHandlerMethod != null) {
return this.resolvedBeanHandlerMethod;
HandlerMethod resolvedBeanHandlerMethod = this.resolvedBeanHandlerMethod;
if (resolvedBeanHandlerMethod != null) {
return resolvedBeanHandlerMethod;
}
// We need to resolve a bean name reference.
if (!(this.bean instanceof String beanName)) {
return this;
}
Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
Object handler = this.beanFactory.getBean(beanName);
Assert.notNull(handler, "No handler instance");
HandlerMethod handlerMethod = new HandlerMethod(this, handler, false);
if (this.beanFactory.isSingleton(beanName)) {
this.resolvedBeanHandlerMethod = handlerMethod;
}
return handlerMethod;
}
@@ -209,7 +209,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
/**
* Configure the list of {@link ResourceResolver ResourceResolvers} to use.
* <p>By default {@link PathResourceResolver} is configured. If using this property,
* <p>By default, {@link PathResourceResolver} is configured. If using this property,
* it is recommended to add {@link PathResourceResolver} as the last resolver.
*/
public void setResourceResolvers(@Nullable List<ResourceResolver> resourceResolvers) {
@@ -228,7 +228,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
/**
* Configure the list of {@link ResourceTransformer ResourceTransformers} to use.
* <p>By default no transformers are configured for use.
* <p>By default, no transformers are configured for use.
*/
public void setResourceTransformers(@Nullable List<ResourceTransformer> resourceTransformers) {
this.resourceTransformers.clear();
@@ -246,7 +246,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
/**
* Configure the {@link ResourceHttpMessageConverter} to use.
* <p>By default a {@link ResourceHttpMessageConverter} will be configured.
* <p>By default, a {@link ResourceHttpMessageConverter} will be configured.
* @since 4.3
*/
public void setResourceHttpMessageConverter(@Nullable ResourceHttpMessageConverter messageConverter) {
@@ -264,7 +264,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
/**
* Configure the {@link ResourceRegionHttpMessageConverter} to use.
* <p>By default a {@link ResourceRegionHttpMessageConverter} will be configured.
* <p>By default, a {@link ResourceRegionHttpMessageConverter} will be configured.
* @since 4.3
*/
public void setResourceRegionHttpMessageConverter(@Nullable ResourceRegionHttpMessageConverter messageConverter) {
@@ -331,7 +331,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
/**
* Specify the CORS configuration for resources served by this handler.
* <p>By default this is not set in which allows cross-origin requests.
* <p>By default, this is not set in which allows cross-origin requests.
*/
public void setCorsConfiguration(CorsConfiguration corsConfiguration) {
this.corsConfiguration = corsConfiguration;
@@ -581,7 +581,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
// For very general mappings (for example, "/") we need to check 404 first
Resource resource = getResource(request);
if (resource == null) {
logger.debug("Resource not found");
throw new NoResourceFoundException(HttpMethod.valueOf(request.getMethod()), getPath(request));
}
@@ -597,10 +596,12 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
prepareResponse(response);
// Header phase
String eTagValue = (getEtagGenerator() != null ? getEtagGenerator().apply(resource) : null);
String etagValue = (getEtagGenerator() != null ? getEtagGenerator().apply(resource) : null);
long lastModified = (isUseLastModified() ? resource.lastModified() : -1);
if (new ServletWebRequest(request, response).checkNotModified(eTagValue, lastModified)) {
logger.trace("Resource not modified");
if (new ServletWebRequest(request, response).checkNotModified(etagValue, lastModified)) {
if (logger.isTraceEnabled()) {
logger.trace("Resource not modified: " + resource);
}
return;
}
@@ -611,8 +612,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
// Content phase
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
if (request.getHeader(HttpHeaders.RANGE) == null) {
Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
Assert.state(this.resourceHttpMessageConverter != null, "Converter not initialized");
if (HttpMethod.HEAD.matches(request.getMethod())) {
this.resourceHttpMessageConverter.addDefaultHeaders(outputMessage, resource, mediaType);
outputMessage.flush();
@@ -622,7 +622,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
}
}
else {
Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized");
Assert.state(this.resourceRegionHttpMessageConverter != null, "Converter not initialized");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
@@ -643,14 +643,21 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
String path = getPath(request);
path = processPath(path);
if (ResourceHandlerUtils.shouldIgnoreInputPath(path) || isInvalidPath(path)) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring invalid resource path [" + path + "]");
}
return null;
}
Assert.state(this.resolverChain != null, "ResourceResolverChain not initialized.");
Assert.state(this.transformerChain != null, "ResourceTransformerChain not initialized.");
Assert.state(this.resolverChain != null, "ResourceResolverChain not initialized");
Assert.state(this.transformerChain != null, "ResourceTransformerChain not initialized");
Resource resource = this.resolverChain.resolveResource(request, path, getLocations());
if (resource != null) {
if (resource == null) {
if (logger.isDebugEnabled()) {
logger.debug("Resource not found for path [" + path + "]");
}
}
else {
resource = this.transformerChain.transform(request, resource);
}
return resource;