CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework--spring-expression

Spring Expression Language (SpEL) provides a powerful expression language for querying and manipulating object graphs at runtime.

Pending
Overview
Eval results
Files

evaluation-contexts.mddocs/

Evaluation Contexts

This document covers SpEL's evaluation context implementations, providing the runtime environment for expression evaluation including variables, functions, and various resolvers.

EvaluationContext Interface

Core Interface Definition

public interface EvaluationContext {
    // Root object access
    TypedValue getRootObject();
    
    // Accessor chains
    List<PropertyAccessor> getPropertyAccessors();
    List<IndexAccessor> getIndexAccessors();
    List<ConstructorResolver> getConstructorResolvers();
    List<MethodResolver> getMethodResolvers();
    
    // Specialized resolvers
    BeanResolver getBeanResolver();
    TypeLocator getTypeLocator();
    TypeConverter getTypeConverter();
    TypeComparator getTypeComparator();
    OperatorOverloader getOperatorOverloader();
    
    // Variable management
    void setVariable(String name, Object value);
    Object lookupVariable(String name);
    
    // Assignment support (6.2+)
    boolean isAssignmentEnabled();
    TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier);
}

{ .api }

StandardEvaluationContext

Class Definition

public class StandardEvaluationContext implements EvaluationContext {
    // Constructors
    public StandardEvaluationContext();
    public StandardEvaluationContext(Object rootObject);
    
    // Root object management
    public void setRootObject(Object rootObject);
    public void setRootObject(Object rootObject, TypeDescriptor typeDescriptor);
    public TypedValue getRootObject();
    
    // Accessor management
    public void setPropertyAccessors(List<PropertyAccessor> propertyAccessors);
    public List<PropertyAccessor> getPropertyAccessors();
    public void addPropertyAccessor(PropertyAccessor accessor);
    
    public void setIndexAccessors(List<IndexAccessor> indexAccessors);
    public List<IndexAccessor> getIndexAccessors();
    public void addIndexAccessor(IndexAccessor accessor);
    
    // Resolver management
    public void setConstructorResolvers(List<ConstructorResolver> constructorResolvers);
    public List<ConstructorResolver> getConstructorResolvers();
    public void addConstructorResolver(ConstructorResolver resolver);
    
    public void setMethodResolvers(List<MethodResolver> methodResolvers);
    public List<MethodResolver> getMethodResolvers();
    public void addMethodResolver(MethodResolver resolver);
    
    // Specialized component setters
    public void setBeanResolver(BeanResolver beanResolver);
    public BeanResolver getBeanResolver();
    
    public void setTypeLocator(TypeLocator typeLocator);
    public TypeLocator getTypeLocator();
    
    public void setTypeConverter(TypeConverter typeConverter);
    public TypeConverter getTypeConverter();
    
    public void setTypeComparator(TypeComparator typeComparator);
    public TypeComparator getTypeComparator();
    
    public void setOperatorOverloader(OperatorOverloader operatorOverloader);
    public OperatorOverloader getOperatorOverloader();
    
    // Variable management
    public void setVariables(Map<String, Object> variables);
    public void setVariable(String name, Object value);
    public Object lookupVariable(String name);
    
    // Function registration
    public void registerFunction(String name, Method method);
    
    // Assignment support
    public boolean isAssignmentEnabled();
    public void setAssignmentEnabled(boolean assignmentEnabled);
    public TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier);
}

{ .api }

Basic Usage

// Create with default settings
StandardEvaluationContext context = new StandardEvaluationContext();

// Create with root object
Person person = new Person("John", 30);
StandardEvaluationContext context = new StandardEvaluationContext(person);

// Set root object later
context.setRootObject(person);

// Basic expression evaluation
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
String name = exp.getValue(context, String.class); // "John"

{ .api }

Variables and Functions

StandardEvaluationContext context = new StandardEvaluationContext();

// Set individual variables
context.setVariable("greeting", "Hello");
context.setVariable("maxRetries", 3);

// Set multiple variables
Map<String, Object> variables = new HashMap<>();
variables.put("prefix", "Mr.");
variables.put("suffix", "Jr.");
context.setVariables(variables);

// Register functions
context.registerFunction("reverse", 
    StringUtils.class.getDeclaredMethod("reverse", String.class));
context.registerFunction("randomInt", 
    Math.class.getDeclaredMethod("random"));

// Use variables and functions in expressions
ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("#greeting + ' World'");
String result = exp.getValue(context, String.class); // "Hello World"

exp = parser.parseExpression("#reverse('hello')");
String reversed = exp.getValue(context, String.class);

exp = parser.parseExpression("#randomInt() * 100");
Double random = exp.getValue(context, Double.class);

{ .api }

Custom Resolvers

StandardEvaluationContext context = new StandardEvaluationContext();

// Add custom property accessor
context.addPropertyAccessor(new MapAccessor());
context.addPropertyAccessor(new ReflectivePropertyAccessor());

// Add custom method resolver
context.addMethodResolver(new ReflectiveMethodResolver());

// Set type locator with custom packages
StandardTypeLocator typeLocator = new StandardTypeLocator();
typeLocator.registerImport("com.myapp.model");
typeLocator.registerImport("com.myapp.util");
context.setTypeLocator(typeLocator);

// Set custom type converter
context.setTypeConverter(new StandardTypeConverter());

// Set bean resolver (typically in Spring context)
context.setBeanResolver(new BeanFactoryResolver(applicationContext));

{ .api }

SimpleEvaluationContext

Class Definition and Builder

public class SimpleEvaluationContext implements EvaluationContext {
    // Static factory methods
    public static Builder forReadOnlyDataBinding();
    public static Builder forReadWriteDataBinding();
    public static Builder forPropertyAccessors(PropertyAccessor... accessors);
    
    // Builder class
    public static final class Builder {
        // Method access configuration
        public Builder withInstanceMethods();
        public Builder withStaticMethods();
        
        // Collection access configuration
        public Builder withArrayAccess();
        public Builder withListAccess();
        public Builder withMapAccess();
        
        // Environment access
        public Builder withEnvironmentAccess();
        public Builder withSystemProperties();
        public Builder withSystemEnvironment();
        
        // Root object configuration
        public Builder withTypedRootObject(Object rootObject);
        public Builder withRootObject(Object rootObject);
        
        // Type conversion
        public Builder withConversionService(ConversionService conversionService);
        public Builder withTypeConverter(TypeConverter typeConverter);
        
        // Assignment control
        public Builder withAssignmentDisabled();
        
        // Build method
        public SimpleEvaluationContext build();
    }
}

{ .api }

Read-Only Data Binding

// Basic read-only context
SimpleEvaluationContext context = SimpleEvaluationContext
    .forReadOnlyDataBinding()
    .build();

// Read-only with method access
SimpleEvaluationContext context = SimpleEvaluationContext
    .forReadOnlyDataBinding()
    .withInstanceMethods()
    .build();

// Read-only with collection access
SimpleEvaluationContext context = SimpleEvaluationContext
    .forReadOnlyDataBinding()
    .withArrayAccess()
    .withListAccess()
    .withMapAccess()
    .build();

Person person = new Person("John", 30);
ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("name");
String name = exp.getValue(context, person, String.class); // "John"

exp = parser.parseExpression("name.length()"); // Requires withInstanceMethods()
Integer length = exp.getValue(context, person, Integer.class); // 4

{ .api }

Read-Write Data Binding

// Basic read-write context
SimpleEvaluationContext context = SimpleEvaluationContext
    .forReadWriteDataBinding()
    .build();

// Read-write with extended features
SimpleEvaluationContext context = SimpleEvaluationContext
    .forReadWriteDataBinding()
    .withInstanceMethods()
    .withArrayAccess()
    .withListAccess()
    .withMapAccess()
    .build();

Person person = new Person("John", 30);
ExpressionParser parser = new SpelExpressionParser();

// Reading
Expression exp = parser.parseExpression("name");
String name = exp.getValue(context, person, String.class); // "John"

// Writing
exp = parser.parseExpression("name");
exp.setValue(context, person, "Jane");
// person.name is now "Jane"

{ .api }

Environment Access

// Context with environment access
SimpleEvaluationContext context = SimpleEvaluationContext
    .forReadOnlyDataBinding()
    .withEnvironmentAccess()
    .withSystemProperties()
    .withSystemEnvironment()
    .build();

ExpressionParser parser = new SpelExpressionParser();

// Access system properties
Expression exp = parser.parseExpression("systemProperties['java.version']");
String javaVersion = exp.getValue(context, String.class);

// Access environment variables
exp = parser.parseExpression("systemEnvironment['PATH']");
String path = exp.getValue(context, String.class);

// Access Spring environment (when available)
exp = parser.parseExpression("environment.getProperty('my.app.property')");
String property = exp.getValue(context, String.class);

{ .api }

Custom Property Accessors

// Create context with specific property accessors
PropertyAccessor customAccessor = new CustomPropertyAccessor();
PropertyAccessor mapAccessor = new MapAccessor();

SimpleEvaluationContext context = SimpleEvaluationContext
    .forPropertyAccessors(customAccessor, mapAccessor)
    .withInstanceMethods()
    .build();

// Custom accessor implementation
public class CustomPropertyAccessor implements PropertyAccessor {
    @Override
    public Class<?>[] getSpecificTargetClasses() {
        return new Class<?>[] { MyCustomClass.class };
    }
    
    @Override
    public boolean canRead(EvaluationContext context, Object target, String name) {
        return target instanceof MyCustomClass && "customProperty".equals(name);
    }
    
    @Override
    public TypedValue read(EvaluationContext context, Object target, String name) {
        if (target instanceof MyCustomClass && "customProperty".equals(name)) {
            return new TypedValue(((MyCustomClass) target).getCustomValue());
        }
        throw new AccessException("Cannot read property: " + name);
    }
    
    @Override
    public boolean canWrite(EvaluationContext context, Object target, String name) {
        return false; // Read-only
    }
    
    @Override
    public void write(EvaluationContext context, Object target, String name, Object newValue) {
        throw new AccessException("Property is read-only: " + name);
    }
}

{ .api }

Type Conversion Configuration

// Using ConversionService
ConversionService conversionService = new DefaultConversionService();
SimpleEvaluationContext context = SimpleEvaluationContext
    .forReadOnlyDataBinding()
    .withConversionService(conversionService)
    .build();

// Using custom TypeConverter
TypeConverter customConverter = new CustomTypeConverter();
SimpleEvaluationContext context = SimpleEvaluationContext
    .forReadOnlyDataBinding()
    .withTypeConverter(customConverter)
    .build();

// Custom type converter implementation
public class CustomTypeConverter implements TypeConverter {
    private final ConversionService conversionService = new DefaultConversionService();
    
    @Override
    public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
        // Custom conversion logic
        if (sourceType.getType() == String.class && targetType.getType() == MyCustomType.class) {
            return true;
        }
        return conversionService.canConvert(sourceType, targetType);
    }
    
    @Override
    public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) {
        // Custom conversion implementation
        if (sourceType.getType() == String.class && targetType.getType() == MyCustomType.class) {
            return new MyCustomType((String) value);
        }
        return conversionService.convert(value, sourceType, targetType);
    }
}

{ .api }

Context Comparison

StandardEvaluationContext vs SimpleEvaluationContext

FeatureStandardEvaluationContextSimpleEvaluationContext
PerformanceSlower due to reflection overheadFaster, optimized for data binding
SecurityFull SpEL features (potential security risks)Restricted features (more secure)
FlexibilityHighly customizableLimited but sufficient for most use cases
Method InvocationAll methods by defaultOpt-in via builder
Type ReferencesT(Class) syntax supportedNot supported
Constructor Callsnew Constructor() supportedNot supported
Bean References@bean syntax supportedNot supported
AssignmentSupported by defaultOpt-in via builder
Thread SafetyNot thread-safeThread-safe when immutable

Choosing the Right Context

// Use SimpleEvaluationContext for:
// - Data binding scenarios
// - Security-sensitive environments
// - Performance-critical applications
// - Web applications with user input

SimpleEvaluationContext secureContext = SimpleEvaluationContext
    .forReadOnlyDataBinding()
    .withInstanceMethods()
    .build();

// Use StandardEvaluationContext for:
// - Full SpEL feature requirements
// - Complex expression scenarios
// - Integration with Spring frameworks
// - Development and testing environments

StandardEvaluationContext fullContext = new StandardEvaluationContext();

{ .api }

Advanced Context Usage

Context Inheritance and Chaining

// Base context with common configuration
StandardEvaluationContext baseContext = new StandardEvaluationContext();
baseContext.setVariable("appName", "MyApplication");
baseContext.setVariable("version", "1.0.0");
baseContext.registerFunction("formatDate", 
    DateUtils.class.getDeclaredMethod("format", Date.class, String.class));

// Specialized context inheriting from base
StandardEvaluationContext userContext = new StandardEvaluationContext();
userContext.setVariables(baseContext.lookupVariable("appName")); // Copy variables
userContext.setVariable("username", "john.doe");
userContext.setRootObject(currentUser);

// Context for specific operations
StandardEvaluationContext operationContext = new StandardEvaluationContext(userContext.getRootObject());
operationContext.setVariable("operation", "UPDATE");
operationContext.setVariable("timestamp", new Date());

{ .api }

Dynamic Context Configuration

public class DynamicContextFactory {
    
    public EvaluationContext createContext(String profile, Object rootObject) {
        switch (profile.toLowerCase()) {
            case "secure":
                return SimpleEvaluationContext
                    .forReadOnlyDataBinding()
                    .withRootObject(rootObject)
                    .build();
                    
            case "standard":
                return SimpleEvaluationContext
                    .forReadWriteDataBinding()
                    .withInstanceMethods()
                    .withArrayAccess()
                    .withListAccess()
                    .withMapAccess()
                    .withRootObject(rootObject)
                    .build();
                    
            case "full":
                StandardEvaluationContext context = new StandardEvaluationContext(rootObject);
                context.addPropertyAccessor(new ReflectivePropertyAccessor());
                context.addMethodResolver(new ReflectiveMethodResolver());
                return context;
                
            default:
                throw new IllegalArgumentException("Unknown profile: " + profile);
        }
    }
}

{ .api }

Context Pooling for Performance

public class EvaluationContextPool {
    private final Queue<StandardEvaluationContext> pool = new ConcurrentLinkedQueue<>();
    private final int maxSize;
    
    public EvaluationContextPool(int maxSize) {
        this.maxSize = maxSize;
    }
    
    public StandardEvaluationContext borrowContext() {
        StandardEvaluationContext context = pool.poll();
        if (context == null) {
            context = new StandardEvaluationContext();
            configureContext(context);
        }
        return context;
    }
    
    public void returnContext(StandardEvaluationContext context) {
        if (pool.size() < maxSize) {
            clearContext(context);
            pool.offer(context);
        }
    }
    
    private void configureContext(StandardEvaluationContext context) {
        // Standard configuration
        context.addPropertyAccessor(new ReflectivePropertyAccessor());
        context.addMethodResolver(new ReflectiveMethodResolver());
    }
    
    private void clearContext(StandardEvaluationContext context) {
        // Clear variables and root object for reuse
        context.setRootObject(null);
        context.setVariable("temp", null); // Clear known temporary variables
    }
}

{ .api }

Best Practices

Security Best Practices

// 1. Use SimpleEvaluationContext for user-provided expressions
SimpleEvaluationContext secureContext = SimpleEvaluationContext
    .forReadOnlyDataBinding()
    .build(); // Minimal permissions

// 2. Validate expressions before evaluation
public boolean isSafeExpression(String expression) {
    // Check for dangerous patterns
    return !expression.contains("T(") &&          // No type references
           !expression.contains("new ") &&        // No constructor calls
           !expression.contains("@") &&           // No bean references
           !expression.contains("#") &&           // No variables (if not needed)
           expression.length() < 200;             // Reasonable length limit
}

// 3. Use whitelisted property accessors
public class WhitelistPropertyAccessor implements PropertyAccessor {
    private final Set<String> allowedProperties = Set.of("name", "id", "status");
    
    @Override
    public boolean canRead(EvaluationContext context, Object target, String name) {
        return allowedProperties.contains(name) && 
               target instanceof SafeDataObject;
    }
    
    // ... other methods
}

{ .api }

Performance Best Practices

// 1. Reuse contexts when possible (SimpleEvaluationContext)
public class ContextManager {
    private final SimpleEvaluationContext sharedReadOnlyContext = 
        SimpleEvaluationContext.forReadOnlyDataBinding().build();
    
    public Object evaluateReadOnly(Expression expression, Object root) {
        return expression.getValue(sharedReadOnlyContext, root);
    }
}

// 2. Cache context configurations
public class CachedContextFactory {
    private final Map<String, EvaluationContext> contextCache = new ConcurrentHashMap<>();
    
    public EvaluationContext getContext(String type) {
        return contextCache.computeIfAbsent(type, this::createContext);
    }
    
    private EvaluationContext createContext(String type) {
        // Create context based on type
        return SimpleEvaluationContext.forReadOnlyDataBinding().build();
    }
}

// 3. Minimize context setup overhead
public EvaluationContext createOptimizedContext(Object root) {
    return SimpleEvaluationContext
        .forReadOnlyDataBinding()
        .withRootObject(root)              // Set root directly
        .build();                          // No additional configuration
}

{ .api }

Thread Safety Considerations

// SimpleEvaluationContext is thread-safe when immutable
public class ThreadSafeExpressionEvaluator {
    private final SimpleEvaluationContext sharedContext = 
        SimpleEvaluationContext.forReadOnlyDataBinding().build();
    private final ExpressionParser parser = new SpelExpressionParser();
    
    public Object evaluate(String expression, Object root) {
        // Safe to share context across threads for read-only operations
        Expression exp = parser.parseExpression(expression);
        return exp.getValue(sharedContext, root);
    }
}

// StandardEvaluationContext requires thread-local instances
public class ThreadLocalContextEvaluator {
    private final ThreadLocal<StandardEvaluationContext> contextHolder = 
        ThreadLocal.withInitial(this::createContext);
    
    private StandardEvaluationContext createContext() {
        StandardEvaluationContext context = new StandardEvaluationContext();
        // Configure context...
        return context;
    }
    
    public Object evaluate(String expression, Object root) {
        StandardEvaluationContext context = contextHolder.get();
        context.setRootObject(root);
        
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(expression);
        return exp.getValue(context);
    }
}

{ .api }

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework--spring-expression

docs

advanced-features.md

error-handling.md

evaluation-contexts.md

expression-evaluation.md

index.md

method-constructor-resolution.md

property-index-access.md

spel-configuration.md

type-system-support.md

tile.json