Support Optional with null-safe and Elvis operators in SpEL expressions
This commit introduces null-safe support for java.util.Optional in the
following SpEL operators:
- PropertyOrFieldReference
- MethodReference
- Indexer
- Projection
- Selection
- Elvis
Specifically, when a null-safe operator is applied to an empty
`Optional`, it will be treated as if the `Optional` were `null`, and
the subsequent operation will evaluate to `null`. However, if a
null-safe operator is applied to a non-empty `Optional`, the subsequent
operation will be applied to the object contained in the `Optional`,
thereby effectively unwrapping the `Optional`.
For example, if `user` is of type `Optional<User>`, the expression
`user?.name` will evaluate to `null` if `user` is either `null` or an
empty `Optional` and will otherwise evaluate to the `name` of the
`user`, effectively `user.get().getName()` for property access.
Note, however, that invocations of methods defined in the `Optional`
API are still supported on an empty `Optional`. For example, if `name`
is of type `Optional<String>`, the expression `name?.orElse('Unknown')`
will evaluate to "Unknown" if `name` is an empty `Optional` and will
otherwise evaluate to the `String` contained in the `Optional` if
`name` is a non-empty `Optional`, effectively `name.get()`.
Closes gh-20433
@ -46,6 +46,17 @@ need to use `name != null && !name.isEmpty()` as the predicate to be compatible
@@ -46,6 +46,17 @@ need to use `name != null && !name.isEmpty()` as the predicate to be compatible
semantics of the SpEL Elvis operator.
====
[TIP]
====
As of Spring Framework 7.0, the SpEL Elvis operator supports `java.util.Optional` with
transparent unwrapping semantics.
For example, given the expression `A ?: B`, if `A` is `null` or an _empty_ `Optional`,
the expression evaluates to `B`. However, if `A` is a non-empty `Optional` the expression
evaluates to the object contained in the `Optional`, thereby effectively unwrapping the
`Optional` which correlates to `A.get()`.
====
The following listing shows a more complex example:
@ -179,9 +192,24 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@@ -179,9 +192,24 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@ -205,6 +233,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@@ -205,6 +233,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
// then ask them to read it.
try{
for(PropertyAccessoraccessor:accessorsToTry){
// First, attempt to find the property on the target object.
@ -213,18 +242,34 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@@ -213,18 +242,34 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
this.cachedReadAccessor=accessor;
returnaccessor.read(evalContext,target,name);
}
// Second, attempt to find the property on the original Optional instance.