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

简介: 【小家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()进去的取值时,是必须指定前缀的。介绍的黑科技,也有它的使用注意事项哦~


相关文章
|
6月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
796 22
|
6月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
2358 0
|
10月前
|
监控 安全 Java
Spring AOP实现原理
本内容主要介绍了Spring AOP的核心概念、实现机制及代理生成流程。涵盖切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)等关键概念,解析了JDK动态代理与CGLIB代理的原理及对比,并深入探讨了通知执行链路和责任链模式的应用。同时,详细分析了AspectJ注解驱动的AOP解析过程,包括切面识别、切点表达式匹配及通知适配为Advice的机制,帮助理解Spring AOP的工作原理与实现细节。
1487 13
|
5月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
5月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
652 2
|
7月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
1051 1
|
11月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
5291 116
|
10月前
|
前端开发 Java 数据库连接
Spring核心原理剖析与解说
每个部分都是将一种巨大并且复杂的技术理念传达为更易于使用的接口,而这就是Spring的价值所在,它能让你专注于开发你的应用,而不必从头开始设计每一部分。
289 32
|
8月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
312 0
|
8月前
|
监控 架构师 NoSQL
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)

热门文章

最新文章