Introduce MapAccessor in SpEL and deprecate existing implementation

Prior to this commit, the MapAccessor for SpEL resided in the
org.springframework.context.expression package in the spring-context
module; however, it arguably should reside in the spring-expression
module so that it can be used whenever SpEL is used (without requiring
spring-context).

This commit therefore officially deprecates the MapAccessor in
spring-context for removal in favor of a new copy of the implementation
in the org.springframework.expression.spel.support package in the
spring-expression module.

Closes gh-35537
This commit is contained in:
Sam Brannen
2025-09-24 16:52:19 +02:00
parent 27b2243b43
commit 051adf267f
8 changed files with 210 additions and 121 deletions
@@ -16,18 +16,7 @@
package org.springframework.context.expression;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.CompilablePropertyAccessor;
import org.springframework.util.Assert;
/**
* SpEL {@link PropertyAccessor} that knows how to access the keys of a standard
@@ -36,11 +25,10 @@ import org.springframework.util.Assert;
* @author Juergen Hoeller
* @author Andy Clement
* @since 3.0
* @deprecated as of Spring Framework 7.0 in favor of {@link org.springframework.expression.spel.support.MapAccessor}.
*/
public class MapAccessor implements CompilablePropertyAccessor {
private final boolean allowWrite;
@Deprecated(since = "7.0", forRemoval = true)
public class MapAccessor extends org.springframework.expression.spel.support.MapAccessor {
/**
* Create a new {@code MapAccessor} for reading as well as writing.
@@ -57,88 +45,7 @@ public class MapAccessor implements CompilablePropertyAccessor {
* @see #canWrite
*/
public MapAccessor(boolean allowWrite) {
this.allowWrite = allowWrite;
}
@Override
public Class<?>[] getSpecificTargetClasses() {
return new Class<?>[] {Map.class};
}
@Override
public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
return (target instanceof Map<?, ?> map && map.containsKey(name));
}
@Override
public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
Assert.state(target instanceof Map, "Target must be of type Map");
Map<?, ?> map = (Map<?, ?>) target;
Object value = map.get(name);
if (value == null && !map.containsKey(name)) {
throw new MapAccessException(name);
}
return new TypedValue(value);
}
@Override
public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
return (this.allowWrite && target instanceof Map);
}
@Override
@SuppressWarnings("unchecked")
public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue)
throws AccessException {
Assert.state(target instanceof Map, "Target must be of type Map");
Map<Object, Object> map = (Map<Object, Object>) target;
map.put(name, newValue);
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public Class<?> getPropertyType() {
return Object.class;
}
@Override
public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) {
String descriptor = cf.lastDescriptor();
if (descriptor == null || !descriptor.equals("Ljava/util/Map")) {
if (descriptor == null) {
cf.loadTarget(mv);
}
CodeFlow.insertCheckCast(mv, "Ljava/util/Map");
}
mv.visitLdcInsn(propertyName);
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
}
/**
* Exception thrown from {@code read} in order to reset a cached
* PropertyAccessor, allowing other accessors to have a try.
*/
@SuppressWarnings("serial")
private static class MapAccessException extends AccessException {
private final String key;
public MapAccessException(String key) {
super("");
this.key = key;
}
@Override
public String getMessage() {
return "Map does not contain a value for key '" + this.key + "'";
}
super(allowWrite);
}
}
@@ -168,7 +168,7 @@ public class StandardBeanExpressionResolver implements BeanExpressionResolver {
StandardEvaluationContext sec = new StandardEvaluationContext(bec);
sec.addPropertyAccessor(new BeanExpressionContextAccessor());
sec.addPropertyAccessor(new BeanFactoryAccessor());
sec.addPropertyAccessor(new MapAccessor());
sec.addPropertyAccessor(new org.springframework.expression.spel.support.MapAccessor());
sec.addPropertyAccessor(new EnvironmentAccessor());
sec.setBeanResolver(new BeanFactoryResolver(beanFactory));
sec.setTypeLocator(new StandardTypeLocator(beanFactory.getBeanClassLoader()));