From 762434c85fa3007a738a2ab6ed206d8bf8f8c4c6 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 20 Jun 2024 15:01:04 +0100 Subject: [PATCH 1/5] Upgrade to Spring Framework 6.2.0-M4 See gh-41177 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d70bd556d7e..4e1cad6fed6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ junitJupiterVersion=5.10.2 kotlinVersion=1.9.24 mavenVersion=3.9.4 nativeBuildToolsVersion=0.10.2 -springFrameworkVersion=6.1.10 +springFrameworkVersion=6.2.0-M4 springFramework60xVersion=6.0.21 tomcatVersion=10.1.25 snakeYamlVersion=2.2 From 2053e13c09b52f5b7d769645006dfd4af8983d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Sat, 24 Feb 2024 09:29:02 +0100 Subject: [PATCH 2/5] Adapt to new Placeholder resolution API See gh-41177 --- .../autoconfigure/jdbc/DataSourceAutoConfiguration.java | 1 + ...ngBootTestRandomPortEnvironmentPostProcessorTests.java | 7 ++++--- ...figDataEnvironmentContributorPlaceholdersResolver.java | 3 ++- .../bind/PropertySourcesPlaceholdersResolver.java | 8 +++++--- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java index 5ad8ff75ae8..43e9620a462 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java @@ -157,6 +157,7 @@ public class DataSourceAutoConfiguration { return StringUtils.hasText(environment.getProperty(DATASOURCE_URL_PROPERTY)); } catch (IllegalArgumentException ex) { + // NOTE: This should be PlaceholderResolutionException // Ignore unresolvable placeholder errors } } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java index e3ee27b5e36..3147d19e520 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java @@ -27,9 +27,10 @@ import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.mock.env.MockEnvironment; import org.springframework.test.context.support.TestPropertySourceUtils; +import org.springframework.util.PlaceholderResolutionException; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link SpringBootTestRandomPortEnvironmentPostProcessor}. @@ -169,7 +170,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { addTestPropertySource("0", null); this.propertySources .addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "${port}"))); - assertThatIllegalArgumentException() + assertThatExceptionOfType(PlaceholderResolutionException.class) .isThrownBy(() -> this.postProcessor.postProcessEnvironment(this.environment, null)) .withMessage("Could not resolve placeholder 'port' in value \"${port}\""); } @@ -196,7 +197,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { source.put("server.port", "${port}"); source.put("management.server.port", "9090"); this.propertySources.addLast(new MapPropertySource("other", source)); - assertThatIllegalArgumentException() + assertThatExceptionOfType(PlaceholderResolutionException.class) .isThrownBy(() -> this.postProcessor.postProcessEnvironment(this.environment, null)) .withMessage("Could not resolve placeholder 'port' in value \"${port}\""); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolver.java index 56add53e59d..cce91638af8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolver.java @@ -56,7 +56,8 @@ class ConfigDataEnvironmentContributorPlaceholdersResolver implements Placeholde this.failOnResolveFromInactiveContributor = failOnResolveFromInactiveContributor; this.conversionService = conversionService; this.helper = new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, - SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true); + SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, + SystemPropertyUtils.ESCAPE_CHARACTER, true); } @Override diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java index b6b6b9e10cd..67e432fab52 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 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. @@ -47,8 +47,10 @@ public class PropertySourcesPlaceholdersResolver implements PlaceholdersResolver public PropertySourcesPlaceholdersResolver(Iterable> sources, PropertyPlaceholderHelper helper) { this.sources = sources; - this.helper = (helper != null) ? helper : new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, - SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true); + this.helper = (helper != null) ? helper + : new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, + SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, + SystemPropertyUtils.ESCAPE_CHARACTER, true); } @Override From 305bfb1641b222f4a7a93fb224f3267ca25a4715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 12 Mar 2024 08:26:23 +0100 Subject: [PATCH 3/5] Adapt to Mockito support in the Test Context Framework This commit updates MockitoTestExecutionListener to not handle mocks as this is already done in the listener provided by the core framework, and registration can only happen once. Integration tests have been left as-is to validate that the presence of both listeners doesn't have an unwanted side effect. See gh-41177 --- .../mockito/MockitoTestExecutionListener.java | 68 +------------------ .../MockitoTestExecutionListenerTests.java | 10 +-- 2 files changed, 2 insertions(+), 76 deletions(-) diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java index be4561d45a5..0da250af7e7 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListener.java @@ -16,27 +16,18 @@ package org.springframework.boot.test.mock.mockito; -import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.util.LinkedHashSet; -import java.util.Set; import java.util.function.BiConsumer; -import org.mockito.Captor; -import org.mockito.MockitoAnnotations; - import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.util.ReflectionUtils; -import org.springframework.util.ReflectionUtils.FieldCallback; /** * {@link TestExecutionListener} to enable {@link MockBean @MockBean} and - * {@link SpyBean @SpyBean} support. Also triggers - * {@link MockitoAnnotations#openMocks(Object)} when any Mockito annotations used, - * primarily to allow {@link Captor @Captor} annotations. + * {@link SpyBean @SpyBean} support. *

* To use the automatic reset support of {@code @MockBean} and {@code @SpyBean}, configure * {@link ResetMocksTestExecutionListener} as well. @@ -49,8 +40,6 @@ import org.springframework.util.ReflectionUtils.FieldCallback; */ public class MockitoTestExecutionListener extends AbstractTestExecutionListener { - private static final String MOCKS_ATTRIBUTE_NAME = MockitoTestExecutionListener.class.getName() + ".mocks"; - @Override public final int getOrder() { return 1950; @@ -58,8 +47,6 @@ public class MockitoTestExecutionListener extends AbstractTestExecutionListener @Override public void prepareTestInstance(TestContext testContext) throws Exception { - closeMocks(testContext); - initMocks(testContext); injectFields(testContext); } @@ -67,41 +54,10 @@ public class MockitoTestExecutionListener extends AbstractTestExecutionListener public void beforeTestMethod(TestContext testContext) throws Exception { if (Boolean.TRUE.equals( testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) { - closeMocks(testContext); - initMocks(testContext); reinjectFields(testContext); } } - @Override - public void afterTestMethod(TestContext testContext) throws Exception { - closeMocks(testContext); - } - - @Override - public void afterTestClass(TestContext testContext) throws Exception { - closeMocks(testContext); - } - - private void initMocks(TestContext testContext) { - if (hasMockitoAnnotations(testContext)) { - testContext.setAttribute(MOCKS_ATTRIBUTE_NAME, MockitoAnnotations.openMocks(testContext.getTestInstance())); - } - } - - private void closeMocks(TestContext testContext) throws Exception { - Object mocks = testContext.getAttribute(MOCKS_ATTRIBUTE_NAME); - if (mocks instanceof AutoCloseable closeable) { - closeable.close(); - } - } - - private boolean hasMockitoAnnotations(TestContext testContext) { - MockitoAnnotationCollection collector = new MockitoAnnotationCollection(); - ReflectionUtils.doWithFields(testContext.getTestClass(), collector); - return collector.hasAnnotations(); - } - private void injectFields(TestContext testContext) { postProcessFields(testContext, (mockitoField, postProcessor) -> postProcessor.inject(mockitoField.field, mockitoField.target, mockitoField.definition)); @@ -130,28 +86,6 @@ public class MockitoTestExecutionListener extends AbstractTestExecutionListener } } - /** - * {@link FieldCallback} to collect Mockito annotations. - */ - private static final class MockitoAnnotationCollection implements FieldCallback { - - private final Set annotations = new LinkedHashSet<>(); - - @Override - public void doWith(Field field) throws IllegalArgumentException { - for (Annotation annotation : field.getDeclaredAnnotations()) { - if (annotation.annotationType().getName().startsWith("org.mockito")) { - this.annotations.add(annotation); - } - } - } - - boolean hasAnnotations() { - return !this.annotations.isEmpty(); - } - - } - private static final class MockitoField { private final Field field; diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java index 3ce7d272a2e..74dacbdcf1b 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/MockitoTestExecutionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -53,14 +53,6 @@ class MockitoTestExecutionListenerTests { @Mock private MockitoPostProcessor postProcessor; - @Test - void prepareTestInstanceShouldInitMockitoAnnotations() throws Exception { - WithMockitoAnnotations instance = new WithMockitoAnnotations(); - this.listener.prepareTestInstance(mockTestContext(instance)); - assertThat(instance.mock).isNotNull(); - assertThat(instance.captor).isNotNull(); - } - @Test void prepareTestInstanceShouldInjectMockBean() throws Exception { given(this.applicationContext.getBean(MockitoPostProcessor.class)).willReturn(this.postProcessor); From 74a2144a3737b0602a5d3365639001756c617254 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Wed, 19 Jun 2024 16:39:22 +0200 Subject: [PATCH 4/5] Adapt to Task and ScheduledTask changes in Framework Spring Framework wraps `Task` and `ScheduledTask` runnables to collect and share metadata about task execution and scheduling. The `ScheduledTasksEndpoint` descriptors were relying on the fact that tasks would never be wrapped. Spring Framework already wrapped runnables in various cases, for methods returning `Callable` or reactive types. This commit makes use of the `toString()` method to describe the runnable. Runnable implementations can override this method for displaying purposes on the actuator endpoint. See spring-projects/spring-framework#24560 See gh-41177 --- .../actuate/scheduling/ScheduledTasksEndpoint.java | 12 ++---------- .../scheduling/ScheduledTasksEndpointTests.java | 12 ++++++------ 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java index 7b666c8b05c..98eff14a5d4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.scheduling; -import java.lang.reflect.Method; import java.time.Duration; import java.util.Collection; import java.util.Collections; @@ -46,7 +45,6 @@ import org.springframework.scheduling.config.Task; import org.springframework.scheduling.config.TriggerTask; import org.springframework.scheduling.support.CronTrigger; import org.springframework.scheduling.support.PeriodicTrigger; -import org.springframework.scheduling.support.ScheduledMethodRunnable; /** * {@link Endpoint @Endpoint} to expose information about an application's scheduled @@ -284,13 +282,7 @@ public class ScheduledTasksEndpoint { private final String target; private RunnableDescriptor(Runnable runnable) { - if (runnable instanceof ScheduledMethodRunnable scheduledMethodRunnable) { - Method method = scheduledMethodRunnable.getMethod(); - this.target = method.getDeclaringClass().getName() + "." + method.getName(); - } - else { - this.target = runnable.getClass().getName(); - } + this.target = runnable.toString(); } public String getTarget() { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java index 8befd959fcd..6456d266da6 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java @@ -80,7 +80,7 @@ class ScheduledTasksEndpointTests { assertThat(tasks.getCron()).hasSize(1); CronTaskDescriptor description = (CronTaskDescriptor) tasks.getCron().get(0); assertThat(description.getExpression()).isEqualTo("0 0 0/6 1/1 * ?"); - assertThat(description.getRunnable().getTarget()).isEqualTo(CronTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(CronTriggerRunnable.class.getName()); }); } @@ -109,7 +109,7 @@ class ScheduledTasksEndpointTests { FixedDelayTaskDescriptor description = (FixedDelayTaskDescriptor) tasks.getFixedDelay().get(0); assertThat(description.getInitialDelay()).isEqualTo(2000); assertThat(description.getInterval()).isEqualTo(1000); - assertThat(description.getRunnable().getTarget()).isEqualTo(FixedDelayTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(FixedDelayTriggerRunnable.class.getName()); }); } @@ -123,7 +123,7 @@ class ScheduledTasksEndpointTests { FixedDelayTaskDescriptor description = (FixedDelayTaskDescriptor) tasks.getFixedDelay().get(0); assertThat(description.getInitialDelay()).isEqualTo(0); assertThat(description.getInterval()).isEqualTo(1000); - assertThat(description.getRunnable().getTarget()).isEqualTo(FixedDelayTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(FixedDelayTriggerRunnable.class.getName()); }); } @@ -152,7 +152,7 @@ class ScheduledTasksEndpointTests { FixedRateTaskDescriptor description = (FixedRateTaskDescriptor) tasks.getFixedRate().get(0); assertThat(description.getInitialDelay()).isEqualTo(3000); assertThat(description.getInterval()).isEqualTo(2000); - assertThat(description.getRunnable().getTarget()).isEqualTo(FixedRateTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(FixedRateTriggerRunnable.class.getName()); }); } @@ -166,7 +166,7 @@ class ScheduledTasksEndpointTests { FixedRateTaskDescriptor description = (FixedRateTaskDescriptor) tasks.getFixedRate().get(0); assertThat(description.getInitialDelay()).isEqualTo(0); assertThat(description.getInterval()).isEqualTo(2000); - assertThat(description.getRunnable().getTarget()).isEqualTo(FixedRateTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(FixedRateTriggerRunnable.class.getName()); }); } @@ -178,7 +178,7 @@ class ScheduledTasksEndpointTests { assertThat(tasks.getFixedRate()).isEmpty(); assertThat(tasks.getCustom()).hasSize(1); CustomTriggerTaskDescriptor description = (CustomTriggerTaskDescriptor) tasks.getCustom().get(0); - assertThat(description.getRunnable().getTarget()).isEqualTo(CustomTriggerRunnable.class.getName()); + assertThat(description.getRunnable().getTarget()).contains(CustomTriggerRunnable.class.getName()); assertThat(description.getTrigger()).isEqualTo(CustomTriggerTask.trigger.toString()); }); } From fe4c34d2263f4f4905a70594720d4bc8cb221291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Wed, 3 Apr 2024 10:16:43 +0200 Subject: [PATCH 5/5] Adapt test to include MappingJackson2CborHttpMessageConverter See spring-projects/spring-framework#32428 See gh-41177 --- .../boot/autoconfigure/http/HttpMessageConvertersTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java index 4a93124f2d5..17dcc631d12 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -146,7 +146,7 @@ class HttpMessageConvertersTests { } assertThat(converterClasses).containsExactly(ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class, ResourceHttpMessageConverter.class, - MappingJackson2HttpMessageConverter.class); + MappingJackson2HttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class); } private List> extractFormPartConverters(List> converters) {