GetXXXプロパティとsetXXXプロパティのみを持つ非常に単純なJavaオブジェクトがあるとします。このオブジェクトは、値、基本的にはレコードまたはタイプセーフ(およびパフォーマンス)マップを処理するためにのみ使用されます。このオブジェクトをキー値のペア(文字列またはタイプセーフ)に変換するか、キー値のペアからこのオブジェクトに変換する必要があることがよくあります。
リフレクションまたはこの変換を行うためのコードを手動で作成する以外に、これを達成する最良の方法は何ですか?
たとえば、ObjectMessageタイプを使用せずにjms経由でこのオブジェクトを送信する(または受信メッセージを適切な種類のオブジェクトに変換する)場合があります。
常にApache commons beanutils がありますが、もちろん内部でリフレクションを使用します
多くの潜在的なソリューションがありますが、もう1つだけ追加しましょう。 Jackson (JSON processing lib)を使用して、「jsonなし」の変換を行います。例:
ObjectMapper m = new ObjectMapper();
Map<String,Object> props = m.convertValue(myBean, Map.class);
MyBean anotherBean = m.convertValue(props, MyBean.class);
( このブログエントリ にはいくつかの例があります)
基本的に互換性のある型を変換できます:互換性は、型からJSONに変換し、そのJSONから結果の型に変換した場合、エントリが一致することを意味します(適切に構成されていれば、認識されないものも無視できます)。
Maps、Lists、arrays、primitives、bean-like POJOなど、予想されるケースに適しています。
コード生成は、私が考えることができる他の唯一の方法です。個人的には、一般的に再利用可能なリフレクションソリューションがありました(コードのその部分が絶対にパフォーマンスクリティカルでない限り)。 JMSの使用は過剰に聞こえます(追加の依存関係であり、それが意図するものではありません)。また、ボンネットの下でも反射を使用する可能性があります。
これは、Javaオブジェクトをマップに変換するためのメソッドです
public static Map<String, Object> ConvertObjectToMap(Object obj) throws
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException {
Class<?> pomclass = obj.getClass();
pomclass = obj.getClass();
Method[] methods = obj.getClass().getMethods();
Map<String, Object> map = new HashMap<String, Object>();
for (Method m : methods) {
if (m.getName().startsWith("get") && !m.getName().startsWith("getClass")) {
Object value = (Object) m.invoke(obj);
map.put(m.getName().substring(3), (Object) value);
}
}
return map;
}
これはそれを呼び出す方法です
Test test = new Test()
Map<String, Object> map = ConvertObjectToMap(test);
Java 8を使用すると、これを試すことができます。
public Map<String, Object> toKeyValuePairs(Object instance) {
return Arrays.stream(Bean.class.getDeclaredMethods())
.collect(Collectors.toMap(
Method::getName,
m -> {
try {
Object result = m.invoke(instance);
return result != null ? result : "";
} catch (Exception e) {
return "";
}
}));
}
JSON は、たとえばXStream + Jettisonを使用して、キーと値のペアを持つ単純なテキスト形式です。例えば、他のプラットフォーム/言語とのJavaオブジェクト交換のためのApache ActiveMQ JMSメッセージブローカーによってサポートされています。
Springを使用する場合、Spring Integration object-to-map-transformerも使用できます。これだけの依存関係としてSpringを追加する価値はおそらくないでしょう。
ドキュメントについては、「Object-to-Map Transformer」を http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapterで検索してください。 html
基本的に、入力として与えられたオブジェクトから到達可能なオブジェクトグラフ全体を走査し、オブジェクト上のすべてのプリミティブタイプ/文字列フィールドからマップを生成します。次のいずれかを出力するように構成できます。
以下は彼らのページの例です:
public class Parent{
private Child child;
private String name;
// setters and getters are omitted
}
public class Child{
private String name;
private List<String> nickNames;
// setters and getters are omitted
}
出力は次のようになります。
{person.name =ジョージ、person.child.name = Jenna、person.child.nickNames [0] = Bimbo 。 。等}
逆トランスも利用できます。
juffrou-reflect のBeanWrapperを使用します。それは非常に高性能です。
Beanをマップに変換する方法は次のとおりです。
public static Map<String, Object> getBeanMap(Object bean) {
Map<String, Object> beanMap = new HashMap<String, Object>();
BeanWrapper beanWrapper = new BeanWrapper(BeanWrapperContext.create(bean.getClass()));
for(String propertyName : beanWrapper.getPropertyNames())
beanMap.put(propertyName, beanWrapper.getValue(propertyName));
return beanMap;
}
私は自分でJuffrouを開発しました。これはオープンソースなので、自由に使用して変更できます。また、ご質問がある場合は、喜んでお返事いたします。
乾杯
カルロス
単純にリフレクションとGroovyを使用する:
def Map toMap(object) {
return object?.properties.findAll{ (it.key != 'class') }.collectEntries {
it.value == null || it.value instanceof Serializable ? [it.key, it.value] : [it.key, toMap(it.value)]
}
}
def toObject(map, obj) {
map.each {
def field = obj.class.getDeclaredField(it.key)
if (it.value != null) {
if (field.getType().equals(it.value.class)){
obj."$it.key" = it.value
}else if (it.value instanceof Map){
def objectFieldValue = obj."$it.key"
def fieldValue = (objectFieldValue == null) ? field.getType().newInstance() : objectFieldValue
obj."$it.key" = toObject(it.value,fieldValue)
}
}
}
return obj;
}
各ゲッターとセッターへの呼び出しをハードコードしたくない場合、リフレクションがこれらのメソッドを呼び出す唯一の方法です(しかし難しくはありません)。
問題のクラスをリファクタリングして、Propertiesオブジェクトを使用して実際のデータを保持し、各getterおよびsetterが単にget/setを呼び出すようにすることはできますか?そうすれば、やりたいことに適した構造になります。キーと値の形式で保存およびロードする方法もあります。
もちろん、絶対に最も簡単な変換手段があります-変換はまったくありません!
クラスで定義されたプライベート変数を使用する代わりに、インスタンスの値を格納するHashMapのみをクラスに含めるようにします。
次に、ゲッターとセッターが値を返し、HashMapに値を設定します。マップに変換するときは、出来上がりです。 -それはすでに地図です。
少しのAOPウィザードを使用すると、個々のゲッターとセッターを実際に記述することなく、各値名に固有のゲッターとセッターを使用できるようにすることで、Beanに固有の柔軟性を維持することさえできます。
おそらくパーティーに遅れました。 Jacksonを使用して、それをPropertiesオブジェクトに変換できます。これは、ネストされたクラス、およびa.b.c = valueのキーが必要な場合に適しています。
JavaPropsMapper mapper = new JavaPropsMapper();
Properties properties = mapper.writeValueAsProperties(sct);
Map<Object, Object> map = properties;
サフィックスが必要な場合は
SerializationConfig config = mapper.getSerializationConfig()
.withRootName("suffix");
mapper.setConfig(config);
この依存関係を追加する必要があります
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-properties</artifactId>
</dependency>
Jodaフレームワークを使用できます。
jodaPropertiesを活用してください。ただし、これは特定の方法でBeanを作成し、特定のインターフェイスを実装することを規定しています。ただし、特定のクラスからリフレクションなしでプロパティマップを返すことができます。サンプルコードは次のとおりです。
http://pbin.oogly.co.uk/listings/viewlistingdetail/0e78eb6c76d071b4e22bbcac748c57
Java 8ストリームフィルターコレクタープロパティを使用できます。
public Map<String, Object> objectToMap(Object obj) {
return Arrays.stream(YourBean.class.getDeclaredMethods())
.filter(p -> !p.getName().startsWith("set"))
.filter(p -> !p.getName().startsWith("getClass"))
.filter(p -> !p.getName().startsWith("setClass"))
.collect(Collectors.toMap(
d -> d.getName().substring(3),
m -> {
try {
Object result = m.invoke(obj);
return result;
} catch (Exception e) {
return "";
}
}, (p1, p2) -> p1)
);
}
最善の解決策は、Dozerを使用することです。マッパーファイルには次のようなものが必要です。
<mapping map-id="myTestMapping">
<class-a>org.dozer.vo.map.SomeComplexType</class-a>
<class-b>Java.util.Map</class-b>
</mapping>
そして、それだけです、ドーザーは残りの世話をします!!!
私のJavaDude Bean注釈プロセッサは、これを行うためのコードを生成します。
http://javadude.googlecode.com
例えば:
@Bean(
createPropertyMap=true,
properties={
@Property(name="name"),
@Property(name="phone", bound=true),
@Property(name="friend", type=Person.class, kind=PropertyKind.LIST)
}
)
public class Person extends PersonGen {}
上記は、@ Beanを使用して定義されたすべてのプロパティのマップを生成するcreatePropertyMap()メソッドを含むスーパークラスPersonGenを生成します。
(次のバージョンのためにAPIを少し変更していることに注意してください-注釈属性はdefineCreatePropertyMap = trueになります)
ジャクソンライブラリの助けを借りて、String/integer/double型のすべてのクラスプロパティ、およびMapクラスのそれぞれの値を見つけることができました。 (リフレクションAPIを使用しない!)
TestClass testObject = new TestClass();
com.fasterxml.jackson.databind.ObjectMapper m = new com.fasterxml.jackson.databind.ObjectMapper();
Map<String,Object> props = m.convertValue(testObject, Map.class);
for(Map.Entry<String, Object> entry : props.entrySet()){
if(entry.getValue() instanceof String || entry.getValue() instanceof Integer || entry.getValue() instanceof Double){
System.out.println(entry.getKey() + "-->" + entry.getValue());
}
}
汎用変換サービスを作成する必要があります!ジェネリックを使用して、自由に入力できるようにします(したがって、すべてのオブジェクトをkey => valueに変換して戻すことができます)。
どのフィールドをキーにする必要がありますか? Beanからそのフィールドを取得し、値マップに他の非一時的な値を追加します。
戻る方法はとても簡単です。 key(x)を読み取り、最初にキーを書き込み、次にすべてのリストエントリを新しいオブジェクトに戻します。
Apache commons beanutils !を使用して、Beanのプロパティ名を取得できます。
もう1つの可能な方法はこちらです。
BeanWrapperは、プロパティ値の設定および取得(個別または一括)、プロパティ記述子の取得、およびプロパティのクエリを行って読み取り可能または書き込み可能かどうかを判断する機能を提供します。
Company c = new Company();
BeanWrapper bwComp = BeanWrapperImpl(c);
bwComp.setPropertyValue("name", "your Company");
本当にパフォーマンスが必要な場合は、コード生成ルートを使用できます。
独自のリフレクションを行い、mixin AspectJ ITDを構築することにより、これを自分で行うことができます。
または、Spring Rooを使用して Spring Roo Addon を作成できます。 Rooアドオンは上記と同様の処理を行いますが、Spring Rooを使用するすべてのユーザーが利用でき、ランタイムアノテーションを使用する必要はありません。
私は両方をしました。人々はSpring Rooをだますが、それは本当にJavaのための最も包括的なコード生成です。
キーがオブジェクトのルート要素から検査されているリーフまでの点線のパスの説明である、単純なオブジェクトツリーからキー値リストへのマッピングの場合、キー値リストへのツリー変換は、オブジェクトからxmlへのマッピング。 XMLドキュメント内の各要素には定義された位置があり、パスに変換できます。したがって、基本的な安定した変換ツールとして XStream を使用し、階層ドライバーとライターの部分を独自の実装に置き換えました。 XStreamには、基本的なパストラッキングメカニズムも付属しています。これは、他の2つと組み合わせて、厳密にタスクに適したソリューションを導きます。
Gsonを使用すると、
object
をJsonに変換Jsonをマップに変換
retMap = new Gson().fromJson(new Gson().toJson(object),
new TypeToken<HashMap<String, Object>>() {}.getType()
);