当前位置: 首页 > article >正文

26. Spring源码篇之SpEL表达式的上下文EvaluationContext

简介

上节已经介绍了spring表达式,也举了很多案例,本文是对spring表达式上下文EvaluationContext的一个补充

EvaluationContext在spring表达式中非常重要,里面可以定义数据应该从哪里来 比如 @Value(“#{beanName}”),希望应该可以从spring
中获取单例Bean,都可以由它实现

接口定义

public interface EvaluationContext {

	TypedValue getRootObject();

	List<PropertyAccessor> getPropertyAccessors();

	List<ConstructorResolver> getConstructorResolvers();

	List<MethodResolver> getMethodResolvers();

	@Nullable
	BeanResolver getBeanResolver();

	TypeLocator getTypeLocator();

	TypeConverter getTypeConverter();

	TypeComparator getTypeComparator();

	OperatorOverloader getOperatorOverloader();

	void setVariable(String name, @Nullable Object value);
	
	@Nullable
	Object lookupVariable(String name);

}

该类的一个重要子类就是StandardEvaluationContext,前面我们也介绍了,默认实现就是该类

本文主要介绍该类的setVariable,registerFunction以及addPropertyAccessor(PropertyAccessor accessor)方法

其中PropertyAccessor是一个属性解析器,SpEl中的属性可以找到匹配的属性解析器,调用其read方法,读取内容,其接口定义如下

public interface PropertyAccessor {
	
	// 就是与StandardEvaluationContext中的rootObject匹配的类型
	@Nullable
	Class<?>[] getSpecificTargetClasses();

    // 返回true,可以读
	boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException;

    // 编写读的逻辑
	TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException;
    
    // 不可以写返回false
	boolean canWrite(EvaluationContext context, @Nullable Object target, String name) throws AccessException;

	void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue)
			throws AccessException;

}

接下来我们就自定义一个PropertyAccessor,让其从配置文件shura.properties文件读

public class PropertiesAccessor implements PropertyAccessor {
    @Override
    public Class<?>[] getSpecificTargetClasses() {
        return new Class<?>[]{Properties.class};
    }

    @Override
    public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
        return true;
    }

    @Override
    public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
        return new TypedValue(((Properties) target).getProperty(name));
    }

    @Override
    public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
        return false;
    }

    @Override
    public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {

    }
}

上面代码,表示传入的是一个Properties的时候生效,canRead返回true,read方法里面就直接通过Properties.getProperty读取属性值

下面我们以一个例子展示setVariable,registerFunction,addPropertyAccessor的使用

public class ExpressionTest {

    // 解析模版
    private static final ParserContext parserContext = new ParserContext() {
        @Override
        public boolean isTemplate() {
            return true;
        }

        @Override
        public String getExpressionPrefix() {
            return "#{";
        }

        @Override
        public String getExpressionSuffix() {
            return "}";
        }
    };


    private final static ExpressionParser parser = new SpelExpressionParser();

    private static void evaluate(String text) {
        evaluate(text, null);
    }

    private static void evaluate(String text, StandardEvaluationContext context) {
        Expression expression = parser.parseExpression(text, parserContext);
        System.out.println(expression.getValue(context));
    }

    public static void main(String[] args) throws NoSuchMethodException, IOException {

        Properties properties = new Properties();
        properties.load(Files.newInputStream(ResourceUtils.getFile("classpath:shura.properties").toPath()));
        
        // 构造StandardEvaluationContext,传入的是一个Properties与PropertiesAccessor相匹配
        StandardEvaluationContext context = new StandardEvaluationContext(properties);
        // addPropertyAccessor
        context.addPropertyAccessor(new PropertiesAccessor());
        // setVariable
        context.setVariable("env", "prod");
        // registerFunction
        context.registerFunction("long", Long.class.getDeclaredMethod("valueOf", long.class));
        
        // 当前面加上一个#就会从variable去获取,就是上面set的变量以及注册的function
        evaluate("#{#env}", context);
        evaluate("#{#long('123')}", context);
        
        // 直接写一个字符串,会去找匹配的PropertiesAccessor如果没有匹配会通过反射去找
        // 找到PropertiesAccessor执行read方法
        evaluate("#{content}", context);

    }

}

shura.properties内容

content=hello shura

输出结果

prod
123
hello shura

spring中的应用

在spring中,我们给属性上面加上@Value(“#{beanName}”),其实是会根据beanName去找Bean,为什么呢

因为spring中的表达式解析会进入这个方法org.springframework.context.expression.StandardBeanExpressionResolver#evaluate

该方法前面文件也介绍过,源码如下

public Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException {
    try {
        Expression expr = this.expressionCache.get(value);
        if (expr == null) {
            expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
            this.expressionCache.put(value, expr);
        }
        StandardEvaluationContext sec = this.evaluationCache.get(evalContext);
        if (sec == null) {
            sec = new StandardEvaluationContext(evalContext);
            sec.addPropertyAccessor(new BeanExpressionContextAccessor());
            sec.addPropertyAccessor(new BeanFactoryAccessor());
            sec.addPropertyAccessor(new MapAccessor());
            sec.addPropertyAccessor(new EnvironmentAccessor());
            sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
            sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader()));
            ConversionService conversionService = evalContext.getBeanFactory().getConversionService();
            if (conversionService != null) {
                sec.setTypeConverter(new StandardTypeConverter(conversionService));
            }
            this.evaluationCache.put(evalContext, sec);
        }
        return expr.getValue(sec);
    }
    catch (Throwable ex) {
        throw new BeanExpressionException("Expression parsing failed", ex);
    }
}

上面代码可以看出StandardEvaluationContext中的rootObject是BeanExpressionContext类型,并且设置了属性解析器BeanExpressionContextAccessor

我们来看BeanExpressionContextAccessor的逻辑

public class BeanExpressionContextAccessor implements PropertyAccessor {

	@Override
	public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
		return (target instanceof BeanExpressionContext && ((BeanExpressionContext) target).containsObject(name));
	}

	@Override
	public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
		Assert.state(target instanceof BeanExpressionContext, "Target must be of type BeanExpressionContext");
		return new TypedValue(((BeanExpressionContext) target).getObject(name));
	}

	@Override
	public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
		return false;
	}

	@Override
	public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue)
			throws AccessException {

		throw new AccessException("Beans in a BeanFactory are read-only");
	}

	@Override
	public Class<?>[] getSpecificTargetClasses() {
		return new Class<?>[] {BeanExpressionContext.class};
	}

}

通过BeanExpressionContext.getObject(name)获取值,代码如下

public Object getObject(String key) {
    if (this.beanFactory.containsBean(key)) {
        return this.beanFactory.getBean(key);
    }
    else if (this.scope != null) {
        return this.scope.resolveContextualObject(key);
    }
    else {
        return null;
    }
}

实际上就是通过beanFactory.getBean获取的值,到这我们就知道为什么@Value(“#{beanName}”)会去spring中找Bean了

对于SpEl表达式的补充就到这了


欢迎关注,学习不迷路!


http://www.kler.cn/a/145831.html

相关文章:

  • StarRocks 3.4 发布--AI 场景新支点,Lakehouse 能力再升级
  • 统信V20 1070e X86系统编译安装mysql-5.7.44版本以及主从构建
  • 第5章:Python TDD定义Dollar对象相等性
  • 三电平空间矢量详解
  • Spark常见面试题-部分待更新
  • java图像文件的显示
  • count=0语句的位置
  • 电力感知边缘计算网关产品设计方案-网关软件设计方案
  • 自定义指令
  • 网络和Linux网络_5(应用层)HTTP协议(方法+报头+状态码)
  • 基于uniapp+vue微信小程序的健康饮食管理系统 907m6
  • C_5练习题
  • 文本转语音:微软语音合成标记语言 (SSML) 文本结构和事件
  • web前端之vue和echarts的堆叠柱状图顶部显示总数、鼠标悬浮工具提示、设置图例的显示与隐藏、label、legend、tooltip
  • 【Unity入门】Input.GetAxis(““)控制物体移动、旋转
  • 在vue或者react或angular中,模板表达式中的箭头函数是无效的吗?为什么无效?
  • 安卓吸顶效果
  • C#异常处理-throw语句
  • C语言——深入理解指针(2)
  • 二叉树经典面试题—折纸
  • WPF绘图技术介绍
  • Python基于jieba+wordcloud实现文本分词、词频统计、条形图绘制及不同主题的词云图绘制
  • 记一次oracle错误处理
  • 软件工程简明教程
  • 【Amazon】通过直接连接的方式导入 KubeSphere集群至KubeSphere主容器平台
  • 在我国干独立游戏开发有多难?