きり丸の技術日記

技術検証したり、資格等をここに残していきます。

SpringのSpELでネストしたクラスの値をリフレクションして取得する

Java単体でもリフレクションで値を取得できます。しかし、ネストされた値を取得することは非常に面倒です。

Springではもっと簡単に値を取得することができます。それは、Spring式言語(Spring Expression Language、以後 SpELと表現します)を使用した方法です。

今回の記事では、SpELを活用してネストしたクラスの値をリフレクションして取得することを目指します。

環境

  • Java
    • 15
  • Spring Boot
    • 2.4.5
  • Lombok

ゴール

  • 別のクラスからリフレクションを使って値を取得する

前提

  • Getterがprivateではないこと

ユースケース

  • ネストするクラスから目的の変数を取得したい

例えば、「メールに必要な文言を巨大なネストするクラスから取得したい。しかも、簡単に書き換えられるようにマスタで管理したい。」という要件があったとします。

この要件を満たすために、マスタではchild.grandChild.taxという文言だけを管理しておいて、その値を元にリフレクションをする。

といったケースに使えると思います。

クラス構成

この記事ではユースケースを意識するために、親子関係をもつクラスを使用します。

- Parent
  - Child
    - GrandChild
@Value
@Builder
public class Parent {
  private Child child;
  private List<Child> children;
}
@Value
@Builder
public class Child {
  private GrandChild grandChild;
  private List<GrandChild> grandChildren;
}
@Value
@Builder
public class GrandChild {
  private String tax;
  private Map<String, Integer> animals = 
    Map.of("dog", 1, "cat", 3, "mouse", 10);  
}

ネストしないクラスをリフレクションで取得する

まずはもっとも簡単な方法です。GrandChildクラスのtax変数を取得します。

リフレクション対象のインスタンスをまずは生成します。

その後、StandardEvaluationContextのコンストラクタにインスタンスを渡して生成します。

最後にSpELを解釈するためのExpressionParserを生成し、リフレクションします。

@Test
void test_01_01() throws Exception {
  // リフレクション対象インスタンス生成
  var target = GrandChild.builder()
          .tax(123)
          .build();

  // StandardEvaluationContextを生成する
  StandardEvaluationContext context = new StandardEvaluationContext(target);
  // ExpressionParserを生成する
  ExpressionParser expressionParser = new SpelExpressionParser();
  // インスタンスの変数名「tax」を指定して取得する
  Object tax = expressionParser.parseExpression("tax").getValue(context);

  assertThat(tax).isEqualTo(123);
}

ネストするクラスをリフレクションで取得する

Parentクラスの変数のChildクラスの変数のGrandChildクラスのtax変数を取得します。SpELとしてはchild.grandchild.taxで表現します。

ネストしていない場合と処理はまったく変わりません。

@Test
void test_01() throws Exception {
  Parent target = Parent.builder()
      .child(Child.builder()
          .grandChild(GrandChild.builder()
              .tax(123)
              .build())
          .build())
      .build();

  StandardEvaluationContext context = new StandardEvaluationContext(target);
  ExpressionParser expressionParser = new SpelExpressionParser();
  Object tax = expressionParser.parseExpression("child.grandChild.tax").getValue(context);

  assertThat(tax).isEqualTo(123);
}

リストやマップの項目をリフレクションで取得する

処理は同じですので、SpELのみ記載します。Java単体だとコレクション処理が面倒なので、非常に嬉しいですね。

リストの項目を取得する

配列の順番を指定して取得できます。

  • children[0].grandChildren[0].tax

マップの項目を取得する

マップのKeyを指定することで、対応するValueを取得することができます。

  • children[0].grandChildren[0].animals['dog']

SpEL式をもっと使いこなす

単純なリフレクションだけでなく、SpELを使用することができます。無理に使うケースは無いと考えていますが、一応覚えておきましょう。

次の例は、マップのサイズが1より大きい場合はtrueを返却するSpELです。

  • children[0].grandChildren[0].animals.size() > 1

SpELをもっと知りたい場合は、こちらのページが参考になりました。

www.baeldung.com

ソースコード

実装コード https://github.com/hirotoKirimaru/cucumber-sample/blob/63230957325a487f23c6800457f5565506e9c551/src/test/java/kirimaru/biz/domain/ReflectionTests.java#L26

終わりに

Springの機能を使用すると簡単にリフレクションをすることができます。リフレクション = SpELではないので、なかなか検索されないワードだと思いますが、便利ですのでぜひ使ってみてください。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考

baeldung: Spring Expression Language Guide www.baeldung.com

f:id:nainaistar:20210507182249p:plain