From 37c8f41633d9274cf4bdba631ad0472380858994 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:08:15 +0200 Subject: [PATCH] Introduce a builder for SpelParserConfiguration Prior to this commit, SpelParserConfiguration exposed 8 overloaded constructors that accumulated over time as new configuration options were introduced (auto-grow support since 3.0, maximumExpressionLength in 5.2.25, maximumOperations in 6.2.19, and maximumBigPowerBits in 7.0.9), culminating in an 8-parameter constructor. This made call sites hard to read due to unlabeled sequences of booleans and ints, and it forced users who wanted to override a single setting to also supply every other value explicitly. To address that, this commit introduces a builder API in SpelParserConfiguration, following the pattern already established by SimpleEvaluationContext's builder API. Specifically, SpelParserConfiguration.builder() returns a Builder that is pre-populated with the same defaults as the no-arg constructor, including the SpringProperties-driven overrides for the default compiler mode, maximum operations, and maximum big-power bits -- the latter two are only resolved lazily in build(), so that overriding them via the builder never triggers an unnecessary SpringProperties lookup. Each property has a dedicated, named setter (compilerMode(), compilerClassLoader(), maximumAutoGrowSize(), maximumExpressionLength(), maximumOperations(), maximumBigPowerBits()), and the two auto-grow flags are exposed as simple no-arg opt-ins (autoGrowNullReferences(), autoGrowCollections()) since they both default to false. build() delegates to the existing canonical constructor, so validation and defaults remain centralized in one place. In addition, a new SpelParserConfiguration.withDefaults() factory method has been introduced as shorthand for SpelParserConfiguration.builder().build(), for the common case where none of the builder's defaults need to be overridden. As the one deliberate exception to matching the no-arg constructor's defaults, the builder defaults maximumAutoGrowSize to 256 -- aligned with DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT -- rather than the constructors' Integer.MAX_VALUE. The constructors keep their legacy default for backward compatibility, but the builder is a new, opt-in API that is not bound by that compatibility contract. This change is purely additive: none of the existing constructors have been modified or deprecated. Deprecating those constructors in favor of the builder is being deferred to 7.1, since new deprecations should not be introduced in a patch release. In the meantime, the Javadoc for the constructors and for the SPRING_EXPRESSION_*_PROPERTY_NAME constants has been updated to favor the builder (or a specific Builder setter) instead of the constructors, and the class-level Javadoc now states that the constructors are planned to be deprecated in favor of the builder as of Spring Framework 7.1. SpelExpressionParser's no-arg constructor, ExpressionState's two convenience constructors, and StandardBeanExpressionResolver's ClassLoader-based constructor have all been switched from the SpelParserConfiguration constructors to the builder (or withDefaults()). This is behaviorally identical in every case: autoGrowCollections remains false at each of those call sites, and maximumAutoGrowSize -- the only property whose default differs between the constructors and the builder -- has no effect when autoGrowCollections is false. Tests have been added in a new SpelParserConfigurationTests class to verify that the builder's defaults match the no-arg constructor (with the one intentional maximumAutoGrowSize exception called out above), that custom values are applied correctly, and that invalid values are rejected. The nested LegacyConstructorTests class provides regression coverage for each of the legacy constructors, consolidating their usage in tests to a single class -- which will keep any future deprecation warnings confined to this class -- and documents that, unlike the builder, the canonical constructor does not (yet) reject a negative maximumAutoGrowSize. The remaining incidental usages of the SpelParserConfiguration constructors throughout EvaluationTests, IndexingTests, SpelCompilationCoverageTests, SpelReproTests, and SpelCompilerTests have been converted to use the builder. Furthermore, the reference documentation has been updated to recommend the builder and withDefaults() over the constructors, both in prose and in the Java/Kotlin examples. Closes gh-37187 --- .../pages/core/expressions/evaluation.adoc | 88 +++--- .../StandardBeanExpressionResolver.java | 6 +- .../expression/spel/ExpressionState.java | 4 +- .../spel/SpelParserConfiguration.java | 277 +++++++++++++++--- .../spel/standard/SpelExpressionParser.java | 2 +- .../expression/spel/EvaluationTests.java | 96 ++++-- .../expression/spel/IndexingTests.java | 21 +- .../spel/SpelCompilationCoverageTests.java | 40 ++- .../spel/SpelParserConfigurationTests.java | 243 +++++++++++++++ .../expression/spel/SpelReproTests.java | 8 +- .../spel/standard/SpelCompilerTests.java | 16 +- 11 files changed, 655 insertions(+), 146 deletions(-) create mode 100644 spring-expression/src/test/java/org/springframework/expression/spel/SpelParserConfigurationTests.java diff --git a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc index 45997fd2846..1db4a58bf3e 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc @@ -488,17 +488,23 @@ Kotlin:: It is possible to configure the SpEL expression parser by using a parser configuration object (`org.springframework.expression.spel.SpelParserConfiguration`). The configuration -object controls the behavior of some of the expression components. For example, if you -index into a collection and the element at the specified index is `null`, SpEL can -automatically create the element. This is useful when using expressions made up of a -chain of property references. Similarly, if you index into a collection and specify an -index that is greater than the current size of the collection, SpEL can automatically -grow the collection to accommodate that index. In order to add an element at the -specified index, SpEL will try to create the element using the element type's default -constructor before setting the specified value. If the element type does not have a -default constructor, `null` will be added to the collection. If there is no built-in -converter or custom converter that knows how to set the value, `null` will remain in the -collection at the specified index. The following example demonstrates how to +object controls the behavior of some of the expression components. To create a +`SpelParserConfiguration` instance, favor `SpelParserConfiguration.builder()` over the +numerous constructors in `SpelParserConfiguration`, since the builder only requires +configuration of the properties that need to deviate from their sensible defaults -- +or use `SpelParserConfiguration.withDefaults()` if none of those defaults need to be +overridden. + +For example, if you index into a collection and the element at the specified index is +`null`, SpEL can automatically create the element. This is useful when using expressions +made up of a chain of property references. Similarly, if you index into a collection and +specify an index that is greater than the current size of the collection, SpEL can +automatically grow the collection to accommodate that index. In order to add an element +at the specified index, SpEL will try to create the element using the element type's +default constructor before setting the specified value. If the element type does not +have a default constructor, `null` will be added to the collection. If there is no +built-in converter or custom converter that knows how to set the value, `null` will +remain in the collection at the specified index. The following example demonstrates how to automatically grow a `List`. [tabs] @@ -511,10 +517,10 @@ Java:: public List list; } - // Turn on: - // - auto null reference initialization - // - auto collection growing - SpelParserConfiguration config = new SpelParserConfiguration(true, true); + SpelParserConfiguration config = SpelParserConfiguration.builder() + .autoGrowNullReferences() + .autoGrowCollections() + .build(); ExpressionParser parser = new SpelExpressionParser(config); @@ -536,10 +542,10 @@ Kotlin:: var list: List? = null } - // Turn on: - // - auto null reference initialization - // - auto collection growing - val config = SpelParserConfiguration(true, true) + val config = SpelParserConfiguration.builder() + .autoGrowNullReferences() + .autoGrowCollections() + .build() val parser = SpelExpressionParser(config) @@ -556,7 +562,8 @@ Kotlin:: By default, a SpEL expression cannot contain more than 10,000 characters; however, the `maxExpressionLength` is configurable. If you create a `SpelExpressionParser` -programmatically, you can specify a custom `maxExpressionLength` when creating the +programmatically, you can specify a custom `maxExpressionLength` via +`SpelParserConfiguration.builder().maximumExpressionLength(...)` when creating the `SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you wish to set the `maxExpressionLength` used for parsing SpEL expressions within an `ApplicationContext` -- for example, in XML bean definitions, `@Value`, etc. -- you can @@ -567,12 +574,14 @@ xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]). Similarly, the number of operations performed during the evaluation of a SpEL expression cannot exceed 10,000 by default; however, the `maxOperations` value is configurable. If you create a `SpelExpressionParser` programmatically (the recommend approach), you can -specify a custom `maxOperations` value when creating the `SpelParserConfiguration` that -you provide to the `SpelExpressionParser`. If you are not able to configure an explicit -value for `maxOperations` via `SpelParserConfiguration`, you can set a JVM system -property or Spring property named `spring.expression.maxOperations` to the maximum number -of operations required by your application (see -xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]). +specify a custom `maxOperations` value via +`SpelParserConfiguration.builder().maximumOperations(...)` when creating the +`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you are not +able to configure an explicit value for `maxOperations` via `SpelParserConfiguration`, +you can set a JVM system property or Spring property named +`spring.expression.maxOperations` to the maximum number of operations required by your +application (see xref:appendix.adoc#appendix-spring-properties[Supported Spring +Properties]). In addition, the result of a `BigDecimal` or `BigInteger` power operation within a SpEL expression cannot exceed 1,000,000 bits by default – approximately equivalent to a @@ -580,13 +589,14 @@ decimal number with 300,000 digits. Power operations involving large base values exponents can be computationally expensive, and this limit ensures that evaluations remain bounded; however, the `maximumBigPowerBits` value is configurable. If you create a `SpelExpressionParser` programmatically (the recommended approach), you can specify a -custom `maximumBigPowerBits` value when creating the `SpelParserConfiguration` that you -provide to the `SpelExpressionParser`. To remove this limit entirely, pass -`Integer.MAX_VALUE` as the `maximumBigPowerBits` value. If you are not able to configure -an explicit value for `maximumBigPowerBits` via `SpelParserConfiguration`, you can set a -JVM system property or Spring property named `spring.expression.maxBigPowerBits` to the -maximum result size in bits (see xref:appendix.adoc#appendix-spring-properties[Supported -Spring Properties]). +custom `maximumBigPowerBits` value via +`SpelParserConfiguration.builder().maximumBigPowerBits(...)` when creating the +`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. To remove this +limit entirely, pass `Integer.MAX_VALUE` as the `maximumBigPowerBits` value. If you are +not able to configure an explicit value for `maximumBigPowerBits` via +`SpelParserConfiguration`, you can set a JVM system property or Spring property named +`spring.expression.maxBigPowerBits` to the maximum result size in bits (see +xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]). [[expressions-spel-compilation]] == SpEL Compilation @@ -669,8 +679,10 @@ Java:: + [source,java,indent=0,subs="verbatim,quotes"] ---- - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, - this.getClass().getClassLoader()); + SpelParserConfiguration config = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.IMMEDIATE) + .compilerClassLoader(getClass().getClassLoader()) + .build(); SpelExpressionParser parser = new SpelExpressionParser(config); @@ -685,8 +697,10 @@ Kotlin:: + [source,kotlin,indent=0,subs="verbatim,quotes"] ---- - val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, - this.javaClass.classLoader) + val config = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.IMMEDIATE) + .compilerClassLoader(javaClass.classLoader) + .build() val parser = SpelExpressionParser(config) diff --git a/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java b/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java index 798d954e577..ea98cd2ad28 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java +++ b/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java @@ -118,8 +118,10 @@ public class StandardBeanExpressionResolver implements BeanExpressionResolver { * @param beanClassLoader the factory's bean class loader */ public StandardBeanExpressionResolver(@Nullable ClassLoader beanClassLoader) { - SpelParserConfiguration parserConfig = new SpelParserConfiguration( - null, beanClassLoader, false, false, Integer.MAX_VALUE, retrieveMaxExpressionLength()); + SpelParserConfiguration parserConfig = SpelParserConfiguration.builder() + .compilerClassLoader(beanClassLoader) + .maximumExpressionLength(retrieveMaxExpressionLength()) + .build(); this.expressionParser = new SpelExpressionParser(parserConfig); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java index b3669fd4fa2..849f259b694 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -75,7 +75,7 @@ public class ExpressionState { public ExpressionState(EvaluationContext context) { - this(context, context.getRootObject(), new SpelParserConfiguration(false, false)); + this(context, context.getRootObject(), SpelParserConfiguration.withDefaults()); } public ExpressionState(EvaluationContext context, SpelParserConfiguration configuration) { @@ -83,7 +83,7 @@ public class ExpressionState { } public ExpressionState(EvaluationContext context, TypedValue rootObject) { - this(context, rootObject, new SpelParserConfiguration(false, false)); + this(context, rootObject, SpelParserConfiguration.withDefaults()); } public ExpressionState(EvaluationContext context, TypedValue rootObject, SpelParserConfiguration configuration) { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java index 73e218ec5f8..e868c316e77 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java @@ -27,12 +27,22 @@ import org.springframework.util.StringUtils; /** * Configuration object for the SpEL expression parser. * + *

Rather than using one of the numerous constructors in this class, it is + * strongly recommended that you use the {@linkplain #builder() builder API} to + * configure and create a {@code SpelParserConfiguration} instance, since the + * builder only requires configuration of the properties that need to deviate from + * their sensible defaults — or use {@link #withDefaults()} if none of those + * defaults need to be overridden. Note that the constructors in this class are + * planned to be deprecated in favor of the builder as of Spring Framework 7.1. + * * @author Juergen Hoeller * @author Phillip Webb * @author Andy Clement * @author Sam Brannen * @since 3.0 * @see org.springframework.expression.spel.standard.SpelExpressionParser#SpelExpressionParser(SpelParserConfiguration) + * @see #withDefaults() + * @see #builder() */ public class SpelParserConfiguration { @@ -62,10 +72,10 @@ public class SpelParserConfiguration { /** * System property to configure the default compiler mode for SpEL expression parsers: {@value}. *

NOTE: Instead of relying on a global default, applications - * and frameworks should ideally set an explicit custom value via the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)} - * constructor which provides complete configuration control and the ability - * to override global defaults per use case. + * and frameworks should ideally set an explicit custom value via + * {@link Builder#compilerMode(SpelCompilerMode)}, which provides complete + * configuration control and the ability to override global defaults per + * use case. *

Can also be configured via the {@link SpringProperties} mechanism. */ public static final String SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME = "spring.expression.compiler.mode"; @@ -74,10 +84,10 @@ public class SpelParserConfiguration { * System property to configure the default maximum number of operations permitted * during SpEL expression evaluation: {@value}. *

NOTE: Instead of relying on a global default, applications - * and frameworks should ideally set an explicit custom value via the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor which provides complete configuration control and the ability - * to override global defaults per use case. + * and frameworks should ideally set an explicit custom value via + * {@link Builder#maximumOperations(int)}, which provides complete + * configuration control and the ability to override global defaults per + * use case. *

Can also be configured via the {@link SpringProperties} mechanism. * @since 6.2.19 * @see #DEFAULT_MAX_OPERATIONS @@ -89,10 +99,10 @@ public class SpelParserConfiguration { * result of a {@link java.math.BigDecimal} or {@link java.math.BigInteger} power * operation within a SpEL expression: {@value}. *

NOTE: Instead of relying on a global default, applications - * and frameworks should ideally set an explicit custom value via the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor which provides complete configuration control and the ability - * to override global defaults per use case. + * and frameworks should ideally set an explicit custom value via + * {@link Builder#maximumBigPowerBits(int)}, which provides complete + * configuration control and the ability to override global defaults per + * use case. *

Can also be configured via the {@link SpringProperties} mechanism. * @since 7.0.9 * @see #DEFAULT_MAX_BIG_POWER_BITS @@ -127,12 +137,35 @@ public class SpelParserConfiguration { private final int maximumBigPowerBits; + /** + * Create a new {@code SpelParserConfiguration} instance with the same defaults + * applied by {@link #builder()}. + *

This is shorthand for {@code SpelParserConfiguration.builder().build()}, + * for use whenever none of the defaults need to be overridden. + * @since 7.0.10 + * @see #builder() + */ + public static SpelParserConfiguration withDefaults() { + return builder().build(); + } + + /** + * Create a new {@link Builder} for configuring a {@code SpelParserConfiguration}. + *

The builder only requires configuration of the properties that need + * to deviate from their sensible defaults. See {@link Builder} for details + * on those defaults. + * @since 7.0.10 + * @see #withDefaults() + */ + public static Builder builder() { + return new Builder(); + } + /** * Create a new {@code SpelParserConfiguration} instance with default settings. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME @@ -143,10 +176,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param compilerMode the compiler mode that parsers using this configuration * should use; or {@code null} to use the default mode * @param compilerClassLoader the {@code ClassLoader} to use as the basis for @@ -161,10 +193,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME @@ -177,10 +208,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow * @param maximumAutoGrowSize the maximum size to which a collection can auto grow @@ -194,10 +224,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param compilerMode the compiler mode that parsers using this configuration * should use; or {@code null} to use the default mode * @param compilerClassLoader the {@code ClassLoader} to use as the basis for @@ -218,10 +247,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param compilerMode the compiler mode that parsers using this configuration * should use; or {@code null} to use the default mode * @param compilerClassLoader the {@code ClassLoader} to use as the basis for @@ -245,10 +273,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param compilerMode the compiler mode that parsers using this configuration * should use; must not be {@code null} * @param compilerClassLoader the {@code ClassLoader} to use as the basis for @@ -273,6 +300,8 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. + *

NOTE: Favor {@link #builder()} for a more readable + * way to override global defaults per use case. * @param compilerMode the compiler mode that parsers using this configuration * should use; must not be {@code null} * @param compilerClassLoader the {@code ClassLoader} to use as the basis for @@ -406,4 +435,172 @@ public class SpelParserConfiguration { } } + + /** + * Fluent builder API for {@link SpelParserConfiguration}. + *

Each property defaults to the same value as the corresponding + * property in a {@code SpelParserConfiguration} created via the + * {@linkplain SpelParserConfiguration#SpelParserConfiguration() no-arg constructor} + * — including honoring the system properties or Spring properties named + * {@value #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME}, + * {@value #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME}, and + * {@value #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME} — with + * one exception: {@link #maximumAutoGrowSize(int) maximumAutoGrowSize} + * defaults to {@code 256} rather than {@link Integer#MAX_VALUE}, for + * consistency with the default auto-grow limit used for Spring's data + * binding support (see {@code DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT}). + *

Only configure the properties that need to deviate from those defaults. + * @since 7.0.10 + * @see SpelParserConfiguration#builder() + */ + public static final class Builder { + + // Aligned with DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT, unlike the + // constructors in the enclosing class, which default to Integer.MAX_VALUE + // for backward compatibility. + private static final int DEFAULT_MAX_AUTO_GROW_SIZE = 256; + + private SpelCompilerMode compilerMode = defaultCompilerMode; + + private @Nullable ClassLoader compilerClassLoader; + + private boolean autoGrowNullReferences = false; + + private boolean autoGrowCollections = false; + + private int maximumAutoGrowSize = DEFAULT_MAX_AUTO_GROW_SIZE; + + private int maximumExpressionLength = DEFAULT_MAX_EXPRESSION_LENGTH; + + private @Nullable Integer maximumOperations; + + private @Nullable Integer maximumBigPowerBits; + + private Builder() { + } + + /** + * Set the compiler mode that parsers using this configuration should use. + *

By default, set to the value configured via {@link SpringProperties} + * for a system property or Spring property named + * {@value #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME}, or + * {@link SpelCompilerMode#OFF} if that property is not set. + * @param compilerMode the compiler mode to use; must not be {@code null} + */ + public Builder compilerMode(SpelCompilerMode compilerMode) { + Assert.notNull(compilerMode, "'compilerMode' must not be null"); + this.compilerMode = compilerMode; + return this; + } + + /** + * Set the {@code ClassLoader} to use as the basis for expression compilation. + *

By default, set to {@code null}, indicating that the default + * {@code ClassLoader} should be used. + * @param compilerClassLoader the {@code ClassLoader} to use + */ + public Builder compilerClassLoader(@Nullable ClassLoader compilerClassLoader) { + this.compilerClassLoader = compilerClassLoader; + return this; + } + + /** + * Enable automatic growth of {@code null} references encountered while + * traversing a property path. + *

By default, this is disabled. + */ + public Builder autoGrowNullReferences() { + this.autoGrowNullReferences = true; + return this; + } + + /** + * Enable automatic growth of collections and arrays encountered while + * traversing a property path. + *

By default, this is disabled. + * @see #maximumAutoGrowSize(int) + */ + public Builder autoGrowCollections() { + this.autoGrowCollections = true; + return this; + } + + /** + * Set the maximum size to which a collection or array can automatically grow. + *

By default, set to {@code 256}, for consistency with the default + * auto-grow limit used for Spring's data binding support (see + * {@code DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT}) — unlike + * the constructors in {@link SpelParserConfiguration}, which default to + * {@link Integer#MAX_VALUE} (effectively unbounded) for backward + * compatibility. + * @param maximumAutoGrowSize the maximum auto-grow size; must not be + * negative, and a value of {@code 0} effectively disables growing a + * collection or array beyond its current size + * @see #autoGrowCollections() + */ + public Builder maximumAutoGrowSize(int maximumAutoGrowSize) { + Assert.isTrue(maximumAutoGrowSize >= 0, "'maximumAutoGrowSize' must not be negative"); + this.maximumAutoGrowSize = maximumAutoGrowSize; + return this; + } + + /** + * Set the maximum length permitted for a SpEL expression. + *

By default, set to {@link #DEFAULT_MAX_EXPRESSION_LENGTH}. + * @param maximumExpressionLength the maximum expression length; must be + * a positive number + */ + public Builder maximumExpressionLength(int maximumExpressionLength) { + Assert.isTrue(maximumExpressionLength > 0, "'maximumExpressionLength' must be a positive number"); + this.maximumExpressionLength = maximumExpressionLength; + return this; + } + + /** + * Set the maximum number of operations permitted during SpEL expression evaluation. + *

By default, set to the value configured via {@link SpringProperties} + * for a system property or Spring property named + * {@value #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME}, or + * {@link #DEFAULT_MAX_OPERATIONS} if that property is not set. + * @param maximumOperations the maximum number of operations; must be a + * positive number + */ + public Builder maximumOperations(int maximumOperations) { + Assert.isTrue(maximumOperations > 0, "'maximumOperations' must be a positive number"); + this.maximumOperations = maximumOperations; + return this; + } + + /** + * Set the maximum number of bits permitted in the result of a + * {@link java.math.BigDecimal} or {@link java.math.BigInteger} power + * operation within a SpEL expression. + *

By default, set to the value configured via {@link SpringProperties} + * for a system property or Spring property named + * {@value #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME}, or + * {@link #DEFAULT_MAX_BIG_POWER_BITS} if that property is not set. + * @param maximumBigPowerBits the maximum number of bits; must be a + * positive number; use {@link Integer#MAX_VALUE} for no limit + */ + public Builder maximumBigPowerBits(int maximumBigPowerBits) { + Assert.isTrue(maximumBigPowerBits > 0, "'maximumBigPowerBits' must be a positive number"); + this.maximumBigPowerBits = maximumBigPowerBits; + return this; + } + + /** + * Build the {@link SpelParserConfiguration} configured via this builder. + */ + public SpelParserConfiguration build() { + int maximumOperations = (this.maximumOperations != null ? + this.maximumOperations : retrieveMaxOperations()); + int maximumBigPowerBits = (this.maximumBigPowerBits != null ? + this.maximumBigPowerBits : retrieveMaxBigPowerBits()); + return new SpelParserConfiguration(this.compilerMode, this.compilerClassLoader, + this.autoGrowNullReferences, this.autoGrowCollections, this.maximumAutoGrowSize, + this.maximumExpressionLength, maximumOperations, maximumBigPowerBits); + } + + } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java index 7322f20ef99..0e1950a3323 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java @@ -41,7 +41,7 @@ public class SpelExpressionParser extends TemplateAwareExpressionParser { * Create a parser with default settings. */ public SpelExpressionParser() { - this.configuration = new SpelParserConfiguration(); + this.configuration = SpelParserConfiguration.withDefaults(); } /** diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index 494cd7856ac..5d44810cd8b 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -86,8 +86,9 @@ class EvaluationTests extends AbstractExpressionTests { String expression = "'%s'".formatted("Y".repeat(19_998)); assertThat(expression).hasSize(maximumExpressionLength); - SpelParserConfiguration configuration = - new SpelParserConfiguration(null, null, false, false, 0, maximumExpressionLength); + SpelParserConfiguration configuration = SpelParserConfiguration.builder() + .maximumExpressionLength(maximumExpressionLength) + .build(); ExpressionParser parser = new SpelExpressionParser(configuration); Expression expr = parser.parseExpression(expression); @@ -111,8 +112,11 @@ class EvaluationTests extends AbstractExpressionTests { Expression expr1 = parser.parseExpression(expression); assertThat(expr1.getValue(Boolean.class)).isTrue(); - SpelParserConfiguration configuration = - new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, maxLength, maxOperations); + SpelParserConfiguration configuration = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .maximumExpressionLength(maxLength) + .maximumOperations(maxOperations) + .build(); parser = new SpelExpressionParser(configuration); Expression expr2 = parser.parseExpression(expression); assertThatExceptionOfType(SpelEvaluationException.class) @@ -167,13 +171,20 @@ class EvaluationTests extends AbstractExpressionTests { SpringProperties.setProperty(SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME, "" + maxOperations / 2); // maxOperations + 1 should override maxOperations / 2 - SpelParserConfiguration configuration = - new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, maxLength, maxOperations + 1); + SpelParserConfiguration configuration = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .maximumExpressionLength(maxLength) + .maximumOperations(maxOperations + 1) + .build(); parser = new SpelExpressionParser(configuration); expr1 = parser.parseExpression(expression); assertThat(expr1.getValue(Boolean.class)).isTrue(); - configuration = new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, maxLength, maxOperations); + configuration = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .maximumExpressionLength(maxLength) + .maximumOperations(maxOperations) + .build(); parser = new SpelExpressionParser(configuration); Expression expr2 = parser.parseExpression(expression); assertThatExceptionOfType(SpelEvaluationException.class) @@ -191,7 +202,8 @@ class EvaluationTests extends AbstractExpressionTests { @Test void createListsOnAttemptToIndexNull01() throws EvaluationException, ParseException { - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e = parser.parseExpression("list[0]"); TestClass testClass = new TestClass(); @@ -213,7 +225,8 @@ class EvaluationTests extends AbstractExpressionTests { void createMapsOnAttemptToIndexNull() { TestClass testClass = new TestClass(); StandardEvaluationContext ctx = new StandardEvaluationContext(testClass); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Object o = parser.parseExpression("map['a']").getValue(ctx); assertThat(o).isNull(); @@ -230,7 +243,8 @@ class EvaluationTests extends AbstractExpressionTests { void createObjectsOnAttemptToReferenceNull() { TestClass testClass = new TestClass(); StandardEvaluationContext ctx = new StandardEvaluationContext(testClass); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Object o = parser.parseExpression("wibble.bar").getValue(ctx); assertThat(o).isEqualTo("hello"); @@ -385,7 +399,7 @@ class EvaluationTests extends AbstractExpressionTests { void initializingCollectionElementsOnWrite() { TestPerson person = new TestPerson(); EvaluationContext context = new StandardEvaluationContext(person); - SpelParserConfiguration config = new SpelParserConfiguration(true, true); + SpelParserConfiguration config = SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build(); ExpressionParser parser = new SpelExpressionParser(config); Expression e = parser.parseExpression("name"); e.setValue(context, "Oleg"); @@ -452,7 +466,8 @@ class EvaluationTests extends AbstractExpressionTests { // Add a new element to the list StandardEvaluationContext ctx = new StandardEvaluationContext(instance); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e = parser.parseExpression("listOfStrings[++index3]='def'"); e.getValue(ctx); assertThat(instance.listOfStrings).hasSize(2); @@ -460,7 +475,8 @@ class EvaluationTests extends AbstractExpressionTests { // Check reference beyond end of collection ctx = new StandardEvaluationContext(instance); - parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); e = parser.parseExpression("listOfStrings[0]"); String value = e.getValue(ctx, String.class); assertThat(value).isEqualTo("abc"); @@ -473,7 +489,7 @@ class EvaluationTests extends AbstractExpressionTests { // Now turn off growing and reference off the end StandardEvaluationContext failCtx = new StandardEvaluationContext(instance); - parser = new SpelExpressionParser(new SpelParserConfiguration(false, false)); + parser = new SpelExpressionParser(SpelParserConfiguration.withDefaults()); Expression failExp = parser.parseExpression("listOfStrings[3]"); assertThatExceptionOfType(SpelEvaluationException.class) .isThrownBy(() -> failExp.getValue(failCtx, String.class)) @@ -484,7 +500,12 @@ class EvaluationTests extends AbstractExpressionTests { void limitCollectionGrowing() { TestClass instance = new TestClass(); StandardEvaluationContext ctx = new StandardEvaluationContext(instance); - SpelExpressionParser parser = new SpelExpressionParser( new SpelParserConfiguration(true, true, 3)); + SpelParserConfiguration config = SpelParserConfiguration.builder() + .autoGrowNullReferences() + .autoGrowCollections() + .maximumAutoGrowSize(3) + .build(); + SpelExpressionParser parser = new SpelExpressionParser(config); Expression e = parser.parseExpression("foo[2]"); e.setValue(ctx, "2"); assertThat(instance.getFoo()).hasSize(3); @@ -819,9 +840,12 @@ class EvaluationTests extends AbstractExpressionTests { // Use a small limit (16 bits) to verify behavior in tests. private static final int TEST_MAX_RESULT_BITS = 16; - private final SpelExpressionParser limitedParser = new SpelExpressionParser( - new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, - 0, 10, 10, TEST_MAX_RESULT_BITS)); + private final SpelExpressionParser limitedParser = new SpelExpressionParser(SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .maximumExpressionLength(10) + .maximumOperations(10) + .maximumBigPowerBits(TEST_MAX_RESULT_BITS) + .build()); @Test @@ -1016,7 +1040,8 @@ class EvaluationTests extends AbstractExpressionTests { void increment01root() { Integer i = 42; StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e = parser.parseExpression("#this++"); assertThat(i).isEqualTo(42); assertThatExceptionOfType(SpelEvaluationException.class) @@ -1028,7 +1053,8 @@ class EvaluationTests extends AbstractExpressionTests { void increment02postfix() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // BigDecimal @@ -1081,7 +1107,8 @@ class EvaluationTests extends AbstractExpressionTests { void increment02prefix() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // BigDecimal @@ -1134,7 +1161,8 @@ class EvaluationTests extends AbstractExpressionTests { void increment03() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e1 = parser.parseExpression("m()++"); assertThatExceptionOfType(SpelEvaluationException.class) @@ -1151,7 +1179,8 @@ class EvaluationTests extends AbstractExpressionTests { void increment04() { Integer i = 42; StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e1 = parser.parseExpression("++1"); assertThatExceptionOfType(SpelEvaluationException.class) .isThrownBy(() -> e1.getValue(ctx, double.class)) @@ -1166,7 +1195,8 @@ class EvaluationTests extends AbstractExpressionTests { void decrement01root() { Integer i = 42; StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e = parser.parseExpression("#this--"); assertThat(i).isEqualTo(42); assertThatExceptionOfType(SpelEvaluationException.class) @@ -1178,7 +1208,8 @@ class EvaluationTests extends AbstractExpressionTests { void decrement02postfix() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // BigDecimal @@ -1231,7 +1262,8 @@ class EvaluationTests extends AbstractExpressionTests { void decrement02prefix() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // BigDecimal @@ -1284,7 +1316,8 @@ class EvaluationTests extends AbstractExpressionTests { void decrement03() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e1 = parser.parseExpression("m()--"); assertThatExceptionOfType(SpelEvaluationException.class) @@ -1301,7 +1334,8 @@ class EvaluationTests extends AbstractExpressionTests { void decrement04() { Integer i = 42; StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e1 = parser.parseExpression("--1"); assertThatExceptionOfType(SpelEvaluationException.class) .isThrownBy(() -> e1.getValue(ctx, Integer.class)) @@ -1317,7 +1351,8 @@ class EvaluationTests extends AbstractExpressionTests { void incrementAndDecrementTogether() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // index1 is 2 at the start - the 'intArray[#root.index1++]' should not be evaluated twice! @@ -1345,7 +1380,8 @@ class EvaluationTests extends AbstractExpressionTests { void incrementAllNodeTypes() throws SecurityException, NoSuchMethodException { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // BooleanLiteral diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java index 618e3450506..b4e503b4d37 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java @@ -159,7 +159,8 @@ class IndexingTests { @Test void setPropertyContainingMapAutoGrow() { - SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, false)); + SpelExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().build()); Expression expression = parser.parseExpression("parameterizedMap"); assertThat(expression.getValueTypeDescriptor(this)).asString() .isEqualTo("java.util.Map"); @@ -204,7 +205,8 @@ class IndexingTests { void setGenericPropertyContainingListAutogrow() { List property = new ArrayList<>(); this.property = property; - SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + SpelExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression expression = parser.parseExpression("property"); assertThat(expression.getValueTypeDescriptor(this)).asString() .isEqualTo("@%s java.util.ArrayList", FieldAnnotation.class.getCanonicalName()); @@ -219,7 +221,8 @@ class IndexingTests { @Test void autoGrowListOfElementsWithoutDefaultConstructor() { this.decimals = new ArrayList<>(); - SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + SpelExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); parser.parseExpression("decimals[0]").setValue(this, "123.4"); assertThat(decimals).containsExactly(BigDecimal.valueOf(123.4)); } @@ -229,7 +232,8 @@ class IndexingTests { this.decimals = new ArrayList<>(); this.decimals.add(null); this.decimals.add(BigDecimal.ONE); - SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + SpelExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); parser.parseExpression("decimals[0]").setValue(this, "9876.5"); assertThat(decimals).containsExactly(BigDecimal.valueOf(9876.5), BigDecimal.ONE); } @@ -280,7 +284,8 @@ class IndexingTests { @Test void indexIntoGenericPropertyContainingNullList() { - SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); + SpelParserConfiguration configuration = + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build(); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("property"); assertThat(expression.getValueTypeDescriptor(this)).asString() @@ -297,7 +302,8 @@ class IndexingTests { void indexIntoGenericPropertyContainingGrowingList() { List property = new ArrayList<>(); this.property = property; - SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); + SpelParserConfiguration configuration = + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build(); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("property"); assertThat(expression.getValueTypeDescriptor(this)).asString() @@ -314,7 +320,8 @@ class IndexingTests { void indexIntoGenericPropertyContainingGrowingList2() { List property2 = new ArrayList<>(); this.property2 = property2; - SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); + SpelParserConfiguration configuration = + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build(); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("property2"); assertThat(expression.getValueTypeDescriptor(this)).asString().isEqualTo("java.util.ArrayList"); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java index c8c0553add0..046086b4080 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java @@ -255,8 +255,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void failsWhenSettingContextForExpression_SPR12326() { - SpelExpressionParser parser = new SpelExpressionParser( - new SpelParserConfiguration(SpelCompilerMode.OFF, getClass().getClassLoader())); + SpelExpressionParser parser = new SpelExpressionParser(SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .compilerClassLoader(getClass().getClassLoader()) + .build()); Person3 person = new Person3("foo", 1); SpelExpression expression = parser.parseRaw("#it?.age?.equals([0])"); StandardEvaluationContext context = new StandardEvaluationContext(new Object[] {1}); @@ -1232,8 +1234,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void opNe_SPR14863() { - SpelParserConfiguration configuration = - new SpelParserConfiguration(SpelCompilerMode.MIXED, ClassLoader.getSystemClassLoader()); + SpelParserConfiguration configuration = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.MIXED) + .compilerClassLoader(ClassLoader.getSystemClassLoader()) + .build(); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("data['my-key'] != 'my-value'"); @@ -1307,7 +1311,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void nullComparison_SPR22358() { - SpelParserConfiguration configuration = new SpelParserConfiguration(SpelCompilerMode.OFF, null); + SpelParserConfiguration configuration = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.OFF).build(); SpelExpressionParser parser = new SpelExpressionParser(configuration); StandardEvaluationContext ctx = new StandardEvaluationContext(); ctx.setRootObject(new Reg(1)); @@ -3460,8 +3464,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void compilationOfBasicNullSafeMethodReference() { - SpelExpressionParser parser = new SpelExpressionParser( - new SpelParserConfiguration(SpelCompilerMode.OFF, getClass().getClassLoader())); + SpelExpressionParser parser = new SpelExpressionParser(SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .compilerClassLoader(getClass().getClassLoader()) + .build()); SpelExpression expression = parser.parseRaw("#it?.equals(3)"); StandardEvaluationContext context = new StandardEvaluationContext(new Object[] {1}); context.setVariable("it", 3); @@ -4132,8 +4138,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertThat(expression.getValue(context, Boolean.class)).isTrue(); // Variant of above more like what was in the bug report: - SpelExpressionParser parser = new SpelExpressionParser( - new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, getClass().getClassLoader())); + SpelExpressionParser parser = new SpelExpressionParser(SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.IMMEDIATE) + .compilerClassLoader(getClass().getClassLoader()) + .build()); SpelExpression ex = parser.parseRaw("#it?.age.equals([0])"); context = new StandardEvaluationContext(new Object[] {person.getAge()}); @@ -5268,8 +5276,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void indexIntoMap_SPR12045() { - SpelParserConfiguration config = new SpelParserConfiguration( - SpelCompilerMode.IMMEDIATE,getClass().getClassLoader()); + SpelParserConfiguration config = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.IMMEDIATE) + .compilerClassLoader(getClass().getClassLoader()) + .build(); SpelExpressionParser parser = new SpelExpressionParser(config); expression = parser.parseExpression("headers[command]"); @@ -6069,7 +6079,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void ternaryWithMapAccess() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); expression = parser.parseExpression( @@ -6106,7 +6116,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test // gh-19758 void ternaryMiscellaneous() { - SpelParserConfiguration configuration = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration configuration = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); Expression exp; StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("map", Map.of("foo", "qux")); @@ -6225,7 +6235,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test // gh-19758 void elvisMiscellaneous() { - SpelParserConfiguration configuration = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration configuration = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); Expression exp; exp = new SpelExpressionParser(configuration).parseExpression("bar()"); @@ -6309,7 +6319,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void elvis_SPR17214() { - SpelParserConfiguration spc = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration spc = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser sep = new SpelExpressionParser(spc); RecordHolder rh = null; diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelParserConfigurationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelParserConfigurationTests.java new file mode 100644 index 00000000000..f5825706d24 --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelParserConfigurationTests.java @@ -0,0 +1,243 @@ +/* + * Copyright 2002-present 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; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link SpelParserConfiguration}. + * + * @author Sam Brannen + * @since 7.0.10 + */ +class SpelParserConfigurationTests { + + @Test + void builderAppliesSameDefaultsAsNoArgConstructor() { + SpelParserConfiguration expected = new SpelParserConfiguration(); + SpelParserConfiguration actual = SpelParserConfiguration.builder().build(); + + assertThat(actual.getCompilerMode()).isEqualTo(expected.getCompilerMode()); + assertThat(actual.getCompilerClassLoader()).isEqualTo(expected.getCompilerClassLoader()); + assertThat(actual.isAutoGrowNullReferences()).isEqualTo(expected.isAutoGrowNullReferences()); + assertThat(actual.isAutoGrowCollections()).isEqualTo(expected.isAutoGrowCollections()); + // maximumAutoGrowSize defaults actually differ, so not asserted here + assertThat(actual.getMaximumExpressionLength()).isEqualTo(expected.getMaximumExpressionLength()); + assertThat(actual.getMaximumOperations()).isEqualTo(expected.getMaximumOperations()); + assertThat(actual.getMaximumBigPowerBits()).isEqualTo(expected.getMaximumBigPowerBits()); + } + + @Test + void maximumAutoGrowSizeDefaults() { + // Unlike the no-arg constructor, which defaults to Integer.MAX_VALUE for + // backward compatibility, the builder defaults to 256 for consistency with + // DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT. + assertThat(new SpelParserConfiguration().getMaximumAutoGrowSize()).isEqualTo(Integer.MAX_VALUE); + assertThat(SpelParserConfiguration.builder().build().getMaximumAutoGrowSize()).isEqualTo(256); + } + + @Test + void withDefaultsMatchesBuilderDefaults() { + SpelParserConfiguration expected = SpelParserConfiguration.builder().build(); + SpelParserConfiguration actual = SpelParserConfiguration.withDefaults(); + + assertThat(actual.getCompilerMode()).isEqualTo(expected.getCompilerMode()); + assertThat(actual.getCompilerClassLoader()).isEqualTo(expected.getCompilerClassLoader()); + assertThat(actual.isAutoGrowNullReferences()).isEqualTo(expected.isAutoGrowNullReferences()); + assertThat(actual.isAutoGrowCollections()).isEqualTo(expected.isAutoGrowCollections()); + assertThat(actual.getMaximumAutoGrowSize()).isEqualTo(expected.getMaximumAutoGrowSize()); + assertThat(actual.getMaximumExpressionLength()).isEqualTo(expected.getMaximumExpressionLength()); + assertThat(actual.getMaximumOperations()).isEqualTo(expected.getMaximumOperations()); + assertThat(actual.getMaximumBigPowerBits()).isEqualTo(expected.getMaximumBigPowerBits()); + } + + @Test + void builderAppliesCustomValues() { + ClassLoader classLoader = getClass().getClassLoader(); + + SpelParserConfiguration configuration = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.IMMEDIATE) + .compilerClassLoader(classLoader) + .autoGrowNullReferences() + .autoGrowCollections() + .maximumAutoGrowSize(99) + .maximumExpressionLength(100) + .maximumOperations(101) + .maximumBigPowerBits(102) + .build(); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.IMMEDIATE); + assertThat(configuration.getCompilerClassLoader()).isSameAs(classLoader); + assertThat(configuration.isAutoGrowNullReferences()).isTrue(); + assertThat(configuration.isAutoGrowCollections()).isTrue(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(99); + assertThat(configuration.getMaximumExpressionLength()).isEqualTo(100); + assertThat(configuration.getMaximumOperations()).isEqualTo(101); + assertThat(configuration.getMaximumBigPowerBits()).isEqualTo(102); + } + + @Test + void builderRejectsInvalidValues() { + SpelParserConfiguration.Builder builder = SpelParserConfiguration.builder(); + + assertThatIllegalArgumentException().isThrownBy(() -> builder.compilerMode(null)); + assertThatIllegalArgumentException().isThrownBy(() -> builder.maximumAutoGrowSize(-1)); + assertThatIllegalArgumentException().isThrownBy(() -> builder.maximumExpressionLength(0)); + assertThatIllegalArgumentException().isThrownBy(() -> builder.maximumOperations(0)); + assertThatIllegalArgumentException().isThrownBy(() -> builder.maximumBigPowerBits(0)); + } + + + /** + * Regression tests for the legacy constructors in {@link SpelParserConfiguration}. + *

Elsewhere in the test suite, we prefer {@link SpelParserConfiguration#builder()} + * over these constructors. Thus, this nested class exists so that constructor coverage + * remains in one place, which will also keep any future deprecation warnings for + * these constructors confined to this class. + */ + @Nested + class LegacyConstructorTests { + + @Test + void noArgConstructorAppliesDefaults() { + SpelParserConfiguration configuration = new SpelParserConfiguration(); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.OFF); + assertThat(configuration.getCompilerClassLoader()).isNull(); + assertThat(configuration.isAutoGrowNullReferences()).isFalse(); + assertThat(configuration.isAutoGrowCollections()).isFalse(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(Integer.MAX_VALUE); + assertThat(configuration.getMaximumExpressionLength()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_EXPRESSION_LENGTH); + assertThat(configuration.getMaximumOperations()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_OPERATIONS); + assertThat(configuration.getMaximumBigPowerBits()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_BIG_POWER_BITS); + } + + @Test + void compilerModeAndClassLoaderConstructor() { + ClassLoader classLoader = getClass().getClassLoader(); + SpelParserConfiguration configuration = + new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, classLoader); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.IMMEDIATE); + assertThat(configuration.getCompilerClassLoader()).isSameAs(classLoader); + assertThat(configuration.isAutoGrowNullReferences()).isFalse(); + assertThat(configuration.isAutoGrowCollections()).isFalse(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(Integer.MAX_VALUE); + } + + @Test + void autoGrowFlagsConstructor() { + SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.OFF); + assertThat(configuration.isAutoGrowNullReferences()).isTrue(); + assertThat(configuration.isAutoGrowCollections()).isTrue(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(Integer.MAX_VALUE); + } + + @Test + void autoGrowFlagsAndMaximumAutoGrowSizeConstructor() { + SpelParserConfiguration configuration = new SpelParserConfiguration(true, true, 99); + + assertThat(configuration.isAutoGrowNullReferences()).isTrue(); + assertThat(configuration.isAutoGrowCollections()).isTrue(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(99); + } + + @Test + void fiveArgConstructorAppliesAllValues() { + ClassLoader classLoader = getClass().getClassLoader(); + SpelParserConfiguration configuration = new SpelParserConfiguration( + SpelCompilerMode.IMMEDIATE, classLoader, true, true, 99); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.IMMEDIATE); + assertThat(configuration.getCompilerClassLoader()).isSameAs(classLoader); + assertThat(configuration.isAutoGrowNullReferences()).isTrue(); + assertThat(configuration.isAutoGrowCollections()).isTrue(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(99); + assertThat(configuration.getMaximumExpressionLength()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_EXPRESSION_LENGTH); + } + + @Test + void sixArgConstructorAppliesMaximumExpressionLength() { + SpelParserConfiguration configuration = new SpelParserConfiguration( + SpelCompilerMode.IMMEDIATE, null, true, true, 99, 100); + + assertThat(configuration.getMaximumExpressionLength()).isEqualTo(100); + assertThat(configuration.getMaximumOperations()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_OPERATIONS); + } + + @Test + void sevenArgConstructorAppliesMaximumOperations() { + SpelParserConfiguration configuration = new SpelParserConfiguration( + SpelCompilerMode.IMMEDIATE, null, true, true, 99, 100, 101); + + assertThat(configuration.getMaximumOperations()).isEqualTo(101); + assertThat(configuration.getMaximumBigPowerBits()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_BIG_POWER_BITS); + } + + @Test + void canonicalConstructorAppliesAllValues() { + ClassLoader classLoader = getClass().getClassLoader(); + SpelParserConfiguration configuration = new SpelParserConfiguration( + SpelCompilerMode.IMMEDIATE, classLoader, true, true, 99, 100, 101, 102); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.IMMEDIATE); + assertThat(configuration.getCompilerClassLoader()).isSameAs(classLoader); + assertThat(configuration.isAutoGrowNullReferences()).isTrue(); + assertThat(configuration.isAutoGrowCollections()).isTrue(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(99); + assertThat(configuration.getMaximumExpressionLength()).isEqualTo(100); + assertThat(configuration.getMaximumOperations()).isEqualTo(101); + assertThat(configuration.getMaximumBigPowerBits()).isEqualTo(102); + } + + @Test + void canonicalConstructorRejectsInvalidValues() { + assertThatIllegalArgumentException().isThrownBy(() -> + new SpelParserConfiguration(null, null, false, false, 0, 1, 1, 1)); + assertThatIllegalArgumentException().isThrownBy(() -> + new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, 0, 1, 1)); + assertThatIllegalArgumentException().isThrownBy(() -> + new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, 1, 0, 1)); + assertThatIllegalArgumentException().isThrownBy(() -> + new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, 1, 1, 0)); + } + + @Test + void canonicalConstructorDoesNotRejectNegativeMaximumAutoGrowSize() { + // Unlike SpelParserConfiguration.Builder#maximumAutoGrowSize(int), this + // constructor does not (yet) validate that maximumAutoGrowSize is not + // negative -- that precondition was only introduced on the 7.1 line. + SpelParserConfiguration configuration = + new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, -1, 1, 1, 1); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(-1); + } + + } + +} diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java index 76a7654d21f..e27c7925605 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java @@ -696,8 +696,8 @@ class SpelReproTests extends AbstractExpressionTests { @Test void compiledExpressionForProxy_SPR16191() { - SpelExpressionParser expressionParser = - new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null)); + SpelExpressionParser expressionParser = new SpelExpressionParser( + SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build()); Expression expression = expressionParser.parseExpression("#target.process(#root)"); VarargsReceiver receiver = new VarargsReceiver(); @@ -1274,7 +1274,7 @@ class SpelReproTests extends AbstractExpressionTests { @Test void SPR10452() { - SpelParserConfiguration configuration = new SpelParserConfiguration(false, false); + SpelParserConfiguration configuration = SpelParserConfiguration.withDefaults(); ExpressionParser parser = new SpelExpressionParser(configuration); StandardEvaluationContext context = new StandardEvaluationContext(); @@ -1299,7 +1299,7 @@ class SpelReproTests extends AbstractExpressionTests { @Test void SPR9495() { - SpelParserConfiguration configuration = new SpelParserConfiguration(false, false); + SpelParserConfiguration configuration = SpelParserConfiguration.withDefaults(); ExpressionParser parser = new SpelExpressionParser(configuration); StandardEvaluationContext context = new StandardEvaluationContext(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java index 67799dd7ab5..b19ebbf8fd7 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java @@ -46,7 +46,7 @@ class SpelCompilerTests { @Test // gh-24357 void expressionCompilesWhenMethodComesFromPublicInterface() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); OrderedComponent component = new OrderedComponent(); @@ -59,7 +59,7 @@ class SpelCompilerTests { @Test // gh-25706 void defaultMethodInvocation() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); StandardEvaluationContext context = new StandardEvaluationContext(); @@ -84,7 +84,7 @@ class SpelCompilerTests { @Test void simpleEvaluationContextBlocksCompilationByDefault() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); // "order" resides in the public Ordered interface and is therefore compilable, // so any non-compilation is attributable solely to the context's policy. @@ -102,7 +102,7 @@ class SpelCompilerTests { @Test void simpleEvaluationContextAllowsCompilationWhenSupported() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); // "order" resides in the public Ordered interface and is therefore compilable. Expression expression = parser.parseExpression("order"); @@ -120,7 +120,7 @@ class SpelCompilerTests { @Test void simpleEvaluationContextIgnoresPrecompiledExpressionByDefault() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); // "order" resides in the public Ordered interface and is therefore compilable. Expression expression = parser.parseExpression("order"); @@ -155,7 +155,7 @@ class SpelCompilerTests { */ @Test void simpleEvaluationContextSetAsDefaultBlocksCompilationForImplicitContextVariants() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); // "order" resides in the public Ordered interface and is therefore compilable, // so any non-compilation is attributable solely to the context's policy. @@ -193,7 +193,7 @@ class SpelCompilerTests { */ @Test void simpleEvaluationContextSetAsDefaultIgnoresPrecompiledExpressionForImplicitContextVariants() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); // "order" resides in the public Ordered interface and is therefore compilable. SpelExpression expression = parser.parseRaw("order"); @@ -240,7 +240,7 @@ class SpelCompilerTests { @Test // gh-28043 void changingRegisteredVariableTypeDoesNotResultInFailureInMixedMode() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.MIXED, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.MIXED).build(); SpelExpressionParser parser = new SpelExpressionParser(config); Expression sharedExpression = parser.parseExpression("#bean.value"); StandardEvaluationContext context = new StandardEvaluationContext();