diff --git a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java
index 21c35afd473..e9a918cfc6d 100644
--- a/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/support/DefaultLifecycleProcessor.java
@@ -409,6 +409,9 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
if (logger.isWarnEnabled()) {
logger.warn("Failed to stop bean '" + beanName + "'", ex);
}
+ if (bean instanceof SmartLifecycle) {
+ latch.countDown();
+ }
}
}
}
diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
index 2b19d68f752..e79c7195d76 100644
--- a/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
+++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/Scheduled.java
@@ -126,6 +126,35 @@ public @interface Scheduled {
*/
String zone() default "";
+ /**
+ * Execute the annotated method with a fixed period between invocations.
+ *
The time unit is milliseconds by default but can be overridden via
+ * {@link #timeUnit}.
+ * @return the period
+ */
+ long fixedRate() default -1;
+
+ /**
+ * Execute the annotated method with a fixed period between invocations.
+ *
The duration String can be in several formats:
+ *
+ * - a plain integer — which is interpreted to represent a duration in
+ * milliseconds by default unless overridden via {@link #timeUnit()} (prefer
+ * using {@link #fixedDelay()} in that case)
+ * - any of the known {@link org.springframework.format.annotation.DurationFormat.Style
+ * DurationFormat.Style}: the {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 ISO8601}
+ * style or the {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE SIMPLE} style
+ * — using the {@link #timeUnit()} as fallback if the string doesn't contain an explicit unit
+ * - one of the above, with Spring-style "${...}" placeholders as well as SpEL expressions
+ *
+ * @return the period as a String value — for example a placeholder,
+ * or a {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 java.time.Duration} compliant value
+ * or a {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE simple format} compliant value
+ * @since 3.2.2
+ * @see #fixedRate()
+ */
+ String fixedRateString() default "";
+
/**
* Execute the annotated method with a fixed period between the end of the
* last invocation and the start of the next.
@@ -143,13 +172,13 @@ public @interface Scheduled {
* last invocation and the start of the next.
* The duration String can be in several formats:
*
- * - a plain integer — which is interpreted to represent a duration in
- * milliseconds by default unless overridden via {@link #timeUnit()} (prefer
- * using {@link #fixedDelay()} in that case)
- * - any of the known {@link org.springframework.format.annotation.DurationFormat.Style
- * DurationFormat.Style}: the {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 ISO8601}
- * style or the {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE SIMPLE} style
- * — using the {@link #timeUnit()} as fallback if the string doesn't contain an explicit unit
+ * - a plain integer — which is interpreted to represent a duration in
+ * milliseconds by default unless overridden via {@link #timeUnit()} (prefer
+ * using {@link #fixedDelay()} in that case)
+ * - any of the known {@link org.springframework.format.annotation.DurationFormat.Style
+ * DurationFormat.Style}: the {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 ISO8601}
+ * style or the {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE SIMPLE} style
+ * — using the {@link #timeUnit()} as fallback if the string doesn't contain an explicit unit
*
* NOTE: With virtual threads, fixed rates and cron triggers are recommended
* over fixed delays. Fixed-delay tasks operate on a single scheduler thread
@@ -162,35 +191,6 @@ public @interface Scheduled {
*/
String fixedDelayString() default "";
- /**
- * Execute the annotated method with a fixed period between invocations.
- *
The time unit is milliseconds by default but can be overridden via
- * {@link #timeUnit}.
- * @return the period
- */
- long fixedRate() default -1;
-
- /**
- * Execute the annotated method with a fixed period between invocations.
- *
The duration String can be in several formats:
- *
- * - a plain integer — which is interpreted to represent a duration in
- * milliseconds by default unless overridden via {@link #timeUnit()} (prefer
- * using {@link #fixedDelay()} in that case)
- * - any of the known {@link org.springframework.format.annotation.DurationFormat.Style
- * DurationFormat.Style}: the {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 ISO8601}
- * style or the {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE SIMPLE} style
- * — using the {@link #timeUnit()} as fallback if the string doesn't contain an explicit unit
- * - one of the above, with Spring-style "${...}" placeholders as well as SpEL expressions
- *
- * @return the period as a String value — for example a placeholder,
- * or a {@link org.springframework.format.annotation.DurationFormat.Style#ISO8601 java.time.Duration} compliant value
- * or a {@link org.springframework.format.annotation.DurationFormat.Style#SIMPLE simple format} compliant value
- * @since 3.2.2
- * @see #fixedRate()
- */
- String fixedRateString() default "";
-
/**
* Number of units of time to delay before the first execution of a
* {@link #fixedRate} or {@link #fixedDelay} task.
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java
index bf93682634c..8b921c1bda3 100644
--- a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java
@@ -34,6 +34,7 @@ import java.net.URLClassLoader;
import java.net.URLConnection;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
+import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
@@ -979,7 +980,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
.formatted(rootPath.toAbsolutePath(), subPattern));
}
- try (Stream files = Files.walk(rootPath)) {
+ try (Stream files = Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS)) {
files.filter(isMatchingFile).sorted().map(FileSystemResource::new).forEach(result::add);
}
catch (Exception ex) {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java
index 4a10d726127..db0c724f7b5 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RouterFunctions.java
@@ -160,6 +160,7 @@ public abstract class RouterFunctions {
*/
public static RouterFunction resource(RequestPredicate predicate, Resource resource,
BiConsumer headersConsumer) {
+
return resources(new PredicateResourceLookupFunction(predicate, resource), headersConsumer);
}
@@ -197,6 +198,7 @@ public abstract class RouterFunctions {
*/
public static RouterFunction resources(String pattern, Resource location,
BiConsumer headersConsumer) {
+
return resources(resourceLookupFunction(pattern, location), headersConsumer);
}
@@ -240,7 +242,9 @@ public abstract class RouterFunctions {
* @return a router function that routes to resources
* @since 6.1
*/
- public static RouterFunction resources(Function> lookupFunction, BiConsumer headersConsumer) {
+ public static RouterFunction resources(Function> lookupFunction,
+ BiConsumer headersConsumer) {
+
return new ResourcesRouterFunction(lookupFunction, headersConsumer);
}
@@ -250,12 +254,12 @@ public abstract class RouterFunctions {
* can be used to change the {@code PathPatternParser} properties from the defaults, for instance to change
* {@linkplain PathPatternParser#setCaseSensitive(boolean) case sensitivity}.
* @param routerFunction the router function to change the parser in
- * @param parser the parser to change to.
+ * @param parser the parser to change to
* @param the type of response returned by the handler function
* @return the change router function
*/
- public static RouterFunction changeParser(RouterFunction routerFunction,
- PathPatternParser parser) {
+ public static RouterFunction changeParser(
+ RouterFunction routerFunction, PathPatternParser parser) {
Assert.notNull(routerFunction, "RouterFunction must not be null");
Assert.notNull(parser, "Parser must not be null");
@@ -1151,7 +1155,6 @@ public abstract class RouterFunctions {
public void accept(Visitor visitor) {
visitor.route(this.predicate, this.handlerFunction);
}
-
}
@@ -1173,13 +1176,10 @@ public abstract class RouterFunctions {
return this.predicate.nest(serverRequest)
.map(nestedRequest -> {
if (logger.isTraceEnabled()) {
- logger.trace(
- String.format(
- "Nested predicate \"%s\" matches against \"%s\"",
- this.predicate, serverRequest));
+ logger.trace(String.format("Nested predicate \"%s\" matches against \"%s\"",
+ this.predicate, serverRequest));
}
- Optional> result =
- this.routerFunction.route(nestedRequest);
+ Optional> result = this.routerFunction.route(nestedRequest);
if (result.isPresent() && nestedRequest != serverRequest) {
// new attributes map from nestedRequest.attributes() can be composed of the old attributes,
// which means that clearing the old attributes will remove those values from new attributes as well
@@ -1202,7 +1202,6 @@ public abstract class RouterFunctions {
this.routerFunction.accept(visitor);
visitor.endNested(this.predicate);
}
-
}
@@ -1212,11 +1211,11 @@ public abstract class RouterFunctions {
private final BiConsumer headersConsumer;
-
public ResourcesRouterFunction(Function> lookupFunction,
BiConsumer headersConsumer) {
- Assert.notNull(lookupFunction, "Function must not be null");
- Assert.notNull(headersConsumer, "HeadersConsumer must not be null");
+
+ Assert.notNull(lookupFunction, "Lookup function must not be null");
+ Assert.notNull(headersConsumer, "Headers consumer must not be null");
this.lookupFunction = lookupFunction;
this.headersConsumer = headersConsumer;
}
@@ -1284,5 +1283,4 @@ public abstract class RouterFunctions {
}
}
-
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java
index 3831e99dbac..ca7e06a3563 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MethodValidationTests.java
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import java.util.function.Consumer;
@@ -28,10 +29,12 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import jakarta.validation.executable.ExecutableValidator;
import jakarta.validation.metadata.BeanDescriptor;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.MessageSourceResolvable;
+import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.MediaType;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@@ -91,6 +94,8 @@ class MethodValidationTests {
@BeforeEach
void setup() throws Exception {
+ LocaleContextHolder.setDefaultLocale(Locale.UK);
+
LocalValidatorFactoryBean validatorBean = new LocalValidatorFactoryBean();
validatorBean.afterPropertiesSet();
this.jakartaValidator = new InvocationCountingValidator(validatorBean);
@@ -120,6 +125,11 @@ class MethodValidationTests {
return handlerAdapter;
}
+ @AfterEach
+ void reset() {
+ LocaleContextHolder.setDefaultLocale(null);
+ }
+
@Test
void modelAttribute() {
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/EvalTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/EvalTagTests.java
index 400f92abcfa..ad5608f043a 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/EvalTagTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/EvalTagTests.java
@@ -22,14 +22,15 @@ import java.util.Locale;
import java.util.Map;
import jakarta.servlet.jsp.tagext.Tag;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.MapPropertySource;
import org.springframework.format.annotation.NumberFormat;
import org.springframework.format.annotation.NumberFormat.Style;
-import org.springframework.format.number.PercentStyleFormatter;
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
@@ -49,6 +50,8 @@ class EvalTagTests extends AbstractTagTests {
@BeforeEach
void setup() {
+ LocaleContextHolder.setDefaultLocale(Locale.UK);
+
context = createPageContext();
FormattingConversionServiceFactoryBean factory = new FormattingConversionServiceFactoryBean();
factory.afterPropertiesSet();
@@ -58,6 +61,11 @@ class EvalTagTests extends AbstractTagTests {
tag.setPageContext(context);
}
+ @AfterEach
+ void reset() {
+ LocaleContextHolder.setDefaultLocale(null);
+ }
+
@Test
void printScopedAttributeResult() throws Exception {
@@ -81,13 +89,12 @@ class EvalTagTests extends AbstractTagTests {
@Test
void printFormattedScopedAttributeResult() throws Exception {
- PercentStyleFormatter formatter = new PercentStyleFormatter();
tag.setExpression("bean.formattable");
int action = tag.doStartTag();
assertThat(action).isEqualTo(Tag.EVAL_BODY_INCLUDE);
action = tag.doEndTag();
assertThat(action).isEqualTo(Tag.EVAL_PAGE);
- assertThat(((MockHttpServletResponse) context.getResponse()).getContentAsString()).isEqualTo(formatter.print(new BigDecimal(".25"), Locale.getDefault()));
+ assertThat(((MockHttpServletResponse) context.getResponse()).getContentAsString()).isEqualTo("25%");
}
@Test