13

私たちのアプリケーションにはかなり一般的なパターンがあります。Xmlでオブジェクトのセット(またはリスト)を構成します。これらはすべて共通のインターフェースを実装します。起動時に、アプリケーションはXmlを読み取り、JAXBを使用してオブジェクトのリストを作成/構成します。JAXBのみを使用してこれを行うための「正しい方法」を(さまざまな投稿を何度も読んだ後)理解したことはありません。

たとえば、インターフェイスFeeと、いくつかの共通のプロパティ、いくつかの異なるプロパティ、および非常に異なる動作を持つ複数の具体的な実装クラスがあります。アプリケーションが使用する料金のリストを構成するために使用するXmlは次のとおりです。

<fees>
   <fee type="Commission" name="commission" rate="0.000125" />
   <fee type="FINRAPerShare" name="FINRA" rate="0.000119" />
   <fee type="SEC" name="SEC" rate="0.0000224" />
   <fee type="Route" name="ROUTES">
       <routes>
        <route>
            <name>NYSE</name>
            <rates>
                <billing code="2" rate="-.0014" normalized="A" />
                <billing code="1" rate=".0029" normalized="R" />
            </rates>
        </route>        
        </routes>
          ...
    </fee>
  </fees>

上記のXmlでは、各<fee>要素はFeeインターフェースの具体的なサブクラスに対応しています。このtype属性は、インスタンス化するタイプに関する情報を提供し、インスタンス化されると、JAXBアンマーシャリングは残りのXmlのプロパティを適用します。

私はいつもこのようなことをすることに頼らなければなりません:

private void addFees(TradeFeeCalculator calculator) throws Exception {
    NodeList feeElements = configDocument.getElementsByTagName("fee");
    for (int i = 0; i < feeElements.getLength(); i++) {
        Element feeElement = (Element) feeElements.item(i);
        TradeFee fee = createFee(feeElement);
        calculator.add(fee);
    }
}

private TradeFee createFee(Element feeElement) {
    try {
        String type = feeElement.getAttribute("type");
        LOG.info("createFee(): creating TradeFee for type=" + type);
        Class<?> clazz = getClassFromType(type);
        TradeFee fee = (TradeFee) JAXBConfigurator.createAndConfigure(clazz, feeElement);
        return fee;
    } catch (Exception e) {
        throw new RuntimeException("Trade Fees are misconfigured, xml which caused this=" + XmlUtils.toString(feeElement), e);
    }
}

上記のコードでは、JAXBConfiguratorは、アンマーシャリングのためのJAXBオブジェクトの単純なラッパーです。

public static Object createAndConfigure(Class<?> clazz, Node startNode) {
    try {
        JAXBContext context = JAXBContext.newInstance(clazz);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        @SuppressWarnings("rawtypes")
        JAXBElement configElement = unmarshaller.unmarshal(startNode, clazz);
        return configElement.getValue();
    } catch (JAXBException e) {
        throw new RuntimeException(e);
    }
}

最後に、上記のコードの最後に、Xmlで構成されたタイプを含むリストが表示されます。

上記のように要素を反復するコードを記述せずに、JAXBにこれを自動的に実行させる方法はありますか?

4

3 に答える 3

5

注: 私はEclipseLink JAXB(MOXy)のリーダーであり、JAXB(JSR-222)エキスパートグループのメンバーです。

JAXBプロバイダーとしてMOXyを使用している場合は、MOXyの@XmlPathsアノテーションを使用して、標準のJAXB@XmlElementsアノテーションを拡張して次のことを行うことができます。

料金

import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;

@XmlRootElement
public class Fees {

    @XmlElements({
        @XmlElement(type=Commission.class),
        @XmlElement(type=FINRAPerShare.class),
        @XmlElement(type=SEC.class),
        @XmlElement(type=Route.class)
    })
    @XmlPaths({
        @XmlPath("fee[@type='Commission']"),
        @XmlPath("fee[@type='FINRAPerShare']"),
        @XmlPath("fee[@type='SEC']"),
        @XmlPath("fee[@type='Route']")
    })
    private List<Fee> fees;

}

手数料

インターフェイスの実装にはFee通常の注釈が付けられます。

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
public class Commission implements Fee {

    @XmlAttribute
    private String name;

    @XmlAttribute
    private String rate;

}

詳細については

于 2013-02-26T00:38:59.257 に答える
4

XmlAdapterこのユースケースにはを使用できます。impl bleowはCommissionタイプのみを処理しますが、すべてのタイプをサポートするように簡単に拡張できます。AdaptedFeeインターフェイスのすべての実装からの結合されたプロパティが含まれていることを確認する必要がありFeeます。

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class FeeAdapter extends XmlAdapter<FeeAdapter.AdaptedFee, Fee>{

    public static class AdaptedFee {

        @XmlAttribute
        public String type;

        @XmlAttribute
        public String name;

        @XmlAttribute
        public String rate;

    }

    @Override
    public AdaptedFee marshal(Fee fee) throws Exception {
        AdaptedFee adaptedFee = new AdaptedFee();
        if(fee instanceof Commission) {
            Commission commission = (Commission) fee;
            adaptedFee.type = "Commission";
            adaptedFee.name = commission.name;
            adaptedFee.rate = commission.rate;
        }
        return adaptedFee;
    }

    @Override
    public Fee unmarshal(AdaptedFee adaptedFee) throws Exception {
        if("Commission".equals(adaptedFee.type)) {
            Commission commission = new Commission();
            commission.name = adaptedFee.name;
            commission.rate = adaptedFee.rate;
            return commission;
        }
        return null;
    }

}

アノテーションXmlAdapterを使用して構成されます。@XmlJavaTypeAdapter

import java.util.List;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Fees {

    @XmlElement(name="fee")
    @XmlJavaTypeAdapter(FeeAdapter.class)
    private List<Fee> fees;

}

詳細については

于 2013-02-26T00:57:56.517 に答える
0

すべての要素に名前が付けられている場合、これは不可能だと思います<fee>。たとえそうであったとしても(またはそうであるとしても)、メンテナンスの観点からは非常に混乱します。

タイプに基づいて(たとえば<tradeFee>ではなく<fee>)さまざまな料金要素の名前を変更する機能はありますか?

BaseFeeそれ以外の場合は、可能なすべてのタイプのすべてのフィールドを持つクラスを作成できます<fee>。データをオブジェクトのリストにマーシャリング解除しBaseFee、実行時にそれらをより具体的なタイプに変換できます。

List<BaseFee> fees = ...;
for (BaseFee fee : fees) {
    if (isTradeFee(fee)) {
        TradeFee tradeFee = toTradeFee(fee);
        // do something with trade fee...
    }
}

ちょっとしたハックですが、要件を考えると、それでうまくいくはずです。

于 2013-02-26T00:07:33.697 に答える