@@ -250,3 +250,115 @@ Kotlin::
250
250
----
251
251
======
252
252
253
+ [[expressions-indexing-custom]]
254
+ == Indexing into Custom Structures
255
+
256
+ Since Spring Framework 6.2, the Spring Expression Language supports indexing into custom
257
+ structures by allowing developers to implement and register an `IndexAccessor` with the
258
+ `EvaluationContext`. If you would like to support
259
+ xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation] of
260
+ expressions that rely on a custom index accessor, that index accessor must implement the
261
+ `CompilableIndexAccessor` SPI.
262
+
263
+ To support common use cases, Spring provides a built-in `ReflectiveIndexAccessor` which
264
+ is a flexible `IndexAccessor` that uses reflection to read from and optionally write to
265
+ an indexed structure of a target object. The indexed structure can be accessed through a
266
+ `public` read-method (when being read) or a `public` write-method (when being written).
267
+ The relationship between the read-method and write-method is based on a convention that
268
+ is applicable for typical implementations of indexed structures.
269
+
270
+ NOTE: `ReflectiveIndexAccessor` also implements `CompilableIndexAccessor` in order to
271
+ support xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation]
272
+ to bytecode for read access. Note, however, that the configured read-method must be
273
+ invokable via a `public` class or `public` interface for compilation to succeed.
274
+
275
+ The following code listings define a `Color` enum and `FruitMap` type that behaves like a
276
+ map but does not implement the `java.util.Map` interface. Thus, if you want to index into
277
+ a `FruitMap` within a SpEL expression, you will need to register an `IndexAccessor`.
278
+
279
+ [source,java,indent=0,subs="verbatim,quotes"]
280
+ ----
281
+ package example;
282
+
283
+ public enum Color {
284
+ RED, ORANGE, YELLOW
285
+ }
286
+ ----
287
+
288
+ [source,java,indent=0,subs="verbatim,quotes"]
289
+ ----
290
+ public class FruitMap {
291
+
292
+ private final Map<Color, String> map = new HashMap<>();
293
+
294
+ public FruitMap() {
295
+ this.map.put(Color.RED, "cherry");
296
+ this.map.put(Color.ORANGE, "orange");
297
+ this.map.put(Color.YELLOW, "banana");
298
+ }
299
+
300
+ public String getFruit(Color color) {
301
+ return this.map.get(color);
302
+ }
303
+
304
+ public void setFruit(Color color, String fruit) {
305
+ this.map.put(color, fruit);
306
+ }
307
+ }
308
+ ----
309
+
310
+ A read-only `IndexAccessor` for `FruitMap` can be created via `new
311
+ ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit")`. With that accessor
312
+ registered and a `FruitMap` registered as a variable named `#fruitMap`, the SpEL
313
+ expression `#fruitMap[T(example.Color).RED]` will evaluate to `"cherry"`.
314
+
315
+ A read-write `IndexAccessor` for `FruitMap` can be created via `new
316
+ ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit", "setFruit")`. With that
317
+ accessor registered and a `FruitMap` registered as a variable named `#fruitMap`, the SpEL
318
+ expression `#fruitMap[T(example.Color).RED] = 'strawberry'` can be used to change the
319
+ fruit mapping for the color red from `"cherry"` to `"strawberry"`.
320
+
321
+ The following example demonstrates how to register a `ReflectiveIndexAccessor` to index
322
+ into a `FruitMap` and then index into the `FruitMap` within a SpEL expression.
323
+
324
+ [tabs]
325
+ ======
326
+ Java::
327
+ +
328
+ [source,java,indent=0,subs="verbatim,quotes",role="primary"]
329
+ ----
330
+ // Create a ReflectiveIndexAccessor for FruitMap
331
+ IndexAccessor fruitMapAccessor = new ReflectiveIndexAccessor(
332
+ FruitMap.class, Color.class, "getFruit", "setFruit");
333
+
334
+ // Register the IndexAccessor for FruitMap
335
+ context.addIndexAccessor(fruitMapAccessor);
336
+
337
+ // Register the fruitMap variable
338
+ context.setVariable("fruitMap", new FruitMap());
339
+
340
+ // evaluates to "cherry"
341
+ String fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
342
+ .getValue(context, String.class);
343
+ ----
344
+
345
+ Kotlin::
346
+ +
347
+ [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
348
+ ----
349
+ // Create a ReflectiveIndexAccessor for FruitMap
350
+ val fruitMapAccessor = ReflectiveIndexAccessor(
351
+ FruitMap::class.java, Color::class.java, "getFruit", "setFruit")
352
+
353
+ // Register the IndexAccessor for FruitMap
354
+ context.addIndexAccessor(fruitMapAccessor)
355
+
356
+ // Register the fruitMap variable
357
+ context.setVariable("fruitMap", FruitMap())
358
+
359
+ // evaluates to "cherry"
360
+ val fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
361
+ .getValue(context, String::class.java)
362
+ ----
363
+ ======
364
+
0 commit comments