Skip to content

Commit a6a9ff7

Browse files
committed
Add documentation.
1 parent 2a92e00 commit a6a9ff7

File tree

5 files changed

+250
-3
lines changed

5 files changed

+250
-3
lines changed

src/main/antora/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
** xref:repositories/null-handling.adoc[]
1616
** xref:repositories/projections.adoc[]
1717
* xref:query-by-example.adoc[]
18+
* xref:value-expressions.adoc[]
1819
* xref:auditing.adoc[]
1920
* xref:custom-conversions.adoc[]
2021
* xref:entity-callbacks.adoc[]
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
[[valueexpressions.fundamentals]]
2+
= Value Expressions Fundamentals
3+
4+
Value Expressions a composition of {spring-framework-docs}/core/expressions.html[Spring Expression Language (SpEL)] and {spring-framework-docs}/core/beans/environment.html#beans-placeholder-resolution-in-statements[Property Placeholder Resolution].
5+
Value Expressions a combine powerful evaluation of programmatic expressions with the simplicity to resort to property-placeholder resolution to obtain values from the `Environment` such as configuration properties.
6+
7+
Expressions are expected to be defined by a trusted input such as an annotation value and not to be determined from user input.
8+
9+
The following code demonstrates how to use expressions in the context of annotations.
10+
11+
.Annotation Usage
12+
====
13+
[source,java]
14+
----
15+
@Document("orders-#{tenantService.getOrderCollection()}-${tenant-config.suffix}")
16+
class Order {
17+
// …
18+
}
19+
----
20+
====
21+
22+
Value Expressions can be defined from a sole SpEL Expression, a Property Placeholder or a composite expression mixing various expressions including literals.
23+
24+
.Expression Examples
25+
====
26+
[source]
27+
----
28+
#{tenantService.getOrderCollection()} <1>
29+
#{(1+1) + '-hello-world'} <2>
30+
${tenant-config.suffix} <3>
31+
orders-${tenant-config.suffix} <4>
32+
#{tenantService.getOrderCollection()}-${tenant-config.suffix} <5>
33+
----
34+
35+
<1> Value Expression using a single SpEL Expression.
36+
<2> Value Expression using a static SpEL Expression evaluating to `2-hello-world`.
37+
<3> Value Expression using a single Property Placeholder.
38+
<4> Composite expression comprised from the literal `orders-` and the Property Placeholder `${tenant-config.suffix}`.
39+
<5> Composite expression using SpEL, Property Placeholders and literals.
40+
====
41+
42+
Using value expressions introduces a lot of flexibility to your code.
43+
Keep in mind that using value expressions requires evaluation of the expression on each usage.
44+
45+
[[valueexpressions.api]]
46+
== Parsing and Evaluation
47+
48+
Value Expressions are parsed by the `ValueExpressionParser` API.
49+
Instances of `ValueExpression` are thread-safe and can be cached for later use to avoid repeated parsing.
50+
51+
The following example shows the Value Expression API usage:
52+
53+
.Parsing and Evaluation
54+
[tabs]
55+
======
56+
Java::
57+
+
58+
[source,java,role="primary"]
59+
----
60+
ValueParserConfiguration configuration = SpelExpressionParser::new;
61+
ValueEvaluationContext context = ValueEvaluationContext.of(environment, evaluationContext);
62+
63+
ValueExpressionParser parser = ValueExpressionParser.create(configuration);
64+
ValueExpression expression = parser.parse("Hello, World");
65+
Object result = expression.evaluate(context);
66+
----
67+
68+
Kotlin::
69+
+
70+
[source,kotlin,role="secondary"]
71+
----
72+
val configuration = ValueParserConfiguration { SpelExpressionParser() }
73+
val context = ValueEvaluationContext.of(environment, evaluationContext)
74+
75+
val parser = ValueExpressionParser.create(configuration)
76+
val expression: ValueExpression = parser.parse("Hello, World")
77+
val result: Any = expression.evaluate(context)
78+
----
79+
======
80+
81+
[[valueexpressions.spel]]
82+
== SpEL Expressions
83+
84+
{spring-framework-docs}/core/expressions.html[SpEL Expressions] follow the Template style where the expression is expected to be enclosed within the `#{…}` format.
85+
Expressions are evaluated using an `EvaluationContext` that is provided by `EvaluationContextProvider`.
86+
The context itself is a powerful `StandardEvaluationContext` allowing a wide range of operations, access to static types and context extensions.
87+
88+
NOTE: Make sure to parse and evaluate only expressions from trusted sources such as annotations.
89+
Accepting user-provided expressions can create an entry path to exploit the application context and your system resulting in a potential security vulnerability.
90+
91+
=== Extending the Evaluation Context
92+
93+
`EvaluationContextProvider` and its reactive variant `ReactiveEvaluationContextProvider` provide access to an `EvaluationContext`.
94+
`ExtensionAwareEvaluationContextProvider` and its reactive variant `ReactiveExtensionAwareEvaluationContextProvider` are default implementations that determine context extensions from an application context, specifically `ListableBeanFactory`.
95+
96+
Extensions implement either `EvaluationContextExtension` or `ReactiveEvaluationContextExtension` to provide extension support to hydrate `EvaluationContext`.
97+
That are a root object, properties and functions (top-level methods).
98+
99+
The following example shows a context extension that provides a root object, properties, functions and an aliased function.
100+
101+
.Implementing a `EvaluationContextExtension`
102+
[tabs]
103+
======
104+
Java::
105+
+
106+
[source,java,role="primary"]
107+
----
108+
@Component
109+
public class MyExtension implements EvaluationContextExtension {
110+
111+
@Override
112+
public String getExtensionId() {
113+
return "my-extension";
114+
}
115+
116+
@Override
117+
public Object getRootObject() {
118+
return new CustomExtensionRootObject();
119+
}
120+
121+
@Override
122+
public Map<String, Object> getProperties() {
123+
124+
Map<String, Object> properties = new HashMap<>();
125+
126+
properties.put("key", "Hello");
127+
128+
return properties;
129+
}
130+
131+
@Override
132+
public Map<String, Function> getFunctions() {
133+
134+
Map<String, Function> functions = new HashMap<>();
135+
136+
try {
137+
functions.put("aliasedMethod", new Function(getClass().getMethod("extensionMethod")));
138+
return functions;
139+
} catch (Exception o_O) {
140+
throw new RuntimeException(o_O);
141+
}
142+
}
143+
144+
public static String extensionMethod() {
145+
return "Hello World";
146+
}
147+
148+
public static int add(int i1, int i2) {
149+
return i1 + i2;
150+
}
151+
152+
}
153+
154+
public class CustomExtensionRootObject {
155+
156+
public boolean rootObjectInstanceMethod() {
157+
return true;
158+
}
159+
160+
}
161+
----
162+
163+
Kotlin::
164+
+
165+
[source,kotlin,role="secondary"]
166+
----
167+
@Component
168+
class MyExtension : EvaluationContextExtension {
169+
170+
override fun getExtensionId(): String {
171+
return "my-extension"
172+
}
173+
174+
override fun getRootObject(): Any? {
175+
return CustomExtensionRootObject()
176+
}
177+
178+
override fun getProperties(): Map<String, Any> {
179+
val properties: MutableMap<String, Any> = HashMap()
180+
181+
properties["key"] = "Hello"
182+
183+
return properties
184+
}
185+
186+
override fun getFunctions(): Map<String, Function> {
187+
val functions: MutableMap<String, Function> = HashMap()
188+
189+
try {
190+
functions["aliasedMethod"] = Function(javaClass.getMethod("extensionMethod"))
191+
return functions
192+
} catch (o_O: Exception) {
193+
throw RuntimeException(o_O)
194+
}
195+
}
196+
197+
companion object {
198+
fun extensionMethod(): String {
199+
return "Hello World"
200+
}
201+
202+
fun add(i1: Int, i2: Int): Int {
203+
return i1 + i2
204+
}
205+
}
206+
}
207+
208+
class CustomExtensionRootObject {
209+
fun rootObjectInstanceMethod(): Boolean {
210+
return true
211+
}
212+
}
213+
----
214+
======
215+
216+
Once the above shown extension is registered, you can use its exported methods, properties and root object to evaluate SpEL expressions:
217+
218+
.Expression Evaluation Examples
219+
====
220+
[source]
221+
----
222+
#{add(1, 2)} <1>
223+
#{extensionMethod()} <2>
224+
#{aliasedMethod()} <3>
225+
#{key} <4>
226+
#{rootObjectInstanceMethod()} <5>
227+
----
228+
229+
<1> Invoke the method `add` declared by `MyExtension` resulting in `3` as the method adds both numeric parameters and returns the sum.
230+
<2> Invoke the method `extensionMethod` declared by `MyExtension` resulting in `Hello World`.
231+
<3> Invoke the method `aliasedMethod`.
232+
The method is exposed as function and redirects into the method `extensionMethod` declared by `MyExtension` resulting in `Hello World`.
233+
<4> Evaluate the `key` property resulting in `Hello`.
234+
<5> Invoke the method `rootObjectInstanceMethod` on the root object instance `CustomExtensionRootObject`.
235+
====
236+
237+
You can find real-life context extensions at https://github.com/spring-projects/spring-security/blob/main/data/src/main/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.java[`SecurityEvaluationContextExtension`].
238+
239+
[[valueexpressions.property-placeholders]]
240+
== Property Placeholders
241+
242+
Property placeholders refer to properties provided typically by a `PropertySource` through `Environment`.
243+
Properties are useful to resolve against system properties, application configuration files, environment configuration or property sources contributed by secret management systems.
244+
You can find more details on the property placeholders in {spring-framework-docs}/core/beans/annotation-config/value-annotations.html#page-title[Spring Framework's documentation on `@Value` usage].
245+
246+

src/main/java/org/springframework/data/spel/CompositeValueExpression.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public boolean isLiteral() {
4545
}
4646

4747
@Override
48-
public Object evaluate(ValueEvaluationContext context) {
48+
public String evaluate(ValueEvaluationContext context) {
4949

5050
StringBuilder builder = new StringBuilder();
5151

src/main/java/org/springframework/data/spel/LiteralValueExpression.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public boolean isLiteral() {
3535
}
3636

3737
@Override
38-
public Object evaluate(ValueEvaluationContext context) {
38+
public String evaluate(ValueEvaluationContext context) {
3939
return expression;
4040
}
4141

src/test/java/org/springframework/data/util/ValueEvaluationUnitTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ void shouldParseQuoted() {
116116

117117
private String eval(String expressionString) {
118118

119-
ValueParserConfiguration parserContext = () -> new SpelExpressionParser();
119+
ValueParserConfiguration parserContext = SpelExpressionParser::new;
120120

121121
ValueExpressionParser parser = ValueExpressionParser.create(parserContext);
122122
return (String) parser.parse(expressionString).evaluate(evaluationContext);

0 commit comments

Comments
 (0)