【小家Spring】SpEL你感兴趣的实现原理浅析spring-expression~(SpelExpressionParser、EvaluationContext、rootObject)(中)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【小家Spring】SpEL你感兴趣的实现原理浅析spring-expression~(SpelExpressionParser、EvaluationContext、rootObject)(中)

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;
}


它的继承树如下:


image.png


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的继承树如下:

image.png


主要有两个开箱即用的实现: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()进去的取值时,是必须指定前缀的。介绍的黑科技,也有它的使用注意事项哦~


相关文章
|
3月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
3月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
4月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
117 0
|
23天前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
4月前
|
设计模式 监控 Java
解析Spring Cloud中的断路器模式原理
解析Spring Cloud中的断路器模式原理
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
117 9
|
30天前
|
设计模式 Java Spring
Spring Boot监听器的底层实现原理
Spring Boot监听器的底层实现原理主要基于观察者模式(也称为发布-订阅模式),这是设计模式中用于实现对象之间一对多依赖的一种常见方式。在Spring Boot中,监听器的实现依赖于Spring框架提供的事件监听机制。
25 1
|
2月前
|
XML Java 开发者
经典面试---spring IOC容器的核心实现原理
作为一名拥有十年研发经验的工程师,对Spring框架尤其是其IOC(Inversion of Control,控制反转)容器的核心实现原理有着深入的理解。
99 3
|
26天前
|
XML 前端开发 Java
拼多多1面:聊聊Spring MVC的工作原理!
本文详细剖析了Spring MVC的工作原理,涵盖其架构、工作流程及核心组件。Spring MVC采用MVC设计模式,通过DispatcherServlet、HandlerMapping、Controller和ViewResolver等组件高效处理Web请求。文章还探讨了DispatcherServlet的初始化和请求处理流程,以及HandlerMapping和Controller的角色。通过理解这些核心概念,开发者能更好地构建可维护、可扩展的Web应用。适合面试准备和技术深挖
40 0
|
27天前
|
负载均衡 Java API
Spring Cloud原理详解
Spring Cloud原理详解
61 0