0
# Evaluation Contexts
1
2
This document covers SpEL's evaluation context implementations, providing the runtime environment for expression evaluation including variables, functions, and various resolvers.
3
4
## EvaluationContext Interface
5
6
### Core Interface Definition
7
8
```java
9
public interface EvaluationContext {
10
// Root object access
11
TypedValue getRootObject();
12
13
// Accessor chains
14
List<PropertyAccessor> getPropertyAccessors();
15
List<IndexAccessor> getIndexAccessors();
16
List<ConstructorResolver> getConstructorResolvers();
17
List<MethodResolver> getMethodResolvers();
18
19
// Specialized resolvers
20
BeanResolver getBeanResolver();
21
TypeLocator getTypeLocator();
22
TypeConverter getTypeConverter();
23
TypeComparator getTypeComparator();
24
OperatorOverloader getOperatorOverloader();
25
26
// Variable management
27
void setVariable(String name, Object value);
28
Object lookupVariable(String name);
29
30
// Assignment support (6.2+)
31
boolean isAssignmentEnabled();
32
TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier);
33
}
34
```
35
{ .api }
36
37
## StandardEvaluationContext
38
39
### Class Definition
40
41
```java
42
public class StandardEvaluationContext implements EvaluationContext {
43
// Constructors
44
public StandardEvaluationContext();
45
public StandardEvaluationContext(Object rootObject);
46
47
// Root object management
48
public void setRootObject(Object rootObject);
49
public void setRootObject(Object rootObject, TypeDescriptor typeDescriptor);
50
public TypedValue getRootObject();
51
52
// Accessor management
53
public void setPropertyAccessors(List<PropertyAccessor> propertyAccessors);
54
public List<PropertyAccessor> getPropertyAccessors();
55
public void addPropertyAccessor(PropertyAccessor accessor);
56
57
public void setIndexAccessors(List<IndexAccessor> indexAccessors);
58
public List<IndexAccessor> getIndexAccessors();
59
public void addIndexAccessor(IndexAccessor accessor);
60
61
// Resolver management
62
public void setConstructorResolvers(List<ConstructorResolver> constructorResolvers);
63
public List<ConstructorResolver> getConstructorResolvers();
64
public void addConstructorResolver(ConstructorResolver resolver);
65
66
public void setMethodResolvers(List<MethodResolver> methodResolvers);
67
public List<MethodResolver> getMethodResolvers();
68
public void addMethodResolver(MethodResolver resolver);
69
70
// Specialized component setters
71
public void setBeanResolver(BeanResolver beanResolver);
72
public BeanResolver getBeanResolver();
73
74
public void setTypeLocator(TypeLocator typeLocator);
75
public TypeLocator getTypeLocator();
76
77
public void setTypeConverter(TypeConverter typeConverter);
78
public TypeConverter getTypeConverter();
79
80
public void setTypeComparator(TypeComparator typeComparator);
81
public TypeComparator getTypeComparator();
82
83
public void setOperatorOverloader(OperatorOverloader operatorOverloader);
84
public OperatorOverloader getOperatorOverloader();
85
86
// Variable management
87
public void setVariables(Map<String, Object> variables);
88
public void setVariable(String name, Object value);
89
public Object lookupVariable(String name);
90
91
// Function registration
92
public void registerFunction(String name, Method method);
93
94
// Assignment support
95
public boolean isAssignmentEnabled();
96
public void setAssignmentEnabled(boolean assignmentEnabled);
97
public TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier);
98
}
99
```
100
{ .api }
101
102
### Basic Usage
103
104
```java
105
// Create with default settings
106
StandardEvaluationContext context = new StandardEvaluationContext();
107
108
// Create with root object
109
Person person = new Person("John", 30);
110
StandardEvaluationContext context = new StandardEvaluationContext(person);
111
112
// Set root object later
113
context.setRootObject(person);
114
115
// Basic expression evaluation
116
ExpressionParser parser = new SpelExpressionParser();
117
Expression exp = parser.parseExpression("name");
118
String name = exp.getValue(context, String.class); // "John"
119
```
120
{ .api }
121
122
### Variables and Functions
123
124
```java
125
StandardEvaluationContext context = new StandardEvaluationContext();
126
127
// Set individual variables
128
context.setVariable("greeting", "Hello");
129
context.setVariable("maxRetries", 3);
130
131
// Set multiple variables
132
Map<String, Object> variables = new HashMap<>();
133
variables.put("prefix", "Mr.");
134
variables.put("suffix", "Jr.");
135
context.setVariables(variables);
136
137
// Register functions
138
context.registerFunction("reverse",
139
StringUtils.class.getDeclaredMethod("reverse", String.class));
140
context.registerFunction("randomInt",
141
Math.class.getDeclaredMethod("random"));
142
143
// Use variables and functions in expressions
144
ExpressionParser parser = new SpelExpressionParser();
145
146
Expression exp = parser.parseExpression("#greeting + ' World'");
147
String result = exp.getValue(context, String.class); // "Hello World"
148
149
exp = parser.parseExpression("#reverse('hello')");
150
String reversed = exp.getValue(context, String.class);
151
152
exp = parser.parseExpression("#randomInt() * 100");
153
Double random = exp.getValue(context, Double.class);
154
```
155
{ .api }
156
157
### Custom Resolvers
158
159
```java
160
StandardEvaluationContext context = new StandardEvaluationContext();
161
162
// Add custom property accessor
163
context.addPropertyAccessor(new MapAccessor());
164
context.addPropertyAccessor(new ReflectivePropertyAccessor());
165
166
// Add custom method resolver
167
context.addMethodResolver(new ReflectiveMethodResolver());
168
169
// Set type locator with custom packages
170
StandardTypeLocator typeLocator = new StandardTypeLocator();
171
typeLocator.registerImport("com.myapp.model");
172
typeLocator.registerImport("com.myapp.util");
173
context.setTypeLocator(typeLocator);
174
175
// Set custom type converter
176
context.setTypeConverter(new StandardTypeConverter());
177
178
// Set bean resolver (typically in Spring context)
179
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
180
```
181
{ .api }
182
183
## SimpleEvaluationContext
184
185
### Class Definition and Builder
186
187
```java
188
public class SimpleEvaluationContext implements EvaluationContext {
189
// Static factory methods
190
public static Builder forReadOnlyDataBinding();
191
public static Builder forReadWriteDataBinding();
192
public static Builder forPropertyAccessors(PropertyAccessor... accessors);
193
194
// Builder class
195
public static final class Builder {
196
// Method access configuration
197
public Builder withInstanceMethods();
198
public Builder withStaticMethods();
199
200
// Collection access configuration
201
public Builder withArrayAccess();
202
public Builder withListAccess();
203
public Builder withMapAccess();
204
205
// Environment access
206
public Builder withEnvironmentAccess();
207
public Builder withSystemProperties();
208
public Builder withSystemEnvironment();
209
210
// Root object configuration
211
public Builder withTypedRootObject(Object rootObject);
212
public Builder withRootObject(Object rootObject);
213
214
// Type conversion
215
public Builder withConversionService(ConversionService conversionService);
216
public Builder withTypeConverter(TypeConverter typeConverter);
217
218
// Assignment control
219
public Builder withAssignmentDisabled();
220
221
// Build method
222
public SimpleEvaluationContext build();
223
}
224
}
225
```
226
{ .api }
227
228
### Read-Only Data Binding
229
230
```java
231
// Basic read-only context
232
SimpleEvaluationContext context = SimpleEvaluationContext
233
.forReadOnlyDataBinding()
234
.build();
235
236
// Read-only with method access
237
SimpleEvaluationContext context = SimpleEvaluationContext
238
.forReadOnlyDataBinding()
239
.withInstanceMethods()
240
.build();
241
242
// Read-only with collection access
243
SimpleEvaluationContext context = SimpleEvaluationContext
244
.forReadOnlyDataBinding()
245
.withArrayAccess()
246
.withListAccess()
247
.withMapAccess()
248
.build();
249
250
Person person = new Person("John", 30);
251
ExpressionParser parser = new SpelExpressionParser();
252
253
Expression exp = parser.parseExpression("name");
254
String name = exp.getValue(context, person, String.class); // "John"
255
256
exp = parser.parseExpression("name.length()"); // Requires withInstanceMethods()
257
Integer length = exp.getValue(context, person, Integer.class); // 4
258
```
259
{ .api }
260
261
### Read-Write Data Binding
262
263
```java
264
// Basic read-write context
265
SimpleEvaluationContext context = SimpleEvaluationContext
266
.forReadWriteDataBinding()
267
.build();
268
269
// Read-write with extended features
270
SimpleEvaluationContext context = SimpleEvaluationContext
271
.forReadWriteDataBinding()
272
.withInstanceMethods()
273
.withArrayAccess()
274
.withListAccess()
275
.withMapAccess()
276
.build();
277
278
Person person = new Person("John", 30);
279
ExpressionParser parser = new SpelExpressionParser();
280
281
// Reading
282
Expression exp = parser.parseExpression("name");
283
String name = exp.getValue(context, person, String.class); // "John"
284
285
// Writing
286
exp = parser.parseExpression("name");
287
exp.setValue(context, person, "Jane");
288
// person.name is now "Jane"
289
```
290
{ .api }
291
292
### Environment Access
293
294
```java
295
// Context with environment access
296
SimpleEvaluationContext context = SimpleEvaluationContext
297
.forReadOnlyDataBinding()
298
.withEnvironmentAccess()
299
.withSystemProperties()
300
.withSystemEnvironment()
301
.build();
302
303
ExpressionParser parser = new SpelExpressionParser();
304
305
// Access system properties
306
Expression exp = parser.parseExpression("systemProperties['java.version']");
307
String javaVersion = exp.getValue(context, String.class);
308
309
// Access environment variables
310
exp = parser.parseExpression("systemEnvironment['PATH']");
311
String path = exp.getValue(context, String.class);
312
313
// Access Spring environment (when available)
314
exp = parser.parseExpression("environment.getProperty('my.app.property')");
315
String property = exp.getValue(context, String.class);
316
```
317
{ .api }
318
319
### Custom Property Accessors
320
321
```java
322
// Create context with specific property accessors
323
PropertyAccessor customAccessor = new CustomPropertyAccessor();
324
PropertyAccessor mapAccessor = new MapAccessor();
325
326
SimpleEvaluationContext context = SimpleEvaluationContext
327
.forPropertyAccessors(customAccessor, mapAccessor)
328
.withInstanceMethods()
329
.build();
330
331
// Custom accessor implementation
332
public class CustomPropertyAccessor implements PropertyAccessor {
333
@Override
334
public Class<?>[] getSpecificTargetClasses() {
335
return new Class<?>[] { MyCustomClass.class };
336
}
337
338
@Override
339
public boolean canRead(EvaluationContext context, Object target, String name) {
340
return target instanceof MyCustomClass && "customProperty".equals(name);
341
}
342
343
@Override
344
public TypedValue read(EvaluationContext context, Object target, String name) {
345
if (target instanceof MyCustomClass && "customProperty".equals(name)) {
346
return new TypedValue(((MyCustomClass) target).getCustomValue());
347
}
348
throw new AccessException("Cannot read property: " + name);
349
}
350
351
@Override
352
public boolean canWrite(EvaluationContext context, Object target, String name) {
353
return false; // Read-only
354
}
355
356
@Override
357
public void write(EvaluationContext context, Object target, String name, Object newValue) {
358
throw new AccessException("Property is read-only: " + name);
359
}
360
}
361
```
362
{ .api }
363
364
### Type Conversion Configuration
365
366
```java
367
// Using ConversionService
368
ConversionService conversionService = new DefaultConversionService();
369
SimpleEvaluationContext context = SimpleEvaluationContext
370
.forReadOnlyDataBinding()
371
.withConversionService(conversionService)
372
.build();
373
374
// Using custom TypeConverter
375
TypeConverter customConverter = new CustomTypeConverter();
376
SimpleEvaluationContext context = SimpleEvaluationContext
377
.forReadOnlyDataBinding()
378
.withTypeConverter(customConverter)
379
.build();
380
381
// Custom type converter implementation
382
public class CustomTypeConverter implements TypeConverter {
383
private final ConversionService conversionService = new DefaultConversionService();
384
385
@Override
386
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
387
// Custom conversion logic
388
if (sourceType.getType() == String.class && targetType.getType() == MyCustomType.class) {
389
return true;
390
}
391
return conversionService.canConvert(sourceType, targetType);
392
}
393
394
@Override
395
public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) {
396
// Custom conversion implementation
397
if (sourceType.getType() == String.class && targetType.getType() == MyCustomType.class) {
398
return new MyCustomType((String) value);
399
}
400
return conversionService.convert(value, sourceType, targetType);
401
}
402
}
403
```
404
{ .api }
405
406
## Context Comparison
407
408
### StandardEvaluationContext vs SimpleEvaluationContext
409
410
| Feature | StandardEvaluationContext | SimpleEvaluationContext |
411
|---------|---------------------------|--------------------------|
412
| **Performance** | Slower due to reflection overhead | Faster, optimized for data binding |
413
| **Security** | Full SpEL features (potential security risks) | Restricted features (more secure) |
414
| **Flexibility** | Highly customizable | Limited but sufficient for most use cases |
415
| **Method Invocation** | All methods by default | Opt-in via builder |
416
| **Type References** | T(Class) syntax supported | Not supported |
417
| **Constructor Calls** | new Constructor() supported | Not supported |
418
| **Bean References** | @bean syntax supported | Not supported |
419
| **Assignment** | Supported by default | Opt-in via builder |
420
| **Thread Safety** | Not thread-safe | Thread-safe when immutable |
421
422
### Choosing the Right Context
423
424
```java
425
// Use SimpleEvaluationContext for:
426
// - Data binding scenarios
427
// - Security-sensitive environments
428
// - Performance-critical applications
429
// - Web applications with user input
430
431
SimpleEvaluationContext secureContext = SimpleEvaluationContext
432
.forReadOnlyDataBinding()
433
.withInstanceMethods()
434
.build();
435
436
// Use StandardEvaluationContext for:
437
// - Full SpEL feature requirements
438
// - Complex expression scenarios
439
// - Integration with Spring frameworks
440
// - Development and testing environments
441
442
StandardEvaluationContext fullContext = new StandardEvaluationContext();
443
```
444
{ .api }
445
446
## Advanced Context Usage
447
448
### Context Inheritance and Chaining
449
450
```java
451
// Base context with common configuration
452
StandardEvaluationContext baseContext = new StandardEvaluationContext();
453
baseContext.setVariable("appName", "MyApplication");
454
baseContext.setVariable("version", "1.0.0");
455
baseContext.registerFunction("formatDate",
456
DateUtils.class.getDeclaredMethod("format", Date.class, String.class));
457
458
// Specialized context inheriting from base
459
StandardEvaluationContext userContext = new StandardEvaluationContext();
460
userContext.setVariables(baseContext.lookupVariable("appName")); // Copy variables
461
userContext.setVariable("username", "john.doe");
462
userContext.setRootObject(currentUser);
463
464
// Context for specific operations
465
StandardEvaluationContext operationContext = new StandardEvaluationContext(userContext.getRootObject());
466
operationContext.setVariable("operation", "UPDATE");
467
operationContext.setVariable("timestamp", new Date());
468
```
469
{ .api }
470
471
### Dynamic Context Configuration
472
473
```java
474
public class DynamicContextFactory {
475
476
public EvaluationContext createContext(String profile, Object rootObject) {
477
switch (profile.toLowerCase()) {
478
case "secure":
479
return SimpleEvaluationContext
480
.forReadOnlyDataBinding()
481
.withRootObject(rootObject)
482
.build();
483
484
case "standard":
485
return SimpleEvaluationContext
486
.forReadWriteDataBinding()
487
.withInstanceMethods()
488
.withArrayAccess()
489
.withListAccess()
490
.withMapAccess()
491
.withRootObject(rootObject)
492
.build();
493
494
case "full":
495
StandardEvaluationContext context = new StandardEvaluationContext(rootObject);
496
context.addPropertyAccessor(new ReflectivePropertyAccessor());
497
context.addMethodResolver(new ReflectiveMethodResolver());
498
return context;
499
500
default:
501
throw new IllegalArgumentException("Unknown profile: " + profile);
502
}
503
}
504
}
505
```
506
{ .api }
507
508
### Context Pooling for Performance
509
510
```java
511
public class EvaluationContextPool {
512
private final Queue<StandardEvaluationContext> pool = new ConcurrentLinkedQueue<>();
513
private final int maxSize;
514
515
public EvaluationContextPool(int maxSize) {
516
this.maxSize = maxSize;
517
}
518
519
public StandardEvaluationContext borrowContext() {
520
StandardEvaluationContext context = pool.poll();
521
if (context == null) {
522
context = new StandardEvaluationContext();
523
configureContext(context);
524
}
525
return context;
526
}
527
528
public void returnContext(StandardEvaluationContext context) {
529
if (pool.size() < maxSize) {
530
clearContext(context);
531
pool.offer(context);
532
}
533
}
534
535
private void configureContext(StandardEvaluationContext context) {
536
// Standard configuration
537
context.addPropertyAccessor(new ReflectivePropertyAccessor());
538
context.addMethodResolver(new ReflectiveMethodResolver());
539
}
540
541
private void clearContext(StandardEvaluationContext context) {
542
// Clear variables and root object for reuse
543
context.setRootObject(null);
544
context.setVariable("temp", null); // Clear known temporary variables
545
}
546
}
547
```
548
{ .api }
549
550
## Best Practices
551
552
### Security Best Practices
553
554
```java
555
// 1. Use SimpleEvaluationContext for user-provided expressions
556
SimpleEvaluationContext secureContext = SimpleEvaluationContext
557
.forReadOnlyDataBinding()
558
.build(); // Minimal permissions
559
560
// 2. Validate expressions before evaluation
561
public boolean isSafeExpression(String expression) {
562
// Check for dangerous patterns
563
return !expression.contains("T(") && // No type references
564
!expression.contains("new ") && // No constructor calls
565
!expression.contains("@") && // No bean references
566
!expression.contains("#") && // No variables (if not needed)
567
expression.length() < 200; // Reasonable length limit
568
}
569
570
// 3. Use whitelisted property accessors
571
public class WhitelistPropertyAccessor implements PropertyAccessor {
572
private final Set<String> allowedProperties = Set.of("name", "id", "status");
573
574
@Override
575
public boolean canRead(EvaluationContext context, Object target, String name) {
576
return allowedProperties.contains(name) &&
577
target instanceof SafeDataObject;
578
}
579
580
// ... other methods
581
}
582
```
583
{ .api }
584
585
### Performance Best Practices
586
587
```java
588
// 1. Reuse contexts when possible (SimpleEvaluationContext)
589
public class ContextManager {
590
private final SimpleEvaluationContext sharedReadOnlyContext =
591
SimpleEvaluationContext.forReadOnlyDataBinding().build();
592
593
public Object evaluateReadOnly(Expression expression, Object root) {
594
return expression.getValue(sharedReadOnlyContext, root);
595
}
596
}
597
598
// 2. Cache context configurations
599
public class CachedContextFactory {
600
private final Map<String, EvaluationContext> contextCache = new ConcurrentHashMap<>();
601
602
public EvaluationContext getContext(String type) {
603
return contextCache.computeIfAbsent(type, this::createContext);
604
}
605
606
private EvaluationContext createContext(String type) {
607
// Create context based on type
608
return SimpleEvaluationContext.forReadOnlyDataBinding().build();
609
}
610
}
611
612
// 3. Minimize context setup overhead
613
public EvaluationContext createOptimizedContext(Object root) {
614
return SimpleEvaluationContext
615
.forReadOnlyDataBinding()
616
.withRootObject(root) // Set root directly
617
.build(); // No additional configuration
618
}
619
```
620
{ .api }
621
622
### Thread Safety Considerations
623
624
```java
625
// SimpleEvaluationContext is thread-safe when immutable
626
public class ThreadSafeExpressionEvaluator {
627
private final SimpleEvaluationContext sharedContext =
628
SimpleEvaluationContext.forReadOnlyDataBinding().build();
629
private final ExpressionParser parser = new SpelExpressionParser();
630
631
public Object evaluate(String expression, Object root) {
632
// Safe to share context across threads for read-only operations
633
Expression exp = parser.parseExpression(expression);
634
return exp.getValue(sharedContext, root);
635
}
636
}
637
638
// StandardEvaluationContext requires thread-local instances
639
public class ThreadLocalContextEvaluator {
640
private final ThreadLocal<StandardEvaluationContext> contextHolder =
641
ThreadLocal.withInitial(this::createContext);
642
643
private StandardEvaluationContext createContext() {
644
StandardEvaluationContext context = new StandardEvaluationContext();
645
// Configure context...
646
return context;
647
}
648
649
public Object evaluate(String expression, Object root) {
650
StandardEvaluationContext context = contextHolder.get();
651
context.setRootObject(root);
652
653
ExpressionParser parser = new SpelExpressionParser();
654
Expression exp = parser.parseExpression(expression);
655
return exp.getValue(context);
656
}
657
}
658
```
659
{ .api }