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表达式的补充就到这了


欢迎关注,学习不迷路!

你可能感兴趣的:(spring,framework,spring,java)