0
# Method and Constructor Resolution
1
2
This document covers SpEL's method and constructor resolution capabilities, including resolver interfaces, standard implementations, and custom resolver development.
3
4
## Core Resolution Interfaces
5
6
### MethodResolver Interface
7
8
```java
9
@FunctionalInterface
10
public interface MethodResolver {
11
MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
12
List<TypeDescriptor> argumentTypes) throws AccessException;
13
}
14
```
15
{ .api }
16
17
### ConstructorResolver Interface
18
19
```java
20
public interface ConstructorResolver {
21
ConstructorExecutor resolve(EvaluationContext context, String typeName,
22
List<TypeDescriptor> argumentTypes) throws AccessException;
23
}
24
```
25
{ .api }
26
27
### Executor Interfaces
28
29
```java
30
public interface MethodExecutor {
31
TypedValue execute(EvaluationContext context, Object target, Object... arguments)
32
throws AccessException;
33
}
34
35
public interface ConstructorExecutor {
36
TypedValue execute(EvaluationContext context, Object... arguments) throws AccessException;
37
}
38
```
39
{ .api }
40
41
## Standard Method Resolution
42
43
### ReflectiveMethodResolver Class
44
45
```java
46
public class ReflectiveMethodResolver implements MethodResolver {
47
public ReflectiveMethodResolver();
48
public ReflectiveMethodResolver(boolean useDistance);
49
50
public void registerMethodFilter(Class<?> type, MethodFilter filter);
51
52
@Override
53
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
54
List<TypeDescriptor> argumentTypes) throws AccessException;
55
}
56
```
57
{ .api }
58
59
The default method resolver that uses Java reflection to find and invoke methods.
60
61
### DataBindingMethodResolver Class
62
63
```java
64
public class DataBindingMethodResolver implements MethodResolver {
65
@Override
66
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
67
List<TypeDescriptor> argumentTypes) throws AccessException;
68
}
69
```
70
{ .api }
71
72
Optimized method resolver for data-binding scenarios with restricted method access.
73
74
### Method Resolution Examples
75
76
```java
77
public class Calculator {
78
public int add(int a, int b) {
79
return a + b;
80
}
81
82
public double add(double a, double b) {
83
return a + b;
84
}
85
86
public String concatenate(String... parts) {
87
return String.join("", parts);
88
}
89
90
public static int multiply(int a, int b) {
91
return a * b;
92
}
93
}
94
95
StandardEvaluationContext context = new StandardEvaluationContext();
96
context.addMethodResolver(new ReflectiveMethodResolver());
97
98
Calculator calc = new Calculator();
99
ExpressionParser parser = new SpelExpressionParser();
100
101
// Instance method invocation
102
Expression exp = parser.parseExpression("add(5, 3)");
103
Integer result = exp.getValue(context, calc, Integer.class); // 8
104
105
// Method overloading resolution
106
exp = parser.parseExpression("add(5.5, 3.2)");
107
Double doubleResult = exp.getValue(context, calc, Double.class); // 8.7
108
109
// Varargs method invocation
110
exp = parser.parseExpression("concatenate('Hello', ' ', 'World')");
111
String text = exp.getValue(context, calc, String.class); // "Hello World"
112
113
// Static method invocation (requires type reference)
114
exp = parser.parseExpression("T(Calculator).multiply(4, 6)");
115
Integer product = exp.getValue(context, Integer.class); // 24
116
```
117
{ .api }
118
119
### Method Filtering
120
121
```java
122
public class RestrictedMethodResolver extends ReflectiveMethodResolver {
123
124
public RestrictedMethodResolver() {
125
super();
126
127
// Register method filters for security
128
registerMethodFilter(Object.class, new SafeMethodFilter());
129
registerMethodFilter(String.class, new StringMethodFilter());
130
}
131
}
132
133
// Filter that allows only safe Object methods
134
public class SafeMethodFilter implements MethodFilter {
135
private final Set<String> allowedMethods = Set.of(
136
"toString", "hashCode", "equals"
137
);
138
139
@Override
140
public List<Method> filter(List<Method> methods) {
141
return methods.stream()
142
.filter(method -> allowedMethods.contains(method.getName()))
143
.collect(Collectors.toList());
144
}
145
}
146
147
// Filter that restricts String methods
148
public class StringMethodFilter implements MethodFilter {
149
private final Set<String> allowedMethods = Set.of(
150
"length", "toUpperCase", "toLowerCase", "trim", "substring",
151
"indexOf", "startsWith", "endsWith", "contains"
152
);
153
154
@Override
155
public List<Method> filter(List<Method> methods) {
156
return methods.stream()
157
.filter(method -> allowedMethods.contains(method.getName()))
158
.collect(Collectors.toList());
159
}
160
}
161
162
// Usage
163
StandardEvaluationContext context = new StandardEvaluationContext();
164
context.addMethodResolver(new RestrictedMethodResolver());
165
166
String text = "Hello World";
167
ExpressionParser parser = new SpelExpressionParser();
168
169
// Allowed methods work
170
Expression exp = parser.parseExpression("length()");
171
Integer length = exp.getValue(context, text, Integer.class); // 11
172
173
exp = parser.parseExpression("toUpperCase()");
174
String upper = exp.getValue(context, text, String.class); // "HELLO WORLD"
175
176
// Restricted methods would fail resolution
177
// exp = parser.parseExpression("getClass()"); // Would fail
178
```
179
{ .api }
180
181
## Standard Constructor Resolution
182
183
### ReflectiveConstructorResolver Class
184
185
```java
186
public class ReflectiveConstructorResolver implements ConstructorResolver {
187
@Override
188
public ConstructorExecutor resolve(EvaluationContext context, String typeName,
189
List<TypeDescriptor> argumentTypes) throws AccessException;
190
}
191
```
192
{ .api }
193
194
### Constructor Resolution Examples
195
196
```java
197
StandardEvaluationContext context = new StandardEvaluationContext();
198
context.addConstructorResolver(new ReflectiveConstructorResolver());
199
200
ExpressionParser parser = new SpelExpressionParser();
201
202
// Constructor invocation with arguments
203
Expression exp = parser.parseExpression("new String('Hello World')");
204
String result = exp.getValue(context, String.class); // "Hello World"
205
206
// Default constructor
207
exp = parser.parseExpression("new java.util.ArrayList()");
208
ArrayList<?> list = exp.getValue(context, ArrayList.class); // empty list
209
210
// Constructor with multiple arguments
211
exp = parser.parseExpression("new java.util.Date(2023, 0, 1)");
212
Date date = exp.getValue(context, Date.class);
213
214
// Constructor chaining with method calls
215
exp = parser.parseExpression("new StringBuilder('Hello').append(' World').toString()");
216
String text = exp.getValue(context, String.class); // "Hello World"
217
```
218
{ .api }
219
220
## Standard Executors
221
222
### ReflectiveMethodExecutor Class
223
224
```java
225
public class ReflectiveMethodExecutor implements MethodExecutor {
226
public ReflectiveMethodExecutor(Method method);
227
228
@Override
229
public TypedValue execute(EvaluationContext context, Object target, Object... arguments)
230
throws AccessException;
231
232
public Method getMethod();
233
}
234
```
235
{ .api }
236
237
### ReflectiveConstructorExecutor Class
238
239
```java
240
public class ReflectiveConstructorExecutor implements ConstructorExecutor {
241
public ReflectiveConstructorExecutor(Constructor<?> constructor);
242
243
@Override
244
public TypedValue execute(EvaluationContext context, Object... arguments)
245
throws AccessException;
246
247
public Constructor<?> getConstructor();
248
}
249
```
250
{ .api }
251
252
## Custom Method Resolvers
253
254
### Cached Method Resolver
255
256
```java
257
public class CachingMethodResolver implements MethodResolver {
258
private final MethodResolver delegate;
259
private final Map<MethodKey, MethodExecutor> cache = new ConcurrentHashMap<>();
260
261
public CachingMethodResolver(MethodResolver delegate) {
262
this.delegate = delegate;
263
}
264
265
@Override
266
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
267
List<TypeDescriptor> argumentTypes) throws AccessException {
268
269
MethodKey key = new MethodKey(targetObject.getClass(), name, argumentTypes);
270
271
return cache.computeIfAbsent(key, k -> {
272
try {
273
return delegate.resolve(context, targetObject, name, argumentTypes);
274
} catch (AccessException e) {
275
return null; // Cache null results to avoid repeated failures
276
}
277
});
278
}
279
280
private static class MethodKey {
281
private final Class<?> targetClass;
282
private final String methodName;
283
private final List<Class<?>> argumentTypes;
284
285
public MethodKey(Class<?> targetClass, String methodName, List<TypeDescriptor> argTypes) {
286
this.targetClass = targetClass;
287
this.methodName = methodName;
288
this.argumentTypes = argTypes.stream()
289
.map(TypeDescriptor::getType)
290
.collect(Collectors.toList());
291
}
292
293
@Override
294
public boolean equals(Object obj) {
295
if (this == obj) return true;
296
if (!(obj instanceof MethodKey)) return false;
297
MethodKey other = (MethodKey) obj;
298
return Objects.equals(targetClass, other.targetClass) &&
299
Objects.equals(methodName, other.methodName) &&
300
Objects.equals(argumentTypes, other.argumentTypes);
301
}
302
303
@Override
304
public int hashCode() {
305
return Objects.hash(targetClass, methodName, argumentTypes);
306
}
307
}
308
}
309
```
310
{ .api }
311
312
### Function Registry Resolver
313
314
```java
315
public class FunctionRegistryResolver implements MethodResolver {
316
private final Map<String, Method> functions = new HashMap<>();
317
318
public void registerFunction(String name, Method method) {
319
functions.put(name, method);
320
}
321
322
public void registerFunction(String name, Class<?> clazz, String methodName,
323
Class<?>... parameterTypes) throws NoSuchMethodException {
324
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
325
functions.put(name, method);
326
}
327
328
@Override
329
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
330
List<TypeDescriptor> argumentTypes) throws AccessException {
331
332
Method function = functions.get(name);
333
if (function != null && isCompatible(function, argumentTypes)) {
334
return new FunctionExecutor(function);
335
}
336
337
return null; // Let other resolvers handle it
338
}
339
340
private boolean isCompatible(Method method, List<TypeDescriptor> argumentTypes) {
341
Class<?>[] paramTypes = method.getParameterTypes();
342
343
if (paramTypes.length != argumentTypes.size()) {
344
return false;
345
}
346
347
for (int i = 0; i < paramTypes.length; i++) {
348
if (!paramTypes[i].isAssignableFrom(argumentTypes.get(i).getType())) {
349
return false;
350
}
351
}
352
353
return true;
354
}
355
356
private static class FunctionExecutor implements MethodExecutor {
357
private final Method method;
358
359
public FunctionExecutor(Method method) {
360
this.method = method;
361
}
362
363
@Override
364
public TypedValue execute(EvaluationContext context, Object target, Object... arguments)
365
throws AccessException {
366
try {
367
Object result = method.invoke(null, arguments); // Static invocation
368
return new TypedValue(result);
369
} catch (Exception e) {
370
throw new AccessException("Function execution failed", e);
371
}
372
}
373
}
374
}
375
376
// Usage
377
public class MathFunctions {
378
public static double sqrt(double value) {
379
return Math.sqrt(value);
380
}
381
382
public static long factorial(int n) {
383
return n <= 1 ? 1 : n * factorial(n - 1);
384
}
385
386
public static boolean isPrime(int n) {
387
if (n < 2) return false;
388
for (int i = 2; i <= Math.sqrt(n); i++) {
389
if (n % i == 0) return false;
390
}
391
return true;
392
}
393
}
394
395
FunctionRegistryResolver functionResolver = new FunctionRegistryResolver();
396
functionResolver.registerFunction("sqrt", MathFunctions.class, "sqrt", double.class);
397
functionResolver.registerFunction("factorial", MathFunctions.class, "factorial", int.class);
398
functionResolver.registerFunction("isPrime", MathFunctions.class, "isPrime", int.class);
399
400
StandardEvaluationContext context = new StandardEvaluationContext();
401
context.addMethodResolver(functionResolver);
402
403
ExpressionParser parser = new SpelExpressionParser();
404
405
Expression exp = parser.parseExpression("sqrt(16)");
406
Double result = exp.getValue(context, Double.class); // 4.0
407
408
exp = parser.parseExpression("factorial(5)");
409
Long factorial = exp.getValue(context, Long.class); // 120
410
411
exp = parser.parseExpression("isPrime(17)");
412
Boolean prime = exp.getValue(context, Boolean.class); // true
413
```
414
{ .api }
415
416
### Domain-Specific Method Resolver
417
418
```java
419
public class DomainSpecificResolver implements MethodResolver {
420
private final Map<Class<?>, DomainHandler> domainHandlers = new HashMap<>();
421
422
public void registerDomainHandler(Class<?> domainClass, DomainHandler handler) {
423
domainHandlers.put(domainClass, handler);
424
}
425
426
@Override
427
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
428
List<TypeDescriptor> argumentTypes) throws AccessException {
429
430
DomainHandler handler = domainHandlers.get(targetObject.getClass());
431
if (handler != null && handler.canHandle(name, argumentTypes)) {
432
return new DomainMethodExecutor(handler, name);
433
}
434
435
return null;
436
}
437
438
public interface DomainHandler {
439
boolean canHandle(String methodName, List<TypeDescriptor> argumentTypes);
440
Object execute(String methodName, Object target, Object... arguments) throws Exception;
441
}
442
443
private static class DomainMethodExecutor implements MethodExecutor {
444
private final DomainHandler handler;
445
private final String methodName;
446
447
public DomainMethodExecutor(DomainHandler handler, String methodName) {
448
this.handler = handler;
449
this.methodName = methodName;
450
}
451
452
@Override
453
public TypedValue execute(EvaluationContext context, Object target, Object... arguments)
454
throws AccessException {
455
try {
456
Object result = handler.execute(methodName, target, arguments);
457
return new TypedValue(result);
458
} catch (Exception e) {
459
throw new AccessException("Domain method execution failed", e);
460
}
461
}
462
}
463
}
464
465
// Domain-specific handler for business objects
466
public class BusinessObjectHandler implements DomainSpecificResolver.DomainHandler {
467
468
@Override
469
public boolean canHandle(String methodName, List<TypeDescriptor> argumentTypes) {
470
return methodName.startsWith("calculate") ||
471
methodName.startsWith("validate") ||
472
methodName.startsWith("transform");
473
}
474
475
@Override
476
public Object execute(String methodName, Object target, Object... arguments) throws Exception {
477
BusinessObject bo = (BusinessObject) target;
478
479
return switch (methodName) {
480
case "calculateTotal" -> bo.getItems().stream()
481
.mapToDouble(Item::getPrice)
482
.sum();
483
case "validateRequired" -> bo.getName() != null && !bo.getName().isEmpty();
484
case "transformToUpper" -> bo.getName().toUpperCase();
485
default -> throw new UnsupportedOperationException("Unknown method: " + methodName);
486
};
487
}
488
}
489
490
// Usage
491
DomainSpecificResolver domainResolver = new DomainSpecificResolver();
492
domainResolver.registerDomainHandler(BusinessObject.class, new BusinessObjectHandler());
493
494
StandardEvaluationContext context = new StandardEvaluationContext();
495
context.addMethodResolver(domainResolver);
496
497
BusinessObject bo = new BusinessObject("Product");
498
bo.addItem(new Item("Item1", 10.0));
499
bo.addItem(new Item("Item2", 20.0));
500
501
ExpressionParser parser = new SpelExpressionParser();
502
503
Expression exp = parser.parseExpression("calculateTotal()");
504
Double total = exp.getValue(context, bo, Double.class); // 30.0
505
506
exp = parser.parseExpression("validateRequired()");
507
Boolean valid = exp.getValue(context, bo, Boolean.class); // true
508
509
exp = parser.parseExpression("transformToUpper()");
510
String upper = exp.getValue(context, bo, String.class); // "PRODUCT"
511
```
512
{ .api }
513
514
## Custom Constructor Resolvers
515
516
### Factory-based Constructor Resolver
517
518
```java
519
public class FactoryConstructorResolver implements ConstructorResolver {
520
private final Map<String, ObjectFactory<?>> factories = new HashMap<>();
521
522
public void registerFactory(String typeName, ObjectFactory<?> factory) {
523
factories.put(typeName, factory);
524
}
525
526
@Override
527
public ConstructorExecutor resolve(EvaluationContext context, String typeName,
528
List<TypeDescriptor> argumentTypes) throws AccessException {
529
530
ObjectFactory<?> factory = factories.get(typeName);
531
if (factory != null) {
532
return new FactoryExecutor(factory);
533
}
534
535
return null;
536
}
537
538
@FunctionalInterface
539
public interface ObjectFactory<T> {
540
T create(Object... arguments) throws Exception;
541
}
542
543
private static class FactoryExecutor implements ConstructorExecutor {
544
private final ObjectFactory<?> factory;
545
546
public FactoryExecutor(ObjectFactory<?> factory) {
547
this.factory = factory;
548
}
549
550
@Override
551
public TypedValue execute(EvaluationContext context, Object... arguments)
552
throws AccessException {
553
try {
554
Object result = factory.create(arguments);
555
return new TypedValue(result);
556
} catch (Exception e) {
557
throw new AccessException("Factory construction failed", e);
558
}
559
}
560
}
561
}
562
563
// Usage
564
FactoryConstructorResolver factoryResolver = new FactoryConstructorResolver();
565
566
// Register custom object factories
567
factoryResolver.registerFactory("Person", args -> {
568
if (args.length == 2) {
569
return new Person((String) args[0], (Integer) args[1]);
570
}
571
throw new IllegalArgumentException("Person requires name and age");
572
});
573
574
factoryResolver.registerFactory("Product", args -> {
575
if (args.length == 1) {
576
return new Product((String) args[0]);
577
}
578
throw new IllegalArgumentException("Product requires name");
579
});
580
581
StandardEvaluationContext context = new StandardEvaluationContext();
582
context.addConstructorResolver(factoryResolver);
583
584
ExpressionParser parser = new SpelExpressionParser();
585
586
// Use factory to create objects
587
Expression exp = parser.parseExpression("new Person('John', 30)");
588
Person person = exp.getValue(context, Person.class);
589
590
exp = parser.parseExpression("new Product('Widget')");
591
Product product = exp.getValue(context, Product.class);
592
```
593
{ .api }
594
595
## Advanced Resolution Patterns
596
597
### Chainable Method Resolver
598
599
```java
600
public class ChainableMethodResolver implements MethodResolver {
601
private final List<MethodResolver> resolvers = new ArrayList<>();
602
603
public ChainableMethodResolver addResolver(MethodResolver resolver) {
604
resolvers.add(resolver);
605
return this;
606
}
607
608
@Override
609
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
610
List<TypeDescriptor> argumentTypes) throws AccessException {
611
612
for (MethodResolver resolver : resolvers) {
613
try {
614
MethodExecutor executor = resolver.resolve(context, targetObject, name, argumentTypes);
615
if (executor != null) {
616
return executor;
617
}
618
} catch (AccessException e) {
619
// Continue to next resolver
620
}
621
}
622
623
return null; // No resolver could handle the method
624
}
625
}
626
627
// Usage
628
ChainableMethodResolver chainResolver = new ChainableMethodResolver()
629
.addResolver(new CachingMethodResolver(new ReflectiveMethodResolver()))
630
.addResolver(new FunctionRegistryResolver())
631
.addResolver(new DomainSpecificResolver());
632
633
StandardEvaluationContext context = new StandardEvaluationContext();
634
context.setMethodResolvers(Arrays.asList(chainResolver));
635
```
636
{ .api }
637
638
### Conditional Method Resolver
639
640
```java
641
public class ConditionalMethodResolver implements MethodResolver {
642
private final Predicate<ResolutionContext> condition;
643
private final MethodResolver resolver;
644
645
public ConditionalMethodResolver(Predicate<ResolutionContext> condition,
646
MethodResolver resolver) {
647
this.condition = condition;
648
this.resolver = resolver;
649
}
650
651
@Override
652
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
653
List<TypeDescriptor> argumentTypes) throws AccessException {
654
655
ResolutionContext resContext = new ResolutionContext(context, targetObject, name, argumentTypes);
656
657
if (condition.test(resContext)) {
658
return resolver.resolve(context, targetObject, name, argumentTypes);
659
}
660
661
return null;
662
}
663
664
public static class ResolutionContext {
665
private final EvaluationContext evaluationContext;
666
private final Object targetObject;
667
private final String methodName;
668
private final List<TypeDescriptor> argumentTypes;
669
670
public ResolutionContext(EvaluationContext evaluationContext, Object targetObject,
671
String methodName, List<TypeDescriptor> argumentTypes) {
672
this.evaluationContext = evaluationContext;
673
this.targetObject = targetObject;
674
this.methodName = methodName;
675
this.argumentTypes = argumentTypes;
676
}
677
678
// Getters...
679
public EvaluationContext getEvaluationContext() { return evaluationContext; }
680
public Object getTargetObject() { return targetObject; }
681
public String getMethodName() { return methodName; }
682
public List<TypeDescriptor> getArgumentTypes() { return argumentTypes; }
683
}
684
}
685
686
// Usage examples
687
MethodResolver secureResolver = new ConditionalMethodResolver(
688
ctx -> isSecureContext(ctx.getEvaluationContext()),
689
new RestrictedMethodResolver()
690
);
691
692
MethodResolver developmentResolver = new ConditionalMethodResolver(
693
ctx -> isDevelopmentMode(),
694
new ReflectiveMethodResolver()
695
);
696
697
private static boolean isSecureContext(EvaluationContext context) {
698
return context instanceof SimpleEvaluationContext;
699
}
700
701
private static boolean isDevelopmentMode() {
702
return "development".equals(System.getProperty("environment"));
703
}
704
```
705
{ .api }
706
707
## Performance Optimization
708
709
### Method Resolution Metrics
710
711
```java
712
public class MetricsMethodResolver implements MethodResolver {
713
private final MethodResolver delegate;
714
private final Map<String, AtomicLong> resolutionCounts = new ConcurrentHashMap<>();
715
private final Map<String, AtomicLong> resolutionTimes = new ConcurrentHashMap<>();
716
717
public MetricsMethodResolver(MethodResolver delegate) {
718
this.delegate = delegate;
719
}
720
721
@Override
722
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
723
List<TypeDescriptor> argumentTypes) throws AccessException {
724
725
String key = targetObject.getClass().getSimpleName() + "." + name;
726
long startTime = System.nanoTime();
727
728
try {
729
MethodExecutor executor = delegate.resolve(context, targetObject, name, argumentTypes);
730
731
if (executor != null) {
732
return new MetricsMethodExecutor(executor, key);
733
}
734
735
return null;
736
} finally {
737
long duration = System.nanoTime() - startTime;
738
resolutionCounts.computeIfAbsent(key, k -> new AtomicLong()).incrementAndGet();
739
resolutionTimes.computeIfAbsent(key, k -> new AtomicLong()).addAndGet(duration);
740
}
741
}
742
743
public void printMetrics() {
744
System.out.println("Method Resolution Metrics:");
745
resolutionCounts.forEach((method, count) -> {
746
long totalTime = resolutionTimes.get(method).get();
747
long avgTime = totalTime / count.get();
748
System.out.printf("%s: %d calls, avg %d ns%n", method, count.get(), avgTime);
749
});
750
}
751
752
private static class MetricsMethodExecutor implements MethodExecutor {
753
private final MethodExecutor delegate;
754
private final String methodKey;
755
756
public MetricsMethodExecutor(MethodExecutor delegate, String methodKey) {
757
this.delegate = delegate;
758
this.methodKey = methodKey;
759
}
760
761
@Override
762
public TypedValue execute(EvaluationContext context, Object target, Object... arguments)
763
throws AccessException {
764
// Could add execution metrics here as well
765
return delegate.execute(context, target, arguments);
766
}
767
}
768
}
769
```
770
{ .api }
771
772
## Best Practices
773
774
### Security Best Practices
775
776
```java
777
// 1. Use method filtering for security
778
public class SecurityAwareMethodResolver extends ReflectiveMethodResolver {
779
780
public SecurityAwareMethodResolver() {
781
super();
782
783
// Register security filters for dangerous classes
784
registerMethodFilter(Runtime.class, methods -> Collections.emptyList());
785
registerMethodFilter(ProcessBuilder.class, methods -> Collections.emptyList());
786
registerMethodFilter(System.class, this::filterSystemMethods);
787
registerMethodFilter(Class.class, this::filterClassMethods);
788
}
789
790
private List<Method> filterSystemMethods(List<Method> methods) {
791
// Only allow safe System methods
792
Set<String> allowedMethods = Set.of("currentTimeMillis", "nanoTime");
793
return methods.stream()
794
.filter(m -> allowedMethods.contains(m.getName()))
795
.collect(Collectors.toList());
796
}
797
798
private List<Method> filterClassMethods(List<Method> methods) {
799
// Prevent reflection access
800
return Collections.emptyList();
801
}
802
}
803
804
// 2. Validate method arguments
805
public class ValidatingMethodExecutor implements MethodExecutor {
806
private final MethodExecutor delegate;
807
808
@Override
809
public TypedValue execute(EvaluationContext context, Object target, Object... arguments)
810
throws AccessException {
811
812
validateArguments(arguments);
813
return delegate.execute(context, target, arguments);
814
}
815
816
private void validateArguments(Object[] arguments) throws AccessException {
817
for (Object arg : arguments) {
818
if (arg instanceof String) {
819
String str = (String) arg;
820
if (str.length() > 1000) { // Prevent extremely large strings
821
throw new AccessException("String argument too large");
822
}
823
}
824
}
825
}
826
}
827
```
828
{ .api }
829
830
### Performance Tips
831
832
1. **Use method filtering**: Filter out methods at resolution time rather than execution time
833
2. **Implement caching**: Cache method resolution results for frequently called methods
834
3. **Order resolvers by frequency**: Place most commonly used resolvers first
835
4. **Use conditional resolvers**: Only invoke expensive resolvers when necessary
836
5. **Monitor resolution metrics**: Track which methods are resolved most frequently
837
6. **Prefer compilation**: Use `CompilablePropertyAccessor` for hot paths where possible
838
839
### Error Handling Best Practices
840
841
```java
842
public class RobustMethodResolver implements MethodResolver {
843
private final MethodResolver delegate;
844
private final boolean logErrors;
845
846
public RobustMethodResolver(MethodResolver delegate, boolean logErrors) {
847
this.delegate = delegate;
848
this.logErrors = logErrors;
849
}
850
851
@Override
852
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
853
List<TypeDescriptor> argumentTypes) throws AccessException {
854
855
try {
856
return delegate.resolve(context, targetObject, name, argumentTypes);
857
} catch (AccessException e) {
858
if (logErrors) {
859
System.err.printf("Method resolution failed for %s.%s: %s%n",
860
targetObject.getClass().getSimpleName(), name, e.getMessage());
861
}
862
return null; // Let other resolvers try
863
} catch (Exception e) {
864
if (logErrors) {
865
System.err.printf("Unexpected error resolving %s.%s: %s%n",
866
targetObject.getClass().getSimpleName(), name, e.getMessage());
867
}
868
return null;
869
}
870
}
871
}
872
```
873
{ .api }