Skip to content

Commit 7855986

Browse files
committed
Make max length of SpEL expressions in an ApplicationContext configurable
This commit introduces support for a Spring property named `spring.context.expression.maxLength`. When set, the value of that property is used internally in StandardBeanExpressionResolver to configure the SpelParserConfiguration used when evaluating String values in bean definitions, @⁠Value, etc. Closes gh-31952
1 parent 3452354 commit 7855986

File tree

5 files changed

+126
-6
lines changed

5 files changed

+126
-6
lines changed

framework-docs/modules/ROOT/pages/appendix.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ JavaBeans `Introspector`. See
2828
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
2929
for details.
3030

31+
| `spring.context.expression.maxLength`
32+
| The maximum length for
33+
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
34+
expressions used in XML bean definitions, `@Value`, etc.
35+
3136
| `spring.expression.compiler.mode`
3237
| The mode to use when compiling expressions for the
3338
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].

framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,15 @@ Kotlin::
380380
----
381381
======
382382

383+
By default, a SpEL expression cannot contain more than 10,000 characters; however, the
384+
`maxExpressionLength` is configurable. If you create a `SpelExpressionParser`
385+
programmatically, you can specify a custom `maxExpressionLength` when creating the
386+
`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you wish to
387+
set the `maxExpressionLength` used for parsing SpEL expressions within an
388+
`ApplicationContext` -- for example, in XML bean definitions, `@Value`, etc. -- you can
389+
set a JVM system property or Spring property named `spring.context.expression.maxLength`
390+
to the maximum expression length needed by your application (see
391+
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
383392

384393

385394
[[expressions-spel-compilation]]

spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import org.springframework.beans.factory.BeanExpressionException;
2424
import org.springframework.beans.factory.config.BeanExpressionContext;
2525
import org.springframework.beans.factory.config.BeanExpressionResolver;
26+
import org.springframework.core.SpringProperties;
2627
import org.springframework.core.convert.ConversionService;
2728
import org.springframework.core.convert.support.DefaultConversionService;
2829
import org.springframework.expression.Expression;
@@ -47,6 +48,7 @@
4748
* beans such as "environment", "systemProperties" and "systemEnvironment".
4849
*
4950
* @author Juergen Hoeller
51+
* @author Sam Brannen
5052
* @since 3.0
5153
* @see BeanExpressionContext#getBeanFactory()
5254
* @see org.springframework.expression.ExpressionParser
@@ -55,6 +57,14 @@
5557
*/
5658
public class StandardBeanExpressionResolver implements BeanExpressionResolver {
5759

60+
/**
61+
* System property to configure the maximum length for SpEL expressions: {@value}.
62+
* <p>Can also be configured via the {@link SpringProperties} mechanism.
63+
* @since 6.1.3
64+
* @see SpelParserConfiguration#getMaximumExpressionLength()
65+
*/
66+
public static final String MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME = "spring.context.expression.maxLength";
67+
5868
/** Default expression prefix: "#{". */
5969
public static final String DEFAULT_EXPRESSION_PREFIX = "#{";
6070

@@ -90,18 +100,24 @@ public String getExpressionSuffix() {
90100

91101
/**
92102
* Create a new {@code StandardBeanExpressionResolver} with default settings.
103+
* <p>As of Spring Framework 6.1.3, the maximum SpEL expression length can be
104+
* configured via the {@link #MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME} property.
93105
*/
94106
public StandardBeanExpressionResolver() {
95-
this.expressionParser = new SpelExpressionParser();
107+
this(null);
96108
}
97109

98110
/**
99111
* Create a new {@code StandardBeanExpressionResolver} with the given bean class loader,
100112
* using it as the basis for expression compilation.
113+
* <p>As of Spring Framework 6.1.3, the maximum SpEL expression length can be
114+
* configured via the {@link #MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME} property.
101115
* @param beanClassLoader the factory's bean class loader
102116
*/
103117
public StandardBeanExpressionResolver(@Nullable ClassLoader beanClassLoader) {
104-
this.expressionParser = new SpelExpressionParser(new SpelParserConfiguration(null, beanClassLoader));
118+
SpelParserConfiguration parserConfig = new SpelParserConfiguration(
119+
null, beanClassLoader, false, false, Integer.MAX_VALUE, retrieveMaxExpressionLength());
120+
this.expressionParser = new SpelExpressionParser(parserConfig);
105121
}
106122

107123

@@ -178,4 +194,22 @@ public Object evaluate(@Nullable String value, BeanExpressionContext beanExpress
178194
protected void customizeEvaluationContext(StandardEvaluationContext evalContext) {
179195
}
180196

197+
private static int retrieveMaxExpressionLength() {
198+
String value = SpringProperties.getProperty(MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME);
199+
if (!StringUtils.hasText(value)) {
200+
return SpelParserConfiguration.DEFAULT_MAX_EXPRESSION_LENGTH;
201+
}
202+
203+
try {
204+
int maxLength = Integer.parseInt(value.trim());
205+
Assert.isTrue(maxLength > 0, () -> "Value [" + maxLength + "] for system property ["
206+
+ MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME + "] must be positive");
207+
return maxLength;
208+
}
209+
catch (NumberFormatException ex) {
210+
throw new IllegalArgumentException("Failed to parse value for system property [" +
211+
MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME + "]: " + ex.getMessage(), ex);
212+
}
213+
}
214+
181215
}

spring-context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
2828

2929
import org.junit.jupiter.api.Test;
3030

31+
import org.springframework.beans.factory.BeanCreationException;
3132
import org.springframework.beans.factory.ObjectFactory;
3233
import org.springframework.beans.factory.annotation.Autowired;
3334
import org.springframework.beans.factory.annotation.Qualifier;
@@ -42,17 +43,24 @@
4243
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
4344
import org.springframework.context.annotation.AnnotationConfigUtils;
4445
import org.springframework.context.support.GenericApplicationContext;
46+
import org.springframework.core.SpringProperties;
4547
import org.springframework.core.convert.support.DefaultConversionService;
4648
import org.springframework.core.convert.support.GenericConversionService;
4749
import org.springframework.core.io.ClassPathResource;
4850
import org.springframework.core.io.Resource;
4951
import org.springframework.core.io.support.EncodedResource;
5052
import org.springframework.core.testfixture.io.SerializationTestUtils;
53+
import org.springframework.expression.spel.SpelEvaluationException;
5154
import org.springframework.util.FileCopyUtils;
5255

5356
import static org.assertj.core.api.Assertions.assertThat;
57+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
58+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
59+
import static org.springframework.context.expression.StandardBeanExpressionResolver.MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME;
5460

5561
/**
62+
* Integration tests for SpEL expression support in an {@code ApplicationContext}.
63+
*
5664
* @author Juergen Hoeller
5765
* @author Sam Brannen
5866
* @since 3.0
@@ -268,6 +276,70 @@ void resourceInjection() throws IOException {
268276
}
269277
}
270278

279+
@Test
280+
void maxSpelExpressionLengthMustBeAnInteger() {
281+
doWithMaxSpelExpressionLength("boom", () -> {
282+
try (AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext()) {
283+
assertThatIllegalArgumentException()
284+
.isThrownBy(ac::refresh)
285+
.withMessageStartingWith("Failed to parse value for system property [%s]",
286+
MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME)
287+
.withMessageContaining("boom");
288+
}
289+
});
290+
}
291+
292+
@Test
293+
void maxSpelExpressionLengthMustBePositive() {
294+
doWithMaxSpelExpressionLength("-99", () -> {
295+
try (AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext()) {
296+
assertThatIllegalArgumentException()
297+
.isThrownBy(ac::refresh)
298+
.withMessage("Value [%d] for system property [%s] must be positive", -99,
299+
MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME);
300+
}
301+
});
302+
}
303+
304+
@Test
305+
void maxSpelExpressionLength() {
306+
final String expression = "#{ 'xyz' + 'xyz' + 'xyz' }";
307+
308+
// With the default max length of 10_000, the expression should succeed.
309+
evaluateExpressionInBean(expression);
310+
311+
// With a max length of 20, the expression should fail.
312+
doWithMaxSpelExpressionLength("20", () ->
313+
assertThatExceptionOfType(BeanCreationException.class)
314+
.isThrownBy(() -> evaluateExpressionInBean(expression))
315+
.havingRootCause()
316+
.isInstanceOf(SpelEvaluationException.class)
317+
.withMessageEndingWith("exceeding the threshold of '20' characters"));
318+
}
319+
320+
private static void doWithMaxSpelExpressionLength(String maxLength, Runnable action) {
321+
try {
322+
SpringProperties.setProperty(MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME, maxLength);
323+
action.run();
324+
}
325+
finally {
326+
SpringProperties.setProperty(MAX_SPEL_EXPRESSION_LENGTH_PROPERTY_NAME, null);
327+
}
328+
}
329+
330+
private static void evaluateExpressionInBean(String expression) {
331+
try (AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext()) {
332+
GenericBeanDefinition bd = new GenericBeanDefinition();
333+
bd.setBeanClass(String.class);
334+
bd.getConstructorArgumentValues().addGenericArgumentValue(expression);
335+
ac.registerBeanDefinition("str", bd);
336+
ac.refresh();
337+
338+
String str = ac.getBean("str", String.class);
339+
assertThat(str).isEqualTo("xyz".repeat(3)); // "#{ 'xyz' + 'xyz' + 'xyz' }"
340+
}
341+
}
342+
271343

272344
@SuppressWarnings("serial")
273345
public static class ValueTestBean implements Serializable {

spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ public class SpelParserConfiguration {
3535
* Default maximum length permitted for a SpEL expression.
3636
* @since 5.2.24
3737
*/
38-
private static final int DEFAULT_MAX_EXPRESSION_LENGTH = 10_000;
38+
public static final int DEFAULT_MAX_EXPRESSION_LENGTH = 10_000;
3939

4040
/** System property to configure the default compiler mode for SpEL expression parsers: {@value}. */
4141
public static final String SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME = "spring.expression.compiler.mode";

0 commit comments

Comments
 (0)