Skip to content

Commit 52c93b1

Browse files
committed
Increase scope of regex pattern cache for the SpEL matches operator
Prior to this commit, the pattern cache for the SpEL `matches` operator only applied to expressions such as the following where the same `matches` operator is invoked multiple times with different input: "map.keySet().?[#this matches '.+xyz']" The pattern cache did not apply to expressions such as the following where the same pattern ('.+xyz') is used in multiple `matches` operations: "foo matches '.+xyz' AND bar matches '.+xyz'" This commit addresses this by moving the instance of the pattern cache map from OperatorMatches to InternalSpelExpressionParser so that the cache can be reused for all `matches` operations for the given parser. Closes gh-30148
1 parent 9c6cb74 commit 52c93b1

File tree

2 files changed

+28
-6
lines changed

2 files changed

+28
-6
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -36,19 +36,35 @@
3636
*
3737
* @author Andy Clement
3838
* @author Juergen Hoeller
39+
* @author Sam Brannen
3940
* @since 3.0
4041
*/
4142
public class OperatorMatches extends Operator {
4243

4344
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
4445

45-
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
46+
private final ConcurrentMap<String, Pattern> patternCache;
4647

4748

49+
/**
50+
* Create a new {@link OperatorMatches} instance.
51+
* @deprecated as of Spring Framework 5.2.23 in favor of invoking
52+
* {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)}
53+
* with a shared pattern cache instead
54+
*/
55+
@Deprecated
4856
public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) {
49-
super("matches", startPos, endPos, operands);
57+
this(new ConcurrentHashMap<>(), startPos, endPos, operands);
5058
}
5159

60+
/**
61+
* Create a new {@link OperatorMatches} instance with a shared pattern cache.
62+
* @since 5.2.23
63+
*/
64+
public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) {
65+
super("matches", startPos, endPos, operands);
66+
this.patternCache = patternCache;
67+
}
5268

5369
/**
5470
* Check the first operand matches the regex specified as the second operand.
@@ -63,7 +79,7 @@ public BooleanTypedValue getValueInternal(ExpressionState state) throws Evaluati
6379
SpelNodeImpl leftOp = getLeftOperand();
6480
SpelNodeImpl rightOp = getRightOperand();
6581
String left = leftOp.getValue(state, String.class);
66-
Object right = getRightOperand().getValue(state);
82+
Object right = rightOp.getValue(state);
6783

6884
if (left == null) {
6985
throw new SpelEvaluationException(leftOp.getStartPosition(),

spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -21,6 +21,8 @@
2121
import java.util.Collections;
2222
import java.util.Deque;
2323
import java.util.List;
24+
import java.util.concurrent.ConcurrentHashMap;
25+
import java.util.concurrent.ConcurrentMap;
2426
import java.util.regex.Pattern;
2527

2628
import org.springframework.expression.ParseException;
@@ -83,6 +85,7 @@
8385
* @author Andy Clement
8486
* @author Juergen Hoeller
8587
* @author Phillip Webb
88+
* @author Sam Brannen
8689
* @since 3.0
8790
*/
8891
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
@@ -95,6 +98,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
9598
// For rules that build nodes, they are stacked here for return
9699
private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>();
97100

101+
// Shared cache for compiled regex patterns
102+
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
103+
98104
// The expression being parsed
99105
private String expressionString = "";
100106

@@ -248,7 +254,7 @@ private SpelNodeImpl eatRelationalExpression() {
248254
}
249255

250256
if (tk == TokenKind.MATCHES) {
251-
return new OperatorMatches(t.startPos, t.endPos, expr, rhExpr);
257+
return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr);
252258
}
253259

254260
Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");

0 commit comments

Comments
 (0)