SnEL 表达式上下文增强和定制
SnEL 的上下文接口为 java.util.function.Function
,可以接收 Map::get
作为上下文。
1、基础上下文
Map<String, Object> context = new HashMap<>();
context.put("Math", Math.class);
System.out.println(SnEL.eval("Math.abs(-5) > 4 ? 'A' : 'B'", context));
2、增强上下文
Function
接口有很大的限局,不能接收 Bean 作为上下文。使用 EnhanceContext
可以增强上下文:
- Map 作为上下文
- Bean 作为上下文
- 可以支持虚拟变量
root
、this
使用 map 作上下文
Map<String, Object> map = new HashMap<>();
map.put("Math", Math.class);
System.out.println(SnEL.eval("Math.abs(-5) > 4 ? 'A' : 'B'", new EnhanceContext(map)));
使用 bean 作上下文
User user = new User();
System.out.println(SnEL.eval("userId > 12 ? 'A' : 'B'", new EnhanceContext(user)));
System.out.println(SnEL.eval("root.userId > 12 ? 'A' : 'B'", new EnhanceContext(user)));
使用 root 虚拟变量
System.out.println(SnEL.eval("root ? 'A' : 'B'", new EnhanceContext(true)));
3、上下文定制参考
参考 EnhanceContext 的实现
public class EnhanceContext<T extends Object, Slf extends EnhanceContext> implements Function<String, Object>, TypeGuidance, PropertiesGuidance, ReturnGuidance {
protected final T target;
protected final boolean isMap;
private TypeGuidance typeGuidance = TypeGuidanceUnsafety.INSTANCE;
private Properties properties;
private boolean allowPropertyDefault = true;
private boolean allowPropertyNesting = false;
private boolean allowTextAsProperty = false;
private boolean allowReturnNull = false;
public EnhanceContext(T target) {
this.target = target;
this.isMap = target instanceof Map;
}
public Slf forProperties(Properties properties) {
this.properties = properties;
return (Slf) this;
}
public Slf forAllowPropertyDefault(boolean allowPropertyDefault) {
this.allowPropertyDefault = allowPropertyDefault;
return (Slf) this;
}
public Slf forAllowPropertyNesting(boolean allowPropertyNesting) {
this.allowPropertyNesting = allowPropertyNesting;
return (Slf) this;
}
public Slf forAllowTextAsProperty(boolean allowTextAsProperty) {
this.allowTextAsProperty = allowTextAsProperty;
return (Slf) this;
}
public Slf forAllowReturnNull(boolean allowReturnNull) {
this.allowReturnNull = allowReturnNull;
return (Slf) this;
}
public Slf forTypeGuidance(TypeGuidance typeGuidance) {
this.typeGuidance = typeGuidance;
return (Slf) this;
}
private Object lastValue;
@Override
public Object apply(String name) {
if (target == null) {
return null;
}
if ("root".equals(name)) {
return target;
}
if ("this".equals(name)) {
if (lastValue == null) {
return target;
} else {
return lastValue;
}
}
if (isMap) {
lastValue = ((Map) target).get(name);
} else {
PropertyHolder tmp = ReflectionUtil.getProperty(target.getClass(), name);
try {
lastValue = tmp.getValue(target);
} catch (Throwable e) {
throw new EvaluationException("Failed to access property: " + name, e);
}
}
return lastValue;
}
//TypeGuidance
@Override
public Class<?> getType(String typeName) throws EvaluationException {
if (typeGuidance == null) {
throw new IllegalStateException("The current context is not supported: 'T(.)'");
} else {
return typeGuidance.getType(typeName);
}
}
public T getTarget() {
//方便单测用
return target;
}
public TypeGuidance getTypeGuidance() {
//方便单测用
return typeGuidance;
}
//PropertiesGuidance
@Override
public Properties getProperties() {
return properties;
}
@Override
public boolean allowPropertyDefault() {
return allowPropertyDefault;
}
@Override
public boolean allowPropertyNesting() {
return allowPropertyNesting;
}
@Override
public boolean allowTextAsProperty() {
return allowTextAsProperty;
}
//ReturnGuidance
@Override
public boolean allowReturnNull() {
return allowReturnNull;
}
}