Beanの全フィールドを取得したい時に試したこと



Bean内の全フィールドを取得するために試行錯誤した過程を備忘録として残します。
#やりたいこと
Bean内の全フィールドをIteratorで取得したい。
以下のBeanであれば**「Field1、Field2、Field3、Field4」**が取得できること。

```java:Fields.java
public class Fields {
  
  // フィールド
  private String Field1;
  private String Field2;
  private String Field3;
  private String Field4;
  // コンストラクタ
  Fields(String Field1, String Field2, String Field3, String Field4) {
    this.Field1 = Field1;
    this.Field2 = Field2;
    this.Field3 = Field3;
    this.Field4 = Field4;
  }
  // settet,getterは省略
  // .
  // .
  // .
}
```

#やってみたこと 1
###Javaの外部ライブラリである**「Apache Commons」**の**PropertyUtils/BeanUtilsクラス**を利用して取得

**PropertyUtils**及び**BeanUtils**は**JavaBeansを扱うクラス**です。[^1]
その中でもbean内の各値をMapに変換して返却する`describe(Object bean)`メソッドを利用して、
**BeanをMapに変換　→　変換したMapのkey値を取得（フィールド名）　**
といった流れでBean内の全フィールドを取得してみることにしました。

[^1]: BeanUtilsの使い方に関して参考になるサイトはこちら。[Java BeanUtilsメモ(Hishidama's commons-BeanUtils Memo)](https://www.ne.jp/asahi/hishidama/home/tech/apache/commons/beanutil.html)

####メソッド

```java:Fields.java(PropertyUtilsを利用)
 // PropertyUtils#describeを用いて全フィールドを取得
 public Iterator<?> getNames() 
		  throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
    try{
        // BeanをMapに変換　→　変換したMapのkey値を取得（フィールド名）
        return PropertyUtils.describe(this).keySet().iterator();
    }catch(IllegalAccessException e){
    	throw e;
    }catch(InvocationTargetException e){
    	throw e;
    }catch(NoSuchMethodException e){
    	throw e;
    }
  }
```

```java:Fields.java(BeanUtilsを利用)
  // BeanUtilsUtils#describeを用いて全フィールドを取得
  public Iterator<?> getNames1() 
		  throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
	    try{
            // BeanをMapに変換　→　変換したMapのkey値を取得（フィールド名）
	        return BeanUtils.describe(this).keySet().iterator();
	    }catch(IllegalAccessException e){
	    	throw e;
	    }catch(InvocationTargetException e){
	    	throw e;
	    }catch(NoSuchMethodException e){
	    	throw e;
	    }
	  }
```

####実行クラス

```java:FieldGet.java
package test;

import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;

public class FieldGet {
  public static void main (String args[]){

    Fields field = new Fields("フィールド1","フィールド２","フィールド３","フィールド４");
    Iterator<?> propertyUtils;
    Iterator<?> beanUtils;
	try {
		// PropertyUtils#describeを用いて全フィールドを取得
		propertyUtils = field.getNames();
		// BeanUtilsUtils#describeを用いて全フィールドを取得
		beanUtils = field.getNames1();
		System.out.println("--PropertyUtils利用--");
	    while(propertyUtils.hasNext()){
	        System.out.println(propertyUtils.next());
	    }
		System.out.println("--BeanUtils利用--");
	    while(beanUtils.hasNext()){
	        System.out.println(beanUtils.next());
	    }
	} catch (IllegalAccessException e) {
		e.printStackTrace();
	} catch (InvocationTargetException e) {
		e.printStackTrace();
	} catch (NoSuchMethodException e) {
		e.printStackTrace();
    }
  }
}
```

####実行結果

```:実行結果
--PropertyUtils利用--
Field1
Field3
class
Field2
Field4
--BeanUtils利用--
Field1
Field3
class
Field2
Field4
```

Beanのフィールド名を取得することが出来ましたが、
フィールド名ではない「class」が取得されたり、順番が昇順でないなど、改善の余地がありそうです。

また、何度か実行しているうちに・・。
`PropertyUtils/BeanUtils.describe(this)`の箇所で`InvocationTargetException`が発生してしまい、フィールドが取得できなくなってしまいました。
詳細な原因がわからないため、別の方法でフィールド名を取得を検討することにしました。

```:実行結果(一部)
Caused by: java.lang.reflect.InvocationTargetException
	... 1024 more
Caused by: java.lang.StackOverflowError
```

#やってみたこと 2
###リフレクションを利用

Beanのフィールドに関しては**リフレクションAPIでも取得が可能**です。[^2]
`getDeclaredFields()`を用いると、**すべてのアクセス修飾子(public、protected、デフォルト、private)のフィールドがFieldクラスの配列で取得可能**です。

[^2]: [JavaのリフレクションAPIでフィールド取得](http://pppurple.hatenablog.com/entry/2016/08/01/225120)

####メソッド

```java:Fields.java(リフレクションを利用)
  // Class#getDeclaredFieldsを用いて全フィールドを取得
  public Iterator<?> getNames2(){
        // すべてのアクセス修飾子のフィールドを取得
    	Field[] tmpField = this.getClass().getDeclaredFields();
        // Iteratorで返却するためのリストを生成
    	List<String> Field = new ArrayList<String>();
    	for(int i = 0; i < tmpField.length; i++) {
            // getName()でフィールドを取得
    		Field.add(tmpField[i].getName());
    	}
    	return Field.iterator();
    }
```

####実行クラス

```java:FieldGet.java
package test;

import java.util.Iterator;

public class FieldGet {
  public static void main (String args[]){

    Fields field = new Fields("フィールド1","フィールド２","フィールド３","フィールド４");
    terator<?> reflection;
    // Class#getDeclaredFieldsを用いて全フィールドを取得
    reflection = field.getNames2();
    System.out.println("--reflection利用--");
    while(propertyUtils.hasNext()){
        System.out.println(propertyUtils.next());
    }
  }
}
```

####実行結果

```:実行結果
--reflection利用--
Field1
Field2
Field3
Field4
```

これで想定どおり、全フィールドを取得することができました。

#番外編 
###StrutsのDynaActionFormで全フィールドを取得

StrutsのDynaActionFormでも、全フィールドを取得することが可能です。
**✳︎考え方としては「やってみたこと 1」と同様**

手順１：DynaValidatorFormまたはDynaActionFormを継承したクラスを作成
手順２：「やってみたこと 1」と同様の考え方**（フォームをMapに変換　→　変換したMapのkey値を取得（フィールド名））**で、メソッドを実装
　
StrutsのDynaActionFormには、**DynaActionFormで定義したプロパティをMap型に変換して返却**する`getMap()`メソッドが存在する為、そのメソッドを利用すると「やって見たこと 1」と同様の考え方で、全フィールドを取得することが可能です。
**PropertyUtils/BeanUtilsクラスのdescribe(Object bean)メソッドに相当する**といったところでしょうか。
内部の詳細な処理についてはよくわかりませんが・・。


```java:StrutsDynaValidatorForm.java
package test;

import java.util.Iterator;
import org.apache.struts.validator.DynaValidatorForm;

public class StrutsDynaValidatorForm extends DynaValidatorForm {
	public Iterator getNames() {		
		return this.getMap().keySet().iterator();		
	}
}
```