Spring Expression Language (SpEL)にマップオブジェクトをルートオブジェクとして受け取る

# ルートオブジェクトとは

[Spring Frameworkドキュメント](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions)によって、オブジェクに対して文字列を式として評価するこのオブジェクは"ルートオブジェク"と呼ばれている。

下の例は同じドキュメントからとった。例えばクラス"Inventor"が下に定義された。

```java
import java.util.Date;
import java.util.GregorianCalendar;

public class Inventor {

    private String name;
    private String nationality;
    private String[] inventions;
    private Date birthdate;
    private PlaceOfBirth placeOfBirth;

    public Inventor(String name, String nationality) {
        GregorianCalendar c= new GregorianCalendar();
        this.name = name;
        this.nationality = nationality;
        this.birthdate = c.getTime();
    }

    public Inventor(String name, Date birthdate, String nationality) {
        this.name = name;
        this.nationality = nationality;
        this.birthdate = birthdate;
    }

    public Inventor() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }

    public Date getBirthdate() {
        return birthdate;
    }

    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }

    public PlaceOfBirth getPlaceOfBirth() {
        return placeOfBirth;
    }

    public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
        this.placeOfBirth = placeOfBirth;
    }

    public void setInventions(String[] inventions) {
        this.inventions = inventions;
    }

    public String[] getInventions() {
        return inventions;
    }
}
```

"ルートオブジェク"は文字列を式として評価するメソッドの引数だ。

```java
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
// メソッドgetValue()が受け取る引数は"ルートオブジェクト"と呼ばれている
String name = (String) exp.getValue(tesla);
// nameは"Nikola Tesla"になる
```

# マップオブジェクトをルートオブジェクとして受け取る

使い方は下のソースコードに

```java
import java.util.HashMap;
import java.util.Map;

import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpelRootObjectExample {

	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		map.put("a", "String 'a'");
		map.put("b", "String 'b'");

		StandardEvaluationContext context = new StandardEvaluationContext();
		context.addPropertyAccessor(new MapAccessor());
		ExpressionParser parser = new SpelExpressionParser();
		Expression expA = parser.parseExpression("a");
		Expression expB = parser.parseExpression("b");

		// ルートオブジェクトとはマップオブジェクトにする
		System.out.println(expA.getValue(context, map));
		System.out.println(expB.getValue(context, map));
	}

}
```

出力は

```
String 'a'
String 'b'
```

# どこに使われるの？

Spring MVCとThymeleafテンプレートエンジン一緒に使う時、似ているソースコードがクラス"org.thymeleaf.spring5.expression.ThymeleafEvaluationContext"から見つけられる。

```java
public final class ThymeleafEvaluationContext
            extends StandardEvaluationContext
            implements IThymeleafEvaluationContext {

    public static final String THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME = "thymeleaf::EvaluationContext";
    private static final MapAccessor MAP_ACCESSOR_INSTANCE = new MapAccessor();
    private final ApplicationContext applicationContext;
    private IExpressionObjects expressionObjects = null;
    private boolean variableAccessRestricted = false;

    public ThymeleafEvaluationContext(final ApplicationContext applicationContext, final ConversionService conversionService) {
        super();

        Validate.notNull(applicationContext, "Application Context cannot be null");
        // ConversionService CAN be null

        this.applicationContext = applicationContext;
        this.setBeanResolver(new BeanFactoryResolver(applicationContext));
        if (conversionService != null) {
            this.setTypeConverter(new StandardTypeConverter(conversionService));
        }

        this.addPropertyAccessor(SPELContextPropertyAccessor.INSTANCE);
		// ここ!!
        this.addPropertyAccessor(MAP_ACCESSOR_INSTANCE);

    }
	// ...
}
```

メソッドorg.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(IExpressionContext, IStandardVariableExpression, StandardExpressionExecutionContext)を見る。クラスSPELContextMapWrapper(java.util.Mapインタフェースを実装する)はルートオブジェクトになり、文字列を式として評価する時使われている。

```java
            final IThymeleafEvaluationContext thymeleafEvaluationContext = (IThymeleafEvaluationContext) evaluationContext;

            /*
             * CONFIGURE THE IThymeleafEvaluationContext INSTANCE: expression objects and restrictions
             *
             * NOTE this is possible even if the evaluation context object is shared for the whole template execution
             * because evaluation contexts are not thread-safe and are only used in a single template execution
             */
            thymeleafEvaluationContext.setExpressionObjects(expressionObjects);
            thymeleafEvaluationContext.setVariableAccessRestricted(expContext.getRestrictVariableAccess());

            /*
             * RESOLVE THE EVALUATION ROOT
             */
            final ITemplateContext templateContext = (context instanceof ITemplateContext ? (ITemplateContext) context : null);
            final Object evaluationRoot =
                    (useSelectionAsRoot && templateContext != null && templateContext.hasSelectionTarget()?
                            templateContext.getSelectionTarget() : new SPELContextMapWrapper(context, thymeleafEvaluationContext));

            /*
             * If no conversion is to be made, JUST RETURN
             */
            if (!expContext.getPerformTypeConversion()) {
                return exp.expression.getValue(thymeleafEvaluationContext, evaluationRoot);
            }
```