0
# Advanced Features
1
2
This document covers SpEL's advanced features including expression compilation, custom functions, variables, bean references, collection operations, and integration patterns.
3
4
## Expression Compilation
5
6
### Compilation Basics
7
8
SpEL can compile frequently-used expressions to Java bytecode for improved performance. Compilation is controlled through `SpelParserConfiguration`.
9
10
```java
11
// Enable immediate compilation
12
SpelParserConfiguration config = new SpelParserConfiguration(
13
SpelCompilerMode.IMMEDIATE,
14
Thread.currentThread().getContextClassLoader()
15
);
16
SpelExpressionParser parser = new SpelExpressionParser(config);
17
18
// Parse and compile
19
SpelExpression expression = parser.parseRaw("name.toUpperCase() + ' (' + age + ')'");
20
21
// First evaluation compiles the expression
22
String result1 = expression.getValue(person, String.class);
23
24
// Subsequent evaluations use compiled bytecode (much faster)
25
String result2 = expression.getValue(person, String.class);
26
27
// Check compilation status
28
boolean isCompiled = expression.compileExpression();
29
System.out.println("Expression compiled: " + isCompiled);
30
```
31
{ .api }
32
33
### Compilation Modes
34
35
```java
36
// OFF: No compilation (default)
37
SpelParserConfiguration offConfig = new SpelParserConfiguration(
38
SpelCompilerMode.OFF, null
39
);
40
41
// IMMEDIATE: Compile after first evaluation
42
SpelParserConfiguration immediateConfig = new SpelParserConfiguration(
43
SpelCompilerMode.IMMEDIATE,
44
Thread.currentThread().getContextClassLoader()
45
);
46
47
// MIXED: Adaptive compilation based on usage patterns
48
SpelParserConfiguration mixedConfig = new SpelParserConfiguration(
49
SpelCompilerMode.MIXED,
50
Thread.currentThread().getContextClassLoader()
51
);
52
```
53
{ .api }
54
55
### Manual Compilation Control
56
57
```java
58
SpelExpressionParser parser = new SpelExpressionParser();
59
SpelExpression expression = parser.parseRaw("items.![price * quantity].sum()");
60
61
// Force compilation
62
boolean compilationSuccessful = expression.compileExpression();
63
if (compilationSuccessful) {
64
System.out.println("Expression successfully compiled");
65
} else {
66
System.out.println("Expression could not be compiled - will use interpretation");
67
}
68
69
// Check if expression is currently compiled
70
if (expression.compileExpression()) {
71
System.out.println("Using compiled version");
72
}
73
74
// Revert to interpreted mode (useful for debugging)
75
expression.revertToInterpreted();
76
System.out.println("Reverted to interpreted mode");
77
```
78
{ .api }
79
80
## Custom Functions and Variables
81
82
### Function Registration
83
84
```java
85
public class MathFunctions {
86
public static double sqrt(double value) {
87
return Math.sqrt(value);
88
}
89
90
public static long factorial(int n) {
91
return n <= 1 ? 1 : n * factorial(n - 1);
92
}
93
94
public static double distance(double x1, double y1, double x2, double y2) {
95
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
96
}
97
98
public static String format(String template, Object... args) {
99
return String.format(template, args);
100
}
101
}
102
103
StandardEvaluationContext context = new StandardEvaluationContext();
104
105
// Register static methods as functions
106
context.registerFunction("sqrt",
107
MathFunctions.class.getDeclaredMethod("sqrt", double.class));
108
context.registerFunction("factorial",
109
MathFunctions.class.getDeclaredMethod("factorial", int.class));
110
context.registerFunction("distance",
111
MathFunctions.class.getDeclaredMethod("distance", double.class, double.class, double.class, double.class));
112
context.registerFunction("format",
113
MathFunctions.class.getDeclaredMethod("format", String.class, Object[].class));
114
115
ExpressionParser parser = new SpelExpressionParser();
116
117
// Use registered functions
118
Expression exp = parser.parseExpression("#sqrt(16)");
119
Double result = exp.getValue(context, Double.class); // 4.0
120
121
exp = parser.parseExpression("#factorial(5)");
122
Long factorial = exp.getValue(context, Long.class); // 120
123
124
exp = parser.parseExpression("#distance(0, 0, 3, 4)");
125
Double distance = exp.getValue(context, Double.class); // 5.0
126
127
exp = parser.parseExpression("#format('Hello %s, you are %d years old', 'John', 30)");
128
String formatted = exp.getValue(context, String.class); // "Hello John, you are 30 years old"
129
```
130
{ .api }
131
132
### Variable Management
133
134
```java
135
StandardEvaluationContext context = new StandardEvaluationContext();
136
137
// Set individual variables
138
context.setVariable("pi", Math.PI);
139
context.setVariable("greeting", "Hello");
140
context.setVariable("maxRetries", 3);
141
142
// Set multiple variables at once
143
Map<String, Object> variables = new HashMap<>();
144
variables.put("appName", "MyApplication");
145
variables.put("version", "1.0.0");
146
variables.put("debug", true);
147
context.setVariables(variables);
148
149
ExpressionParser parser = new SpelExpressionParser();
150
151
// Use variables in expressions
152
Expression exp = parser.parseExpression("2 * #pi");
153
Double circumference = exp.getValue(context, Double.class);
154
155
exp = parser.parseExpression("#greeting + ' World'");
156
String message = exp.getValue(context, String.class); // "Hello World"
157
158
exp = parser.parseExpression("#debug ? 'Debug mode' : 'Production mode'");
159
String mode = exp.getValue(context, String.class); // "Debug mode"
160
161
// Complex variable usage
162
exp = parser.parseExpression("#appName + ' v' + #version + (#debug ? ' (DEBUG)' : '')");
163
String fullName = exp.getValue(context, String.class); // "MyApplication v1.0.0 (DEBUG)"
164
```
165
{ .api }
166
167
### Dynamic Variable Creation
168
169
```java
170
public class DynamicVariableProvider {
171
private final Map<String, Supplier<Object>> dynamicVariables = new HashMap<>();
172
173
public void registerDynamicVariable(String name, Supplier<Object> provider) {
174
dynamicVariables.put(name, provider);
175
}
176
177
public StandardEvaluationContext createContext() {
178
StandardEvaluationContext context = new StandardEvaluationContext();
179
180
// Set dynamic variables
181
dynamicVariables.forEach((name, provider) -> {
182
context.setVariable(name, provider.get());
183
});
184
185
return context;
186
}
187
}
188
189
DynamicVariableProvider provider = new DynamicVariableProvider();
190
provider.registerDynamicVariable("currentTime", System::currentTimeMillis);
191
provider.registerDynamicVariable("randomId", () -> UUID.randomUUID().toString());
192
provider.registerDynamicVariable("freeMemory", () -> Runtime.getRuntime().freeMemory());
193
194
// Each time we create a context, variables have fresh values
195
EvaluationContext context1 = provider.createContext();
196
EvaluationContext context2 = provider.createContext(); // Different random values
197
198
ExpressionParser parser = new SpelExpressionParser();
199
Expression exp = parser.parseExpression("'ID: ' + #randomId + ', Time: ' + #currentTime");
200
201
String result1 = exp.getValue(context1, String.class);
202
String result2 = exp.getValue(context2, String.class);
203
// Results will have different random IDs and timestamps
204
```
205
{ .api }
206
207
## Bean References (Spring Integration)
208
209
### Bean Resolver Configuration
210
211
```java
212
// In a Spring application context
213
@Configuration
214
public class SpelConfiguration {
215
216
@Bean
217
public StandardEvaluationContext evaluationContext(ApplicationContext applicationContext) {
218
StandardEvaluationContext context = new StandardEvaluationContext();
219
220
// Enable bean references (@beanName syntax)
221
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
222
223
return context;
224
}
225
}
226
227
// Service beans
228
@Service
229
public class UserService {
230
public User findById(Long id) {
231
// Implementation...
232
return new User(id, "John Doe");
233
}
234
235
public List<User> findAll() {
236
// Implementation...
237
return Arrays.asList(
238
new User(1L, "John Doe"),
239
new User(2L, "Jane Smith")
240
);
241
}
242
}
243
244
@Service
245
public class EmailService {
246
public void sendEmail(String to, String subject, String body) {
247
System.out.printf("Sending email to %s: %s%n", to, subject);
248
}
249
}
250
```
251
{ .api }
252
253
### Using Bean References in Expressions
254
255
```java
256
// Assuming Spring context with beans
257
ExpressionParser parser = new SpelExpressionParser();
258
259
// Reference beans using @ syntax
260
Expression exp = parser.parseExpression("@userService.findById(1)");
261
User user = exp.getValue(context, User.class);
262
263
exp = parser.parseExpression("@userService.findAll().size()");
264
Integer userCount = exp.getValue(context, Integer.class);
265
266
// Complex bean method chaining
267
exp = parser.parseExpression("@userService.findById(1).getName().toUpperCase()");
268
String upperName = exp.getValue(context, String.class);
269
270
// Bean references in conditional expressions
271
exp = parser.parseExpression("@userService.findById(1) != null ? 'User found' : 'User not found'");
272
String message = exp.getValue(context, String.class);
273
274
// Using beans with variables
275
context.setVariable("userId", 42L);
276
exp = parser.parseExpression("@userService.findById(#userId)");
277
User userById = exp.getValue(context, User.class);
278
```
279
{ .api }
280
281
### Factory Bean Access
282
283
```java
284
// Access factory beans using &beanName syntax
285
Expression exp = parser.parseExpression("&myFactoryBean");
286
FactoryBean<?> factory = exp.getValue(context, FactoryBean.class);
287
288
// Get the actual object created by the factory
289
exp = parser.parseExpression("@myFactoryBean");
290
Object factoryProduct = exp.getValue(context);
291
```
292
{ .api }
293
294
## Collection Operations
295
296
### Collection Selection (Filtering)
297
298
```java
299
public class Product {
300
private String name;
301
private double price;
302
private String category;
303
private boolean inStock;
304
305
// constructors, getters, setters...
306
}
307
308
List<Product> products = Arrays.asList(
309
new Product("Laptop", 999.99, "Electronics", true),
310
new Product("Mouse", 29.99, "Electronics", false),
311
new Product("Book", 19.99, "Books", true),
312
new Product("Keyboard", 79.99, "Electronics", true)
313
);
314
315
StandardEvaluationContext context = new StandardEvaluationContext();
316
context.setVariable("products", products);
317
318
ExpressionParser parser = new SpelExpressionParser();
319
320
// Selection: filter collection using .?[condition]
321
Expression exp = parser.parseExpression("#products.?[inStock == true]");
322
List<Product> inStockProducts = (List<Product>) exp.getValue(context);
323
324
// Filter by price range
325
exp = parser.parseExpression("#products.?[price >= 50 and price <= 100]");
326
List<Product> midRangeProducts = (List<Product>) exp.getValue(context);
327
328
// Filter by category
329
exp = parser.parseExpression("#products.?[category == 'Electronics']");
330
List<Product> electronics = (List<Product>) exp.getValue(context);
331
332
// Complex filtering
333
exp = parser.parseExpression("#products.?[category == 'Electronics' and inStock and price > 50]");
334
List<Product> availableElectronics = (List<Product>) exp.getValue(context);
335
336
// First match: .^[condition]
337
exp = parser.parseExpression("#products.^[price > 100]");
338
Product firstExpensive = (Product) exp.getValue(context);
339
340
// Last match: .$[condition]
341
exp = parser.parseExpression("#products.$[inStock == true]");
342
Product lastInStock = (Product) exp.getValue(context);
343
```
344
{ .api }
345
346
### Collection Projection (Transformation)
347
348
```java
349
// Projection: transform collection using .![expression]
350
Expression exp = parser.parseExpression("#products.![name]");
351
List<String> productNames = (List<String>) exp.getValue(context);
352
// ["Laptop", "Mouse", "Book", "Keyboard"]
353
354
exp = parser.parseExpression("#products.![name + ' - $' + price]");
355
List<String> nameAndPrice = (List<String>) exp.getValue(context);
356
// ["Laptop - $999.99", "Mouse - $29.99", ...]
357
358
// Complex projections
359
exp = parser.parseExpression("#products.![name.toUpperCase() + (inStock ? ' (Available)' : ' (Out of Stock)')]");
360
List<String> statusList = (List<String>) exp.getValue(context);
361
362
// Nested object projection
363
public class Order {
364
private List<Product> items;
365
// constructor, getters, setters...
366
}
367
368
List<Order> orders = Arrays.asList(
369
new Order(Arrays.asList(products.get(0), products.get(1))),
370
new Order(Arrays.asList(products.get(2)))
371
);
372
context.setVariable("orders", orders);
373
374
// Project nested collections
375
exp = parser.parseExpression("#orders.![items.![name]]");
376
List<List<String>> nestedNames = (List<List<String>>) exp.getValue(context);
377
378
// Flatten nested projections
379
exp = parser.parseExpression("#orders.![items].flatten()");
380
List<Product> allOrderItems = (List<Product>) exp.getValue(context);
381
```
382
{ .api }
383
384
### Map Operations
385
386
```java
387
Map<String, Integer> inventory = new HashMap<>();
388
inventory.put("laptop", 10);
389
inventory.put("mouse", 50);
390
inventory.put("keyboard", 25);
391
392
context.setVariable("inventory", inventory);
393
394
// Map key selection
395
Expression exp = parser.parseExpression("#inventory.?[value > 20]");
396
Map<String, Integer> highInventory = (Map<String, Integer>) exp.getValue(context);
397
// {mouse=50, keyboard=25}
398
399
// Map projection (values)
400
exp = parser.parseExpression("#inventory.![value]");
401
List<Integer> inventoryCounts = (List<Integer>) exp.getValue(context);
402
// [10, 50, 25]
403
404
// Map projection (keys)
405
exp = parser.parseExpression("#inventory.![key]");
406
List<String> inventoryItems = (List<String>) exp.getValue(context);
407
// ["laptop", "mouse", "keyboard"]
408
409
// Complex map operations
410
exp = parser.parseExpression("#inventory.?[value < 30].![key + ': ' + value]");
411
List<String> lowInventoryReport = (List<String>) exp.getValue(context);
412
// ["laptop: 10", "keyboard: 25"]
413
```
414
{ .api }
415
416
### Aggregation Operations
417
418
```java
419
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
420
context.setVariable("numbers", numbers);
421
422
// Custom aggregation functions
423
context.registerFunction("sum",
424
Arrays.class.getDeclaredMethod("stream", Object[].class));
425
426
// Sum using projection and custom function
427
Expression exp = parser.parseExpression("#products.![price].stream().mapToDouble(p -> p).sum()");
428
// Note: This requires Java 8 streams support in the context
429
430
// Alternative: Calculate sum manually
431
exp = parser.parseExpression("#products.![price]");
432
List<Double> prices = (List<Double>) exp.getValue(context);
433
double totalPrice = prices.stream().mapToDouble(Double::doubleValue).sum();
434
435
// Count operations
436
exp = parser.parseExpression("#products.?[inStock == true].size()");
437
Integer inStockCount = exp.getValue(context, Integer.class);
438
439
// Min/Max operations (requires custom functions or preprocessing)
440
public class CollectionFunctions {
441
public static Double max(List<Double> values) {
442
return values.stream().mapToDouble(Double::doubleValue).max().orElse(0.0);
443
}
444
445
public static Double min(List<Double> values) {
446
return values.stream().mapToDouble(Double::doubleValue).min().orElse(0.0);
447
}
448
449
public static Double avg(List<Double> values) {
450
return values.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
451
}
452
}
453
454
context.registerFunction("max",
455
CollectionFunctions.class.getDeclaredMethod("max", List.class));
456
context.registerFunction("min",
457
CollectionFunctions.class.getDeclaredMethod("min", List.class));
458
context.registerFunction("avg",
459
CollectionFunctions.class.getDeclaredMethod("avg", List.class));
460
461
// Use aggregation functions
462
exp = parser.parseExpression("#max(#products.![price])");
463
Double maxPrice = exp.getValue(context, Double.class);
464
465
exp = parser.parseExpression("#avg(#products.![price])");
466
Double avgPrice = exp.getValue(context, Double.class);
467
```
468
{ .api }
469
470
## Advanced Operators and Expressions
471
472
### Safe Navigation Operator
473
474
```java
475
public class Address {
476
private String city;
477
private String country;
478
// constructors, getters, setters...
479
}
480
481
public class Person {
482
private String name;
483
private Address address; // might be null
484
// constructors, getters, setters...
485
}
486
487
Person person = new Person("John");
488
// address is null
489
490
ExpressionParser parser = new SpelExpressionParser();
491
492
// Safe navigation prevents NullPointerException
493
Expression exp = parser.parseExpression("address?.city");
494
String city = exp.getValue(person, String.class); // null, no exception
495
496
// Without safe navigation would throw NPE:
497
// exp = parser.parseExpression("address.city"); // NullPointerException
498
499
// Chained safe navigation
500
exp = parser.parseExpression("address?.city?.toUpperCase()");
501
String upperCity = exp.getValue(person, String.class); // null
502
503
// Safe navigation with method calls
504
exp = parser.parseExpression("address?.toString()?.length()");
505
Integer addressLength = exp.getValue(person, Integer.class); // null
506
```
507
{ .api }
508
509
### Elvis Operator
510
511
```java
512
ExpressionParser parser = new SpelExpressionParser();
513
StandardEvaluationContext context = new StandardEvaluationContext();
514
515
// Elvis operator provides default values for null
516
context.setVariable("name", null);
517
Expression exp = parser.parseExpression("#name ?: 'Unknown'");
518
String name = exp.getValue(context, String.class); // "Unknown"
519
520
context.setVariable("name", "John");
521
name = exp.getValue(context, String.class); // "John"
522
523
// Elvis with method calls
524
Person person = new Person(null); // name is null
525
exp = parser.parseExpression("name?.toUpperCase() ?: 'NO NAME'");
526
String upperName = exp.getValue(person, String.class); // "NO NAME"
527
528
// Complex elvis expressions
529
exp = parser.parseExpression("address?.city ?: address?.country ?: 'Unknown Location'");
530
String location = exp.getValue(person, String.class); // "Unknown Location"
531
532
// Elvis with collection operations
533
context.setVariable("items", null);
534
exp = parser.parseExpression("#items ?: {}"); // Empty map as default
535
Map<String, Object> items = (Map<String, Object>) exp.getValue(context);
536
537
context.setVariable("list", null);
538
exp = parser.parseExpression("#list ?: {1, 2, 3}"); // Default list
539
List<Integer> list = (List<Integer>) exp.getValue(context);
540
```
541
{ .api }
542
543
### Regular Expression Matching
544
545
```java
546
ExpressionParser parser = new SpelExpressionParser();
547
StandardEvaluationContext context = new StandardEvaluationContext();
548
549
// Basic pattern matching
550
context.setVariable("email", "user@example.com");
551
Expression exp = parser.parseExpression("#email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}'");
552
Boolean isValidEmail = exp.getValue(context, Boolean.class); // true
553
554
// Case-insensitive matching
555
context.setVariable("text", "Hello World");
556
exp = parser.parseExpression("#text matches '(?i)hello.*'");
557
Boolean matches = exp.getValue(context, Boolean.class); // true
558
559
// Pattern matching in filtering
560
List<String> names = Arrays.asList("John", "Jane", "Bob", "Alice", "Andrew");
561
context.setVariable("names", names);
562
563
exp = parser.parseExpression("#names.?[#this matches 'A.*']"); // Names starting with 'A'
564
List<String> aNames = (List<String>) exp.getValue(context); // ["Alice", "Andrew"]
565
566
// Complex pattern matching
567
context.setVariable("phoneNumber", "+1-555-123-4567");
568
exp = parser.parseExpression("#phoneNumber matches '\\+1-\\d{3}-\\d{3}-\\d{4}'");
569
Boolean isValidPhone = exp.getValue(context, Boolean.class); // true
570
571
// URL validation
572
context.setVariable("url", "https://www.example.com/path?param=value");
573
exp = parser.parseExpression("#url matches 'https?://[\\w.-]+(:[0-9]+)?(/.*)?'");
574
Boolean isValidUrl = exp.getValue(context, Boolean.class); // true
575
```
576
{ .api }
577
578
## Expression Templates and Composition
579
580
### Template Expressions
581
582
```java
583
ExpressionParser parser = new SpelExpressionParser();
584
StandardEvaluationContext context = new StandardEvaluationContext();
585
586
Map<String, Object> user = new HashMap<>();
587
user.put("firstName", "John");
588
user.put("lastName", "Doe");
589
user.put("age", 30);
590
user.put("city", "New York");
591
592
context.setVariables(user);
593
594
// Template with multiple expressions
595
String template = "Hello #{#firstName} #{#lastName}! You are #{#age} years old and live in #{#city}.";
596
Expression exp = parser.parseExpression(template, ParserContext.TEMPLATE_EXPRESSION);
597
String message = exp.getValue(context, String.class);
598
// "Hello John Doe! You are 30 years old and live in New York."
599
600
// Conditional templates
601
template = "Welcome #{#firstName}#{#age >= 18 ? ' (Adult)' : ' (Minor)'}!";
602
exp = parser.parseExpression(template, ParserContext.TEMPLATE_EXPRESSION);
603
String welcome = exp.getValue(context, String.class);
604
// "Welcome John (Adult)!"
605
606
// Mathematical expressions in templates
607
template = "Next year you will be #{#age + 1} years old.";
608
exp = parser.parseExpression(template, ParserContext.TEMPLATE_EXPRESSION);
609
String nextYear = exp.getValue(context, String.class);
610
// "Next year you will be 31 years old."
611
```
612
{ .api }
613
614
### Custom Template Contexts
615
616
```java
617
// Custom template delimiters
618
TemplateParserContext customContext = new TemplateParserContext("${", "}");
619
620
String template = "Hello ${#name}, today is ${T(java.time.LocalDate).now()}";
621
Expression exp = parser.parseExpression(template, customContext);
622
623
context.setVariable("name", "World");
624
String result = exp.getValue(context, String.class);
625
// "Hello World, today is 2023-12-07"
626
627
// XML-friendly delimiters
628
TemplateParserContext xmlContext = new TemplateParserContext("{{", "}}");
629
630
template = "<greeting>Hello {{#name}}</greeting>";
631
exp = parser.parseExpression(template, xmlContext);
632
result = exp.getValue(context, String.class);
633
// "<greeting>Hello World</greeting>"
634
```
635
{ .api }
636
637
### Expression Composition
638
639
```java
640
public class ExpressionComposer {
641
private final ExpressionParser parser = new SpelExpressionParser();
642
private final Map<String, Expression> namedExpressions = new HashMap<>();
643
644
public void defineExpression(String name, String expressionString) {
645
Expression expr = parser.parseExpression(expressionString);
646
namedExpressions.put(name, expr);
647
}
648
649
public Expression composeExpression(String... expressionNames) {
650
// Create composite expression that evaluates multiple sub-expressions
651
StringBuilder composite = new StringBuilder();
652
653
for (int i = 0; i < expressionNames.length; i++) {
654
if (i > 0) {
655
composite.append(" + ");
656
}
657
composite.append("(").append(getExpressionString(expressionNames[i])).append(")");
658
}
659
660
return parser.parseExpression(composite.toString());
661
}
662
663
private String getExpressionString(String name) {
664
Expression expr = namedExpressions.get(name);
665
return expr != null ? expr.getExpressionString() : "''";
666
}
667
668
public Object evaluateComposite(EvaluationContext context, String... expressionNames) {
669
Expression composite = composeExpression(expressionNames);
670
return composite.getValue(context);
671
}
672
}
673
674
// Usage
675
ExpressionComposer composer = new ExpressionComposer();
676
composer.defineExpression("greeting", "'Hello '");
677
composer.defineExpression("name", "#user.name");
678
composer.defineExpression("suffix", "', welcome!'");
679
680
Expression greeting = composer.composeExpression("greeting", "name", "suffix");
681
682
context.setVariable("user", new User("Alice"));
683
String result = greeting.getValue(context, String.class);
684
// "Hello Alice, welcome!"
685
```
686
{ .api }
687
688
## Performance Optimization Strategies
689
690
### Expression Caching and Pooling
691
692
```java
693
public class OptimizedExpressionService {
694
private final ExpressionParser parser;
695
private final Map<String, Expression> expressionCache = new ConcurrentHashMap<>();
696
private final Map<String, AtomicLong> usageStats = new ConcurrentHashMap<>();
697
698
public OptimizedExpressionService() {
699
// Configure for performance
700
SpelParserConfiguration config = new SpelParserConfiguration(
701
SpelCompilerMode.IMMEDIATE,
702
Thread.currentThread().getContextClassLoader(),
703
false, // Disable auto-grow for predictability
704
false,
705
0,
706
10000
707
);
708
this.parser = new SpelExpressionParser(config);
709
}
710
711
public Expression getExpression(String expressionString) {
712
return expressionCache.computeIfAbsent(expressionString, expr -> {
713
System.out.println("Compiling new expression: " + expr);
714
return parser.parseExpression(expr);
715
});
716
}
717
718
public Object evaluate(String expressionString, EvaluationContext context, Object rootObject) {
719
Expression expr = getExpression(expressionString);
720
721
// Track usage
722
usageStats.computeIfAbsent(expressionString, k -> new AtomicLong()).incrementAndGet();
723
724
return expr.getValue(context, rootObject);
725
}
726
727
public void printUsageStats() {
728
System.out.println("Expression Usage Statistics:");
729
usageStats.entrySet().stream()
730
.sorted(Map.Entry.<String, AtomicLong>comparingByValue(
731
(a, b) -> Long.compare(b.get(), a.get())))
732
.limit(10)
733
.forEach(entry ->
734
System.out.printf(" %dx: %s%n", entry.getValue().get(), entry.getKey()));
735
}
736
737
// Precompile frequently used expressions
738
public void warmUp(String... expressions) {
739
for (String expr : expressions) {
740
Expression compiled = getExpression(expr);
741
if (compiled instanceof SpelExpression) {
742
((SpelExpression) compiled).compileExpression();
743
}
744
}
745
}
746
}
747
748
// Usage
749
OptimizedExpressionService service = new OptimizedExpressionService();
750
751
// Warm up with known frequent expressions
752
service.warmUp(
753
"user.name.toUpperCase()",
754
"order.items.![price * quantity].sum()",
755
"product.inStock and product.price > 0"
756
);
757
758
// Use service
759
Object result = service.evaluate("user.name.toUpperCase()", context, user);
760
```
761
{ .api }
762
763
### Specialized Context Configuration
764
765
```java
766
public class PerformanceEvaluationContext extends StandardEvaluationContext {
767
768
public PerformanceEvaluationContext() {
769
super();
770
configureForPerformance();
771
}
772
773
public PerformanceEvaluationContext(Object rootObject) {
774
super(rootObject);
775
configureForPerformance();
776
}
777
778
private void configureForPerformance() {
779
// Use optimized accessors in order of likely usage
780
setPropertyAccessors(Arrays.asList(
781
new DataBindingPropertyAccessor(), // Fastest for simple properties
782
new ReflectivePropertyAccessor(false) // Read-only for security & speed
783
));
784
785
// Minimal method resolution
786
setMethodResolvers(Arrays.asList(
787
new DataBindingMethodResolver()
788
));
789
790
// Optimized type handling
791
setTypeConverter(new StandardTypeConverter());
792
setTypeComparator(new StandardTypeComparator());
793
setOperatorOverloader(new StandardOperatorOverloader());
794
795
// Fast type locator with common imports
796
StandardTypeLocator typeLocator = new StandardTypeLocator();
797
typeLocator.registerImport("", "java.lang");
798
typeLocator.registerImport("", "java.util");
799
typeLocator.registerImport("", "java.time");
800
setTypeLocator(typeLocator);
801
}
802
803
// Bulk variable setting for reduced overhead
804
public void setVariablesBulk(Map<String, Object> variables) {
805
variables.forEach(this::setVariable);
806
}
807
}
808
```
809
{ .api }
810
811
## Integration Patterns
812
813
### Spring Boot Integration
814
815
```java
816
@Configuration
817
@EnableConfigurationProperties(SpelProperties.class)
818
public class SpelAutoConfiguration {
819
820
@Bean
821
@ConditionalOnMissingBean
822
public SpelExpressionParser spelExpressionParser(SpelProperties properties) {
823
SpelParserConfiguration config = new SpelParserConfiguration(
824
properties.getCompilerMode(),
825
Thread.currentThread().getContextClassLoader(),
826
properties.isAutoGrowNullReferences(),
827
properties.isAutoGrowCollections(),
828
properties.getMaximumAutoGrowSize(),
829
properties.getMaximumExpressionLength()
830
);
831
return new SpelExpressionParser(config);
832
}
833
834
@Bean
835
@ConditionalOnMissingBean
836
public StandardEvaluationContext standardEvaluationContext(
837
ApplicationContext applicationContext,
838
SpelProperties properties) {
839
840
StandardEvaluationContext context = new StandardEvaluationContext();
841
842
if (properties.isBeanReferencesEnabled()) {
843
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
844
}
845
846
return context;
847
}
848
}
849
850
@ConfigurationProperties(prefix = "spel")
851
public class SpelProperties {
852
private SpelCompilerMode compilerMode = SpelCompilerMode.OFF;
853
private boolean autoGrowNullReferences = false;
854
private boolean autoGrowCollections = false;
855
private int maximumAutoGrowSize = Integer.MAX_VALUE;
856
private int maximumExpressionLength = 10000;
857
private boolean beanReferencesEnabled = true;
858
859
// getters and setters...
860
}
861
```
862
{ .api }
863
864
### Expression-Based Validation
865
866
```java
867
@Component
868
public class ExpressionValidator {
869
private final SpelExpressionParser parser = new SpelExpressionParser();
870
871
public boolean validate(Object object, String validationExpression) {
872
try {
873
Expression expr = parser.parseExpression(validationExpression);
874
Boolean result = expr.getValue(object, Boolean.class);
875
return result != null && result;
876
} catch (Exception e) {
877
return false;
878
}
879
}
880
881
// Annotation-based validation
882
@Target(ElementType.TYPE)
883
@Retention(RetentionPolicy.RUNTIME)
884
@Constraint(validatedBy = ExpressionConstraintValidator.class)
885
public @interface ValidExpression {
886
String value();
887
String message() default "Expression validation failed";
888
Class<?>[] groups() default {};
889
Class<? extends Payload>[] payload() default {};
890
}
891
892
public static class ExpressionConstraintValidator
893
implements ConstraintValidator<ValidExpression, Object> {
894
895
private String expression;
896
private final ExpressionValidator validator = new ExpressionValidator();
897
898
@Override
899
public void initialize(ValidExpression constraint) {
900
this.expression = constraint.value();
901
}
902
903
@Override
904
public boolean isValid(Object value, ConstraintValidatorContext context) {
905
return value == null || validator.validate(value, expression);
906
}
907
}
908
}
909
910
// Usage
911
@ValidExpression("age >= 18 and age <= 120")
912
public class Person {
913
private int age;
914
private String name;
915
// getters and setters...
916
}
917
```
918
{ .api }
919
920
## Best Practices for Advanced Usage
921
922
### Security Considerations
923
924
```java
925
// Secure expression evaluation service
926
public class SecureExpressionService {
927
private final SpelExpressionParser parser;
928
private final Set<String> allowedExpressionPatterns;
929
930
public SecureExpressionService() {
931
// Disable compilation for security
932
SpelParserConfiguration config = new SpelParserConfiguration(
933
SpelCompilerMode.OFF, null
934
);
935
this.parser = new SpelExpressionParser(config);
936
937
// Define allowed expression patterns
938
this.allowedExpressionPatterns = Set.of(
939
"^[a-zA-Z_][a-zA-Z0-9_.]*$", // Simple property access
940
"^#[a-zA-Z_][a-zA-Z0-9_]*$", // Variable access
941
"^[^T(]*$" // No type references
942
);
943
}
944
945
public Object safeEvaluate(String expression, EvaluationContext context, Object root) {
946
// Validate expression against security rules
947
if (!isExpressionSafe(expression)) {
948
throw new SecurityException("Expression not allowed: " + expression);
949
}
950
951
// Use restricted context
952
SimpleEvaluationContext secureContext = SimpleEvaluationContext
953
.forReadOnlyDataBinding()
954
.build();
955
956
Expression expr = parser.parseExpression(expression);
957
return expr.getValue(secureContext, root);
958
}
959
960
private boolean isExpressionSafe(String expression) {
961
return allowedExpressionPatterns.stream()
962
.anyMatch(pattern -> expression.matches(pattern)) &&
963
!containsDangerousPatterns(expression);
964
}
965
966
private boolean containsDangerousPatterns(String expression) {
967
String[] dangerousPatterns = {
968
"T(", "new ", "getClass", "forName", "invoke"
969
};
970
971
return Arrays.stream(dangerousPatterns)
972
.anyMatch(expression::contains);
973
}
974
}
975
```
976
{ .api }