From b60cc54b1e6a73708e124033e5485d7364bda368 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 8 Nov 2024 16:25:55 +0100 Subject: [PATCH 1/4] Update tests for 6.2 See gh-33861 --- .../expression/spel/ast/AstUtilsTests.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java index 174fdff9041e..d9b2c507ecbb 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java @@ -18,7 +18,6 @@ import java.util.List; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.expression.EvaluationContext; @@ -79,17 +78,10 @@ void singleExactTypeMatch() { @Test void exactTypeMatches() { List accessorsToTry = getPropertyAccessorsToTry(new Cat(), accessors); - // We would actually expect the following. - // assertThat(accessorsToTry).containsExactly( - // cat1Accessor, cat2Accessor, animal1Accessor, animal2Accessor, generic1Accessor, generic2Accessor); - // However, prior to Spring Framework 6.2, the supertype and generic accessors are not - // ordered properly. So we test that the exact matches come first and in the expected order. - assertThat(accessorsToTry) - .hasSize(accessors.size()) - .startsWith(cat1Accessor, cat2Accessor); + assertThat(accessorsToTry).containsExactly( + cat1Accessor, cat2Accessor, animal1Accessor, animal2Accessor, generic1Accessor, generic2Accessor); } - @Disabled("PropertyAccessor ordering for supertype and generic matches is broken prior to Spring Framework 6.2") @Test void supertypeMatches() { List accessorsToTry = getPropertyAccessorsToTry(new Dog(), accessors); @@ -109,7 +101,7 @@ private static PropertyAccessor createAccessor(String name, Class type) { } private static List getPropertyAccessorsToTry(Object target, List propertyAccessors) { - return AstUtils.getPropertyAccessorsToTry(target.getClass(), propertyAccessors); + return AstUtils.getAccessorsToTry(target, propertyAccessors); } From fb2afa685d8e81050f4c65b8692089cf530ec5de Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:07:15 +0100 Subject: [PATCH 2/4] Revise Javadoc for PropertyAccessor & IndexAccessor regarding ordering Closes gh-33862 --- .../expression/IndexAccessor.java | 15 ++++----------- .../expression/PropertyAccessor.java | 14 ++++---------- .../expression/TargetedAccessor.java | 6 ++++-- .../expression/spel/ast/AstUtils.java | 19 +++++++++++-------- 4 files changed, 23 insertions(+), 31 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/IndexAccessor.java b/spring-expression/src/main/java/org/springframework/expression/IndexAccessor.java index 5d066ac65d2d..1471ad48df35 100644 --- a/spring-expression/src/main/java/org/springframework/expression/IndexAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/IndexAccessor.java @@ -26,21 +26,14 @@ * structure. Implementors are therefore free to access indexed values any way * they deem appropriate. * - *

An index accessor can optionally specify an array of target classes for - * which it should be called. However, if it returns {@code null} or an empty - * array from {@link #getSpecificTargetClasses()}, it will be called for all - * indexing operations and given a chance to determine if it can read from or - * write to the indexed structure. - * - *

Index accessors are considered to be ordered, and each will be called in - * turn. The only rule that affects the call order is that any index accessor - * which specifies explicit support for the target class via - * {@link #getSpecificTargetClasses()} will be called first, before other - * generic index accessors. + *

An index accessor can specify an array of + * {@linkplain #getSpecificTargetClasses() target classes} for which it should be + * called. See {@link TargetedAccessor} for details. * * @author Jackmiking Lee * @author Sam Brannen * @since 6.2 + * @see TargetedAccessor * @see PropertyAccessor */ public interface IndexAccessor extends TargetedAccessor { diff --git a/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java index 0be829cddc6e..84e4fe5ac1dd 100644 --- a/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/PropertyAccessor.java @@ -26,19 +26,13 @@ * Implementors are therefore free to access properties directly via fields, * through getters, or in any other way they deem appropriate. * - *

A property accessor can optionally specify an array of target classes for - * which it should be called. However, if it returns {@code null} from - * {@link #getSpecificTargetClasses()}, it will be called for all property - * references and given a chance to determine if it can read or write them. - * - *

Property accessors are considered to be ordered, and each will be called in - * turn. The only rule that affects the call order is that any property accessor - * which specifies explicit support for the target class via - * {@link #getSpecificTargetClasses()} will be called first, before the generic - * property accessors. + *

A property accessor can specify an array of + * {@linkplain #getSpecificTargetClasses() target classes} for which it should be + * called. See {@link TargetedAccessor} for details. * * @author Andy Clement * @since 3.0 + * @see TargetedAccessor * @see IndexAccessor */ public interface PropertyAccessor extends TargetedAccessor { diff --git a/spring-expression/src/main/java/org/springframework/expression/TargetedAccessor.java b/spring-expression/src/main/java/org/springframework/expression/TargetedAccessor.java index 88b91055f5da..6403f9ca07ba 100644 --- a/spring-expression/src/main/java/org/springframework/expression/TargetedAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/TargetedAccessor.java @@ -31,9 +31,11 @@ * *

Targeted accessors are considered to be ordered, and each will be called * in turn. The only rule that affects the call order is that any accessor which - * specifies explicit support for a given target class via + * specifies explicit support for a given target type via * {@link #getSpecificTargetClasses()} will be called first, before other generic - * accessors that do not specify explicit support for the given target class. + * accessors that do not specify support for explicit target types. In addition, + * accessors that support the exact target type will be called before accessors + * that support a supertype of the target type. * * @author Sam Brannen * @since 6.2 diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java index e290102ceb57..4f1dc1e2f06d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java @@ -55,20 +55,23 @@ static List getAccessorsToTry( /** * Determine the set of accessors that should be used to try to access an * element on the specified target type. - *

The accessors are considered to be in an ordered list; however, in the - * returned list any accessors that are exact matches for the input target - * type (as opposed to 'generic' accessors that could work for any type) are - * placed at the start of the list. In addition, if there are specific - * accessors that exactly name the class in question and accessors that name - * a specific class which is a supertype of the class in question, the latter - * are put at the end of the specific accessors set and will be tried after - * exactly matching accessors but before generic accessors. + *

The supplied accessors are considered to be in an ordered list; however, + * in the returned list any accessors that are exact matches for the supplied + * target type are placed at the start of the list (as opposed to 'generic' + * accessors that could work for any target type). In addition, if there are + * accessors that claim support for the exact target type as well as accessors + * that claim support for a supertype of the target type, the latter are placed + * at the end of the specific accessors set and will be tried after exactly + * matching accessors but before generic accessors. + *

Only matching accessors and generic accessors will be included in the + * returned list. * @param targetType the type upon which element access is being attempted * @param accessors the list of element accessors to process * @return a list of accessors that should be tried in order to access the * element on the specified target type, or an empty list if no suitable * accessor could be found * @since 6.2 + * @see TargetedAccessor#getSpecificTargetClasses() */ static List getAccessorsToTry( @Nullable Class targetType, List accessors) { From ae62e33daee2efb01d43950e02e984c0ce1efb79 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:26:54 +0100 Subject: [PATCH 3/4] Polish SpEL internals --- .../ast/{AstUtils.java => AccessorUtils.java} | 4 +- .../expression/spel/ast/Indexer.java | 10 +- .../spel/ast/PropertyOrFieldReference.java | 6 +- .../spel/ast/AccessorUtilsTests.java | 125 ++++++++++++++ .../expression/spel/ast/AstUtilsTests.java | 162 ------------------ 5 files changed, 135 insertions(+), 172 deletions(-) rename spring-expression/src/main/java/org/springframework/expression/spel/ast/{AstUtils.java => AccessorUtils.java} (97%) create mode 100644 spring-expression/src/test/java/org/springframework/expression/spel/ast/AccessorUtilsTests.java delete mode 100644 spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AccessorUtils.java similarity index 97% rename from spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java rename to spring-expression/src/main/java/org/springframework/expression/spel/ast/AccessorUtils.java index 4f1dc1e2f06d..fcb03ced3b84 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/AstUtils.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/AccessorUtils.java @@ -25,13 +25,13 @@ import org.springframework.util.ObjectUtils; /** - * Utility methods for use in the AST classes. + * Utility methods for use with property and index accessors. * * @author Andy Clement * @author Sam Brannen * @since 3.0.2 */ -abstract class AstUtils { +abstract class AccessorUtils { /** * Determine the set of accessors that should be used to try to access an diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index cab438f04786..94cc5d37016d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -253,7 +253,7 @@ private ValueRef getValueRef(ExpressionState state, AccessMode accessMode) throw // Check for a custom IndexAccessor. EvaluationContext evalContext = state.getEvaluationContext(); List accessorsToTry = - AstUtils.getAccessorsToTry(target, evalContext.getIndexAccessors()); + AccessorUtils.getAccessorsToTry(target, evalContext.getIndexAccessors()); if (accessMode.supportsReads) { try { for (IndexAccessor indexAccessor : accessorsToTry) { @@ -754,7 +754,7 @@ public TypedValue getValue() { Indexer.this.cachedPropertyReadState = null; } List accessorsToTry = - AstUtils.getAccessorsToTry(targetType, this.evaluationContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(targetType, this.evaluationContext.getPropertyAccessors()); for (PropertyAccessor accessor : accessorsToTry) { if (accessor.canRead(this.evaluationContext, this.targetObject, this.name)) { if (accessor instanceof ReflectivePropertyAccessor reflectivePropertyAccessor) { @@ -797,7 +797,7 @@ public void setValue(@Nullable Object newValue) { Indexer.this.cachedPropertyWriteState = null; } List accessorsToTry = - AstUtils.getAccessorsToTry(targetType, this.evaluationContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(targetType, this.evaluationContext.getPropertyAccessors()); for (PropertyAccessor accessor : accessorsToTry) { if (accessor.canWrite(this.evaluationContext, this.targetObject, this.name)) { accessor.write(this.evaluationContext, this.targetObject, this.name, newValue); @@ -1014,7 +1014,7 @@ public TypedValue getValue() { Indexer.this.cachedIndexReadState = null; } List accessorsToTry = - AstUtils.getAccessorsToTry(this.target, this.evaluationContext.getIndexAccessors()); + AccessorUtils.getAccessorsToTry(this.target, this.evaluationContext.getIndexAccessors()); for (IndexAccessor indexAccessor : accessorsToTry) { if (indexAccessor.canRead(this.evaluationContext, this.target, this.index)) { TypedValue result = indexAccessor.read(this.evaluationContext, this.target, this.index); @@ -1069,7 +1069,7 @@ public void setValue(@Nullable Object newValue) { Indexer.this.cachedIndexWriteState = null; } List accessorsToTry = - AstUtils.getAccessorsToTry(this.target, this.evaluationContext.getIndexAccessors()); + AccessorUtils.getAccessorsToTry(this.target, this.evaluationContext.getIndexAccessors()); for (IndexAccessor indexAccessor : accessorsToTry) { if (indexAccessor.canWrite(this.evaluationContext, this.target, this.index)) { indexAccessor.write(this.evaluationContext, this.target, this.index, newValue); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index 5907e2dde01c..c40dc8a61dcf 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -201,7 +201,7 @@ private TypedValue readProperty(TypedValue contextObject, EvaluationContext eval } List accessorsToTry = - AstUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); // Go through the accessors that may be able to resolve it. If they are a cacheable accessor then // get the accessor and use it. If they are not cacheable but report they can read the property // then ask them to read it @@ -259,7 +259,7 @@ private void writeProperty( } List accessorsToTry = - AstUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); try { for (PropertyAccessor accessor : accessorsToTry) { if (accessor.canWrite(evalContext, targetObject, name)) { @@ -284,7 +284,7 @@ public boolean isWritableProperty(String name, TypedValue contextObject, Evaluat Object targetObject = contextObject.getValue(); if (targetObject != null) { List accessorsToTry = - AstUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); + AccessorUtils.getAccessorsToTry(targetObject, evalContext.getPropertyAccessors()); for (PropertyAccessor accessor : accessorsToTry) { try { if (accessor.canWrite(evalContext, targetObject, name)) { diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ast/AccessorUtilsTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ast/AccessorUtilsTests.java new file mode 100644 index 000000000000..659a99a87adf --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ast/AccessorUtilsTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-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. + * 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.expression.spel.ast; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.expression.TargetedAccessor; +import org.springframework.lang.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AccessorUtils}. + * + * @author Sam Brannen + * @since 6.1.15 + */ +class AccessorUtilsTests { + + private final TargetedAccessor animal1Accessor = createAccessor("Animal1", Animal.class); + + private final TargetedAccessor animal2Accessor = createAccessor("Animal2", Animal.class); + + private final TargetedAccessor cat1Accessor = createAccessor("Cat1", Cat.class); + + private final TargetedAccessor cat2Accessor = createAccessor("Cat2", Cat.class); + + private final TargetedAccessor generic1Accessor = createAccessor("Generic1", null); + + private final TargetedAccessor generic2Accessor = createAccessor("Generic2", null); + + private final List accessors = List.of( + generic1Accessor, + cat1Accessor, + animal1Accessor, + animal2Accessor, + cat2Accessor, + generic2Accessor + ); + + + @Test + void emptyAccessorsList() { + List accessorsToTry = AccessorUtils.getAccessorsToTry(new Cat(), List.of()); + assertThat(accessorsToTry).isEmpty(); + } + + @Test + void noMatch() { + List accessorsToTry = AccessorUtils.getAccessorsToTry(new Dog(), List.of(cat1Accessor)); + assertThat(accessorsToTry).isEmpty(); + } + + @Test + void singleExactTypeMatch() { + List accessorsToTry = AccessorUtils.getAccessorsToTry(new Cat(), List.of(cat1Accessor)); + assertThat(accessorsToTry).containsExactly(cat1Accessor); + } + + @Test + void exactTypeSupertypeAndGenericMatches() { + List accessorsToTry = AccessorUtils.getAccessorsToTry(new Cat(), accessors); + assertThat(accessorsToTry).containsExactly( + cat1Accessor, cat2Accessor, animal1Accessor, animal2Accessor, generic1Accessor, generic2Accessor); + } + + @Test + void supertypeAndGenericMatches() { + List accessorsToTry = AccessorUtils.getAccessorsToTry(new Dog(), accessors); + assertThat(accessorsToTry).containsExactly( + animal1Accessor, animal2Accessor, generic1Accessor, generic2Accessor); + } + + @Test + void genericMatches() { + List accessorsToTry = AccessorUtils.getAccessorsToTry("not an Animal", accessors); + assertThat(accessorsToTry).containsExactly(generic1Accessor, generic2Accessor); + } + + + private static TargetedAccessor createAccessor(String name, Class type) { + return new DemoAccessor(name, (type != null ? new Class[] { type } : null)); + } + + + private record DemoAccessor(String name, Class[] types) implements TargetedAccessor { + + @Override + @Nullable + public Class[] getSpecificTargetClasses() { + return this.types; + } + + @Override + public final String toString() { + return this.name; + } + } + + sealed interface Animal permits Cat, Dog { + } + + static final class Cat implements Animal { + } + + static final class Dog implements Animal { + } + +} diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java deleted file mode 100644 index d9b2c507ecbb..000000000000 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ast/AstUtilsTests.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2002-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. - * 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.expression.spel.ast; - -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.PropertyAccessor; -import org.springframework.expression.TypedValue; -import org.springframework.lang.Nullable; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link AstUtils}. - * - * @author Sam Brannen - * @since 6.1.15 - */ -class AstUtilsTests { - - private final PropertyAccessor animal1Accessor = createAccessor("Animal1", Animal.class); - - private final PropertyAccessor animal2Accessor = createAccessor("Animal2", Animal.class); - - private final PropertyAccessor cat1Accessor = createAccessor("Cat1", Cat.class); - - private final PropertyAccessor cat2Accessor = createAccessor("Cat2", Cat.class); - - private final PropertyAccessor generic1Accessor = createAccessor("Generic1", null); - - private final PropertyAccessor generic2Accessor = createAccessor("Generic2", null); - - private final List accessors = List.of( - generic1Accessor, - cat1Accessor, - animal1Accessor, - animal2Accessor, - cat2Accessor, - generic2Accessor - ); - - - @Test - void emptyAccessorsList() { - List accessorsToTry = getPropertyAccessorsToTry(new Cat(), List.of()); - assertThat(accessorsToTry).isEmpty(); - } - - @Test - void noMatch() { - List accessorsToTry = getPropertyAccessorsToTry(new Dog(), List.of(cat1Accessor)); - assertThat(accessorsToTry).isEmpty(); - } - - @Test - void singleExactTypeMatch() { - List accessorsToTry = getPropertyAccessorsToTry(new Cat(), List.of(cat1Accessor)); - assertThat(accessorsToTry).containsExactly(cat1Accessor); - } - - @Test - void exactTypeMatches() { - List accessorsToTry = getPropertyAccessorsToTry(new Cat(), accessors); - assertThat(accessorsToTry).containsExactly( - cat1Accessor, cat2Accessor, animal1Accessor, animal2Accessor, generic1Accessor, generic2Accessor); - } - - @Test - void supertypeMatches() { - List accessorsToTry = getPropertyAccessorsToTry(new Dog(), accessors); - assertThat(accessorsToTry).containsExactly( - animal1Accessor, animal2Accessor, generic1Accessor, generic2Accessor); - } - - @Test - void genericMatches() { - List accessorsToTry = getPropertyAccessorsToTry("not an Animal", accessors); - assertThat(accessorsToTry).containsExactly(generic1Accessor, generic2Accessor); - } - - - private static PropertyAccessor createAccessor(String name, Class type) { - return new DemoAccessor(name, type); - } - - private static List getPropertyAccessorsToTry(Object target, List propertyAccessors) { - return AstUtils.getAccessorsToTry(target, propertyAccessors); - } - - - private static class DemoAccessor implements PropertyAccessor { - - private final String name; - private final Class[] types; - - DemoAccessor(String name, Class type) { - this.name = name; - this.types = (type != null ? new Class[] {type} : null); - } - - @Override - @Nullable - public Class[] getSpecificTargetClasses() { - return this.types; - } - - @Override - public String toString() { - return this.name; - } - - @Override - public boolean canRead(EvaluationContext context, Object target, String name) { - return true; - } - - @Override - public TypedValue read(EvaluationContext context, Object target, String name) { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - @Override - public boolean canWrite(EvaluationContext context, Object target, String name) { - return false; - } - - @Override - public void write(EvaluationContext context, Object target, String name, Object newValue) { - /* no-op */ - } - } - - sealed interface Animal permits Bat, Cat, Dog { - } - - static final class Bat implements Animal { - } - - static final class Cat implements Animal { - } - - static final class Dog implements Animal { - } - -} From 039c64b5c2cf744440d9284f0b8476165f6e57b9 Mon Sep 17 00:00:00 2001 From: lucky8987 Date: Fri, 6 Dec 2024 00:42:16 +0800 Subject: [PATCH 4/4] Add get functions to ExecutorFigurationSupport --- .../ExecutorConfigurationSupport.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java index 99f22d71a09c..a16649aa1678 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java @@ -537,4 +537,22 @@ protected void initiateEarlyShutdown() { initiateShutdown(); } + /** + * Whether this executor is using virtual threads. + * @return {@code true} if using virtual threads + * @since 6.2.1 + */ + public boolean isVirtualThreads() { + return this.virtualThreads; + } + + /** + * Executor beanName. + * @return the executor beanName + * @since 6.2.1 + */ + @Nullable + public String getBeanName() { + return this.beanName; + } }