or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mderror-handling.mdevaluation-contexts.mdexpression-evaluation.mdindex.mdmethod-constructor-resolution.mdproperty-index-access.mdspel-configuration.mdtype-system-support.md

advanced-features.mddocs/

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 }