Skip to content

Commit af2934c

Browse files
committed
Document support for overloading operators in SpEL in reference manual
Closes gh-32182
1 parent 17ee82e commit af2934c

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

Diff for: framework-docs/modules/ROOT/pages/core/expressions/language-ref/operators.adoc

+71
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ The Spring Expression Language supports the following kinds of operators:
88
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-string[String Operators]
99
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-mathematical[Mathematical Operators]
1010
* xref:core/expressions/language-ref/operators.adoc#expressions-assignment[The Assignment Operator]
11+
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-overloaded[Overloaded Operators]
12+
1113

1214

1315
[[expressions-operators-relational]]
@@ -523,3 +525,72 @@ Kotlin::
523525
======
524526

525527

528+
[[expressions-operators-overloaded]]
529+
== Overloaded Operators
530+
531+
By default, the mathematical operations defined in SpEL's `Operation` enum (`ADD`,
532+
`SUBTRACT`, `DIVIDE`, `MULTIPLY`, `MODULUS`, and `POWER`) support simple types like
533+
numbers. By providing an implementation of `OperatorOverloader`, the expression language
534+
can support these operations on other types.
535+
536+
For example, if we want to overload the `ADD` operator to allow two lists to be
537+
concatenated using the `+` sign, we can implement a custom `OperatorOverloader` as
538+
follows.
539+
540+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
541+
----
542+
pubic class ListConcatenation implements OperatorOverloader {
543+
544+
@Override
545+
public boolean overridesOperation(Operation operation, Object left, Object right) {
546+
return (operation == Operation.ADD &&
547+
left instanceof List && right instanceof List);
548+
}
549+
550+
@Override
551+
@SuppressWarnings("unchecked")
552+
public Object operate(Operation operation, Object left, Object right) {
553+
if (operation == Operation.ADD &&
554+
left instanceof List list1 && right instanceof List list2) {
555+
556+
List result = new ArrayList(list1);
557+
result.addAll(list2);
558+
return result;
559+
}
560+
throw new UnsupportedOperationException(
561+
"No overload for operation %s and operands [%s] and [%s]"
562+
.formatted(operation.name(), left, right));
563+
}
564+
}
565+
----
566+
567+
If we register `ListConcatenation` as the `OperatorOverloader` in a
568+
`StandardEvaluationContext`, we can then evaluate expressions like `{1, 2, 3} + {4, 5}`
569+
as demonstrated in the following example.
570+
571+
[tabs]
572+
======
573+
Java::
574+
+
575+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
576+
----
577+
StandardEvaluationContext context = new StandardEvaluationContext();
578+
context.setOperatorOverloader(new ListConcatenation());
579+
580+
// evaluates to a new list: [1, 2, 3, 4, 5]
581+
parser.parseExpression("{1, 2, 3} + {4, 5}").getValue(context, List.class);
582+
----
583+
584+
Kotlin::
585+
+
586+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
587+
----
588+
StandardEvaluationContext context = StandardEvaluationContext()
589+
context.setOperatorOverloader(ListConcatenation())
590+
591+
// evaluates to a new list: [1, 2, 3, 4, 5]
592+
parser.parseExpression("{1, 2, 3} + {4, 5}").getValue(context, List::class.java)
593+
----
594+
======
595+
596+

Diff for: spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java

+38
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.springframework.expression.EvaluationContext;
3333
import org.springframework.expression.Expression;
3434
import org.springframework.expression.ExpressionParser;
35+
import org.springframework.expression.Operation;
36+
import org.springframework.expression.OperatorOverloader;
3537
import org.springframework.expression.common.TemplateParserContext;
3638
import org.springframework.expression.spel.standard.SpelExpressionParser;
3739
import org.springframework.expression.spel.support.SimpleEvaluationContext;
@@ -447,6 +449,18 @@ void assignment() {
447449
assertThat(parser.parseExpression("foo").getValue(context, inventor, String.class)).isEqualTo("Alexandar Seovic");
448450
assertThat(aleks).isEqualTo("Alexandar Seovic");
449451
}
452+
453+
@Test
454+
@SuppressWarnings("unchecked")
455+
void overloadingOperators() {
456+
StandardEvaluationContext context = new StandardEvaluationContext();
457+
context.setOperatorOverloader(new ListConcatenation());
458+
459+
// evaluates to [1, 2, 3, 4, 5]
460+
List list = parser.parseExpression("{1, 2, 3} + {4, 5}").getValue(context, List.class);
461+
assertThat(list).containsExactly(1, 2, 3, 4, 5);
462+
}
463+
450464
}
451465

452466
@Nested
@@ -672,4 +686,28 @@ public static String reverseString(String input) {
672686
}
673687
}
674688

689+
private static class ListConcatenation implements OperatorOverloader {
690+
691+
@Override
692+
public boolean overridesOperation(Operation operation, Object left, Object right) {
693+
return (operation == Operation.ADD &&
694+
left instanceof List && right instanceof List);
695+
}
696+
697+
@Override
698+
@SuppressWarnings("unchecked")
699+
public Object operate(Operation operation, Object left, Object right) {
700+
if (operation == Operation.ADD &&
701+
left instanceof List list1 && right instanceof List list2) {
702+
703+
List result = new ArrayList(list1);
704+
result.addAll(list2);
705+
return result;
706+
}
707+
throw new UnsupportedOperationException(
708+
"No overload for operation %s and operands [%s] and [%s]"
709+
.formatted(operation.name(), left, right));
710+
}
711+
}
712+
675713
}

0 commit comments

Comments
 (0)