Expression
表示的是表达式对象。能够根据上下文对象对自身进行计算的表达式。
封装以前分析的表达式字符串的详细信息。
// @since 3.0 表达式计算的通用抽象。 该接口提供的方法非常非常之多~~~ 但不要怕大部分都是重载的~~~ public interface Expression { // 返回原始表达式的字符串~~~ String getExpressionString(); // 使用一个默认的标准的context执行计算~~~ @Nullable Object getValue() throws EvaluationException; // SpEL内部帮你转换~~~ 使用的是默认的context @Nullable <T> T getValue(@Nullable Class<T> desiredResultType) throws EvaluationException; // 根据指定的根对象计算此表达式 @Nullable Object getValue(Object rootObject) throws EvaluationException; @Nullable <T> T getValue(Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException; // 根据指定的上下文:EvaluationContext来计算值~~~ rootObject:跟对象 @Nullable Object getValue(EvaluationContext context) throws EvaluationException; // 以rootObject作为表达式的root对象来计算表达式的值。 // root对象:比如parser.parseExpression("name").getValue(person);相当于去person里拿到name属性的值。这个person就叫root对象 @Nullable Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException; @Nullable <T> T getValue(EvaluationContext context, @Nullable Class<T> desiredResultType) throws EvaluationException; @Nullable <T> T getValue(EvaluationContext context, Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException; // 返回可传递给@link setvalue的最一般类型 @Nullable Class<?> getValueType() throws EvaluationException; @Nullable Class<?> getValueType(Object rootObject) throws EvaluationException; @Nullable Class<?> getValueType(EvaluationContext context) throws EvaluationException; @Nullable Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException; @Nullable TypeDescriptor getValueTypeDescriptor() throws EvaluationException; @Nullable TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException; @Nullable TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException; @Nullable TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException; // 确定是否可以写入表达式,即可以调用setValue() boolean isWritable(Object rootObject) throws EvaluationException; boolean isWritable(EvaluationContext context) throws EvaluationException; boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException; // 在提供的上下文中将此表达式设置为提供的值。 void setValue(Object rootObject, @Nullable Object value) throws EvaluationException; void setValue(EvaluationContext context, @Nullable Object value) throws EvaluationException; void setValue(EvaluationContext context, Object rootObject, @Nullable Object value) throws EvaluationException; }
它的继承树如下:
SpelExpression
这个是我们核心,甚至也是目前SpEL的唯一实现。
表达式可以独立计算,也可以在指定的上下文中计算。在表达式计算期间,可能会要求上下文解析:对类型、bean、属性和方法的引用。
public class SpelExpression implements Expression { // 在编译表达式之前解释该表达式的次数。 private static final int INTERPRETED_COUNT_THRESHOLD = 100; // 放弃前尝试编译表达式的次数 private static final int FAILED_ATTEMPTS_THRESHOLD = 100; private final String expression; // AST:抽象语法树~ private final SpelNodeImpl ast; // SpelNodeImpl的实现类非常非常之多~~~ private final SpelParserConfiguration configuration; @Nullable private EvaluationContext evaluationContext; // 如果没有指定,就会用默认的上下文 new StandardEvaluationContext() // 如果该表达式已经被编译了,就会放在这里 @since 4.1 Spring内部并没有它的实现类 尴尬~~~编译是要交给我们自己实现??? @Nullable private CompiledExpression compiledAst; // 表达式被解释的次数-达到某个限制时可以触发编译 private volatile int interpretedCount = 0; // 编译尝试和失败的次数——使我们最终放弃了在似乎不可能编译时尝试编译它的尝试。 private volatile int failedAttempts = 0; // 唯一构造函数~ public SpelExpression(String expression, SpelNodeImpl ast, SpelParserConfiguration configuration) { this.expression = expression; this.ast = ast; this.configuration = configuration; } ... // 若没有指定,这里会使用默认的StandardEvaluationContext上下文~ public EvaluationContext getEvaluationContext() { if (this.evaluationContext == null) { this.evaluationContext = new StandardEvaluationContext(); } return this.evaluationContext; } ... @Override @Nullable public Object getValue() throws EvaluationException { // 如果已经被编译过,就直接从编译后的里getValue即可~~~~ if (this.compiledAst != null) { try { EvaluationContext context = getEvaluationContext(); return this.compiledAst.getValue(context.getRootObject().getValue(), context); } catch (Throwable ex) { // If running in mixed mode, revert to interpreted if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) { this.interpretedCount = 0; this.compiledAst = null; } else { throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION); } } } ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration); // 比如此处SeEl是加法+,所以ast为:OpPlus语法树去处理的~~~ Object result = this.ast.getValue(expressionState); // 检查是否需要编译它~~~ checkCompile(expressionState); return result; } ... // 备注:数据转换都是EvaluationContext.getTypeConverter() 来进行转换 // 注意:此处的TypeConverter为`org.springframework.expression`的 只有一个实现类:StandardTypeConverter // 它内部都是委托给ConversionService去做的,具体是`DefaultConversionService`去做的~ @Override @Nullable public Class<?> getValueType() throws EvaluationException { return getValueType(getEvaluationContext()); } @Override @Nullable public Class<?> getValueType(Object rootObject) throws EvaluationException { return getValueType(getEvaluationContext(), rootObject); } @Override @Nullable public Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration); TypeDescriptor typeDescriptor = this.ast.getValueInternal(expressionState).getTypeDescriptor(); return (typeDescriptor != null ? typeDescriptor.getType() : null); } ... @Override public boolean isWritable(Object rootObject) throws EvaluationException { return this.ast.isWritable(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration)); } ... @Override public void setValue(Object rootObject, @Nullable Object value) throws EvaluationException { this.ast.setValue(new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration), value); } ... }
这个是我们最主要的一个Expression表达式,AST是它的心脏。
LiteralExpression
Literal:字面意义的 它没有计算的活,只是表示字面意思(字面量)。
so,它里面处理的类型:全部为String.class,并且和EvaluationContext无关
CompositeStringExpression
表示一个分为多个部分的模板表达式(它只处理Template模式)。每个部分都是表达式,但模板的纯文本部分将表示为LiteralExpression对象。显然它是一个聚合
public class CompositeStringExpression implements Expression { private final String expressionString; // 内部持有多个Expression~~~ private final Expression[] expressions; public CompositeStringExpression(String expressionString, Expression[] expressions) { this.expressionString = expressionString; this.expressions = expressions; } // 它是把每个表达式的值都拼接起来了 因为它只会运用于Template模式~~~~~ @Override public String getValue() throws EvaluationException { StringBuilder sb = new StringBuilder(); for (Expression expression : this.expressions) { String value = expression.getValue(String.class); if (value != null) { sb.append(value); } } return sb.toString(); } // 返回值的类型一样的永远是String.class @Override public Class<?> getValueType(Object rootObject) throws EvaluationException { return String.class; } // 不可set @Override public boolean isWritable(EvaluationContext context) { return false; } @Override public void setValue(Object rootObject, @Nullable Object value) throws EvaluationException { throw new EvaluationException(this.expressionString, "Cannot call setValue on a composite expression"); } }
这个表达式的计算中,和EvaluationContext这个上下文有莫大的关系,因此有必要看看它
EvaluationContext:评估/计算的上下文
表达式在计算上下文中执行。在表达式计算期间遇到引用时,正是在这种上下文中解析引用。它的默认实现为:StandardEvaluationContext
EvaluationContext可以理解为parser 在这个环境里执行parseExpression解析操作。
比如说我们现在往**ctx(一个EvaluationContext )**中放入一个 对象list (注:假设list里面已经有数据,即list[0]=true)
ctx.setVariable("list" , list); //可以理解为往ctx域 里放了一个list变量
接下来要想获取或设置list的值都要在ctx范围内才能找到:
parser.parseExpression("#list[0]").getValue(ctx);//在ctx这个环境里解析出list[0]的值 parser.parseExpression("#list[0]").setValue(ctx , "false");//在ctx这个环境中奖 list[0]设为false
假如我们又往ctx中放入一个person对象(假设person已经实例化并且person.name的值是fsx)
ctx.setVariable("p", person);
那么取其中name属性要像下面这样:
parser.parseExpression("#p.name").getValue(ctx);//结果是 fsx
但是若是我们将ctx的root设为person 取name的时候就可以省略root对象这个前缀("#"
)了
//StandardEvaluationContext是EvaluationContext的子类 提供了setRootObject方法 ((StandardEvaluationContext)ctx2).setRootObject(person); parser.parseExpression("name").getValue(ctx2); //访问rootobject即person的属性那么 结果:fsx // 这种方式同 parser.parseExpression("name").getValue(person); //它的意思是直接从root对象里找~~~~
这样获取name就会去root对象里直接找 而不用#p
这样子了~~~~ 这就是root对象的用处~它在后面的属性访问器中用处更大
public interface EvaluationContext { // 上下文可议持有一个根对象~~ TypedValue getRootObject(); // 返回属性访问器列表,这些访问器将依次被要求读取/写入属性 注意此处的属性访问器是el包自己的,不是bean包下的~~~ // ReflectivePropertyAccessor(DataBindingPropertyAccessor):通过反射读/写对象的属性~ // BeanFactoryAccessor:这个属性访问器让支持bean从bean工厂里获取 // EnvironmentAccessor:可以从环境中.getProperty(name) // BeanExpressionContextAccessor:和BeanExpressionContext相关 // MapAccessor:可以从map中获取值~~~ List<PropertyAccessor> getPropertyAccessors(); // ConstructorResolver它只有一个实现:ReflectiveConstructorResolver List<ConstructorResolver> getConstructorResolvers(); // 它的实现:ReflectiveMethodResolver/DataBindingMethodResolver List<MethodResolver> getMethodResolvers(); /** * Return a bean resolver that can look up beans by name. */ // 返回一个处理器:它能够通过beanName找到bean // BeanResolver:唯一实现 BeanFactoryResolver 它内部持有BeanFactory的引用~ return this.beanFactory.getBean(beanName); @Nullable BeanResolver getBeanResolver(); // 返回一个类型定位器,该定位器可用于通过短名称或完全限定名称查找类型 唯一实现:StandardTypeLocator TypeLocator getTypeLocator(); // TypeConverter:唯一实现为StandardTypeConverter 其实还是依赖DefaultConversionService的 TypeConverter getTypeConverter(); TypeComparator getTypeComparator(); // 处理重载的 OperatorOverloader getOperatorOverloader(); // 这两个方法,就是在这个上下文里设置值、查找值的~~~~ void setVariable(String name, @Nullable Object value); @Nullable Object lookupVariable(String name); }
EvaluationContext的继承树如下:
主要有两个开箱即用的实现:SimpleEvaluationContext和StandardEvaluationContext
SimpleEvaluationContext
公开仅支持部分的SpEL的支持。它有意限制的表达式类别~~
旨在仅支持SpEL语言语法的一个子集,它不包括 Java类型引用,构造函数和bean引用等等。它还要求明确选择对表达式中属性和方法的支持级别。
StandardEvaluationContext
公开支持全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。这也是
public class StandardEvaluationContext implements EvaluationContext { private TypedValue rootObject; @Nullable private volatile List<PropertyAccessor> propertyAccessors; @Nullable private volatile List<ConstructorResolver> constructorResolvers; @Nullable private volatile List<MethodResolver> methodResolvers; @Nullable private volatile ReflectiveMethodResolver reflectiveMethodResolver; @Nullable private BeanResolver beanResolver; @Nullable private TypeLocator typeLocator; @Nullable private TypeConverter typeConverter; private TypeComparator typeComparator = new StandardTypeComparator(); private OperatorOverloader operatorOverloader = new StandardOperatorOverloader(); // 上下文变量 就是一个Map private final Map<String, Object> variables = new ConcurrentHashMap<>(); */ public StandardEvaluationContext() { this.rootObject = TypedValue.NULL; } */ public StandardEvaluationContext(Object rootObject) { this.rootObject = new TypedValue(rootObject); } ... // 省略get/set方法 // 如果为null,就accessors.add(new ReflectivePropertyAccessor()); @Override public List<PropertyAccessor> getPropertyAccessors() { return initPropertyAccessors(); } // 意思是把添加进来的accessor 放在默认的前面。。。 public void addPropertyAccessor(PropertyAccessor accessor) { addBeforeDefault(initPropertyAccessors(), accessor); } // 默认 resolvers.add(new ReflectiveConstructorResolver()); @Override public List<ConstructorResolver> getConstructorResolvers() { return initConstructorResolvers(); } public void addConstructorResolver(ConstructorResolver resolver) { addBeforeDefault(initConstructorResolvers(), resolver); } ... // set null 有移除的效果 @Override public void setVariable(@Nullable String name, @Nullable Object value) { // For backwards compatibility, we ignore null names here... // And since ConcurrentHashMap cannot store null values, we simply take null // as a remove from the Map (with the same result from lookupVariable below). if (name != null) { if (value != null) { this.variables.put(name, value); } else { this.variables.remove(name); } } } public void setVariables(Map<String, Object> variables) { variables.forEach(this::setVariable); } // 注册自定义函数,原理还是variables~~~~~~~~ public void registerFunction(String name, Method method) { this.variables.put(name, method); } @Override @Nullable public Object lookupVariable(String name) { return this.variables.get(name); } public void registerMethodFilter(Class<?> type, MethodFilter filter) throws IllegalStateException { initMethodResolvers(); ReflectiveMethodResolver resolver = this.reflectiveMethodResolver; if (resolver == null) { throw new IllegalStateException( "Method filter cannot be set as the reflective method resolver is not in use"); } resolver.registerMethodFilter(type, filter); } ... }
使用 setRootObject 方法来设置根对象,使用 setVariable 方法来注册自定义变量,使用 registerFunction 来注册自定义函数等等(registerFunction 方法进行注册自定义函数,其实完全可以使用 setVariable 代替,两者其实本质是一样的)
EvaluationContext的作用类似于OGNL中的StackContext,EvaluationContext可以包含多个对象,但只能有一个root对象。
当表达式中包含变量时,SpEL就会根据EvaluationContext中变量的值对表达式进行计算。
往EvaluationContext里放入对象方法:setVariable(String name,Object value);向EvaluationContext中放入value对象,该对象名为name
需要注意的是EvaluationContext接口中并没有定义设置root对象的方法,所以我们可以在StandardEvaluationContext里来设置root对象:setRootObject(Object rootObject) 默认它用#root取得此root对象,在SpEL中访问root对象的属性时,可以省略#root对象前缀,比如#root.name 可以简写成 name(注意不是写成#name)
setVariable()的使用
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); Person person = new Person("fsx", 30); List<String> list = new ArrayList<String>() {{ add("fsx"); add("周杰伦"); }}; Map<String, Integer> map = new HashMap<String, Integer>() {{ put("fsx", 18); put("周杰伦", 40); }}; EvaluationContext ctx = new StandardEvaluationContext(); //把list和map都放进环境变量里面去 ctx.setVariable("myPerson", person); ctx.setVariable("myList", list); ctx.setVariable("myMap", map); //============================================ System.out.println(parser.parseExpression("#myPerson").getValue(ctx)); //Person{name='fsx', age=30} System.out.println(parser.parseExpression("#myPerson.name").getValue(ctx)); //fsx // setVariable方式取值不能像root一样,前缀不可省略~~~~~ System.out.println(parser.parseExpression("#name").getValue(ctx)); //null 显然找不到这个key就返回null呗~~~ // 不写前缀默认去root找,找出一个null。再访问name属性那可不报错了吗 //System.out.println(parser.parseExpression("name").getValue(ctx)); // Property or field 'name' cannot be found on null System.out.println(parser.parseExpression("#myList").getValue(ctx)); // [fsx, 周杰伦] System.out.println(parser.parseExpression("#myList[1]").getValue(ctx)); // 周杰伦 // 请注意对Map取值两者的区别:中文作为key必须用''包起来 当然['fsx']也是没有问题的 System.out.println(parser.parseExpression("#myMap[fsx]").getValue(ctx)); //18 System.out.println(parser.parseExpression("#myMap['周杰伦']").getValue(ctx)); //40 // =========若采用#key引用的变量不存在,返回的是null,并不会报错哦============== System.out.println(parser.parseExpression("#map").getValue(ctx)); //null // 黑科技:SpEL内直接可以使用new方式创建实例 能创建数组、List 但不能创建普通的实例对象(难道是我还不知道)~~~~ System.out.println(parser.parseExpression("new String[]{'java','spring'}").getValue()); //[Ljava.lang.String;@30b8a058 System.out.println(parser.parseExpression("{'java','c语言','PHP'}").getValue()); //[java, c语言, PHP] 创建List System.out.println(parser.parseExpression("new Person()").getValue()); //A problem occurred whilst attempting to const }
需要注意一点:setVariable()
进去的取值时,是必须指定前缀的。介绍的黑科技,也有它的使用注意事项哦~