2009-03-06 6 views
39

Я использую JAXB для чтения и записи XML. Я хочу использовать базовый класс JAXB для сортировки и унаследованный класс JAXB для unmarshalling. Это позволяет Java-приложению отправителя отправлять XML в другое приложение Java-приемника. Отправитель и получатель будут иметь общую библиотеку JAXB. Я хочу, чтобы ретранслятор отменил XML-код на конкретный JAXB-приемник, который расширяет общий класс JAXB.Наследование JAXB, не связанное с подклассом маршалированного класса

Пример:

Это общий класс JAXB, который используется отправителем.

@XmlRootElement(name="person") 
public class Person { 
    public String name; 
    public int age; 
} 

Это особый класс JAXB приемника, используемый при разборке XML. Класс приемника имеет логику, специфичную для приложения-получателя.

@XmlRootElement(name="person") 
public class ReceiverPerson extends Person { 
    public doReceiverSpecificStuff() ... 
} 

Маршаллинг работает должным образом. Проблема заключается в том, что он unmarshalling, он по-прежнему не имеет отношения к Person, несмотря на JAXBContext, используя имя пакета подкласса ReceiverPerson.

JAXBContext jaxbContext = JAXBContext.newInstance(package name of ReceiverPerson); 

То, что я хочу, чтобы маршализацию к ReceiverPerson. Единственный способ, которым я смог это сделать, - удалить @XmlRootElement с Person. К сожалению, это предотвращает марксирование Person. Это похоже на то, что JAXB начинается с базового класса и работает до тех пор, пока не найдет первый @XmlRootElement с соответствующим именем. Я попытался добавить метод createPerson(), который возвращает ReceiverPerson в ObjectFactory, но это не поможет.

ответ

19

Вы используете JAXB 2.0 правильно? (С JDK6)

Существует класс:

javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType> 

которого можно создать подкласс и переопределить следующие методы:

public abstract BoundType unmarshal(ValueType v) throws Exception; 
public abstract ValueType marshal(BoundType v) throws Exception; 

Пример:

public class YourNiceAdapter 
     extends XmlAdapter<ReceiverPerson,Person>{ 

    @Override public Person unmarshal(ReceiverPerson v){ 
     return v; 
    } 
    @Override public ReceiverPerson marshal(Person v){ 
     return new ReceiverPerson(v); // you must provide such c-tor 
    } 
} 

Использование осуществляется при помощи следующим образом:

@Your_favorite_JAXB_Annotations_Go_Here 
class SomeClass{ 
    @XmlJavaTypeAdapter(YourNiceAdapter.class) 
    Person hello; // field to unmarshal 
} 

Я уверен, что с помощью этой концепции вы можете самостоятельно управлять процессом маршаллинга/развязывания (включая выбор правильного типа [sub | super] для построения).

+0

Я пробовал это, и он не работает. Независимо от того, что я пытаюсь, ObjectFactory или мой XmlAdapter никогда не вызываются. Из того, что я прочитал, JAXB от Sun решает статические ссылки на классы. Похоже, реализация Glassfish имеет больше шансов https://jaxb.dev.java.net/guide/Adding_behaviors.html –

+0

XmlJavaAdapters подходят для создания классов, не связанных с JAXB, которые можно использовать с JAXB. Поскольку Person и ReceiverPerson (и/или их надкласс, если они есть) имеют аннотации JAXB, это не работает. Вам нужен Person и ReceiverPerson без JAXB, плюс адаптеры для обоих. –

+0

Вы пробовали подкачки для marshall | unmarshall? [...] расширяет XmlAdapter [...] @Override public ReceiverPerson unmarshal (Person v) { return new ReceiverPerson (v); } @Override публичный маршал лица (ReceiverPerson v) { return v; } –

3

Я не уверен, почему вы хотели бы сделать это ... для меня это кажется мне небезопасным.

Рассмотрите, что произойдет в ReceiverPerson, есть дополнительные переменные экземпляра ... тогда вы закончите с (я думаю), что эти переменные имеют значение null, 0 или false ... и что, если null не разрешен или число должно быть больше 0?

Я думаю, что то, что вы, вероятно, хотите сделать, читается в Лице, а затем конструирует новый ReceiverPerson из этого (возможно, предоставляет конструктор, который принимает Person).

+0

У ReceiverPerson нет дополнительных переменных. Его цель - сделать конкретный материал приемника. –

+0

Вам по-прежнему придется на каком-то уровне создать его как другой класс ... и обычный способ сделать это через конструктор, который принимает тип, из которого вы хотите преобразовать. Кроме того, только потому, что это так, теперь не означает, что вы не добавите вары в будущем. – TofuBeer

+2

Yikes, это способ много размышлений о том, что класс может сделать в будущем. Если это достаточно хорошо для сегодняшнего дня, то это достаточно хорошо, и вы можете всегда реорганизовать позже ... –

-1

Поскольку у вас действительно есть два отдельных приложения, скомпилируйте их с разными версиями класса «Человек» - с приложением-получателем, не имеющим @XmlRootElement(name="person") по адресу Person. Мало того, что это некрасиво, но он побеждает техническую поддержку, которую вы хотели использовать из того же определения Person как для отправителя, так и для получателя. Его одна функция искушения заключается в том, что она работает.

+0

Я хочу, чтобы оба приложения делились базовыми классами JAXB. –

+0

@Steve: но мое второе («опрятное») решение действительно использует общие базовые классы JAXB ... похоже, что вы читаете только первый абзац, и мне было не ясно, что я предлагаю два решения. Я отредактирую. – 13ren

+0

Я разделил «опрятный путь» на новый ответ. – 13ren

17

Следующий фрагмент представляет собой метод испытания JUnit 4 с зеленым светом:

@Test 
public void testUnmarshallFromParentToChild() throws JAXBException { 
    Person person = new Person(); 
    int age = 30; 
    String name = "Foo"; 
    person.name = name; 
    person.age= age; 

    // Marshalling 
    JAXBContext context = JAXBContext.newInstance(person.getClass()); 
    Marshaller marshaller = context.createMarshaller(); 

    StringWriter writer = new StringWriter(); 
    marshaller.marshal(person, writer); 

    String outString = writer.toString(); 

    assertTrue(outString.contains("</person")); 

    // Unmarshalling 
    context = JAXBContext.newInstance(Person.class, RecieverPerson.class); 
    Unmarshaller unmarshaller = context.createUnmarshaller(); 
    StringReader reader = new StringReader(outString); 
    RecieverPerson reciever = (RecieverPerson)unmarshaller.unmarshal(reader); 

    assertEquals(name, reciever.name); 
    assertEquals(age, reciever.age); 
} 

Важной частью является использование метода JAXBContext.newInstance(Class... classesToBeBound) для немаршалинга контекста:

context = JAXBContext.newInstance(Person.class, RecieverPerson.class); 

С этот вызов, JAXB будет вычислять ссылочное замыкание на указанных классах и распознает RecieverPerson. Тест проходит. И если вы измените порядок параметров, вы получите java.lang.ClassCastException (чтобы они должны были пройти в этом порядке).

12

Подкласс Человек дважды, один раз для получателя и один раз для отправителя, и только помещаем XmlRootElement в эти подклассы (оставляя суперкласс, Person, без XmlRootElement). Обратите внимание, что отправитель и получатель совместно используют одни и те же базовые классы JAXB.

@XmlRootElement(name="person") 
public class ReceiverPerson extends Person { 
    // receiver specific code 
} 

@XmlRootElement(name="person") 
public class SenderPerson extends Person { 
    // sender specific code (if any) 
} 

// note: no @XmlRootElement here 
public class Person { 
    // data model + jaxb annotations here 
} 

[проверено и подтверждено для работы с JAXB]. Это обходит проблему, которую вы отмечаете, когда несколько классов в иерархии наследования имеют аннотацию XmlRootElement.

Это, возможно, также более аккуратный и более OO-подход, поскольку он отделяет общую модель данных, поэтому он не является «обходным путем».

+0

Это выглядит как лучшее решение, но не работает для меня, возможно потому, что мой случай немного сложный. Мне нужно преобразовать экземпляры класса класса «Group», который содержит оба списка SenderPerson и ReceiverPerson. что-то вроде этого @XmlRootElement группа открытого класса { public List отправители; открытый список приемников ReceiverPerson; } –

+0

Я могу подтвердить, что это решение работает и для меня. – icfantv

6

Создайте собственный объект ObjectFactory, чтобы создать экземпляр требуемого класса во время отмены маршрутизации. Пример:

JAXBContext context = JAXBContext.newInstance("com.whatever.mypackage"); 
Unmarshaller unmarshaller = context.createUnmarshaller(); 
unmarshaller.setProperty("com.sun.xml.internal.bind.ObjectFactory", new ReceiverPersonObjectFactory()); 
return unmarshaller; 

public class ReceiverPersonObjectFactory extends ObjectFactory { 
    public Person createPerson() { 
     return new ReceiverPerson(); 
    } 
} 
+0

Это кажется мне очень умным способом, так как требуется создать каждый объект только один раз - по сравнению с решением XmlAdpater, где вы сначала создаете экземпляр класса связанного типа, а затем копируете все атрибуты в экземпляр класса значений. Но вопрос в том: Работает ли это также с JREs, отличающимися от Sun/Oracle как OpenJDK? – Robert

+0

+1 для того, чтобы быть единственным решением для меня. Я пытаюсь использовать подстановку типов для подкласса (implClass) в JAXB 2. * в файле xsd, а классы без маршалирования генерируются, кроме тех, которые указаны в implClass. Ответ vocaro разрешает проблему! – Tatera

+0

Небольшое обновление для случая, когда JAXB является внешней реализацией (не из SDK): свойство называется '' com.sun.xml.bind.ObjectFactory''. –