Pregunta

Estoy usando JAXB para leer y escribir XML. Lo que quiero es usar una clase JAXB base para el cálculo de referencias y una clase JAXB heredada para no demoler. Esto es para permitir que una aplicación Java del remitente envíe XML a otra aplicación Java del receptor. El remitente y el destinatario compartirán una biblioteca JAXB común. Quiero que el receptor descomprima el XML en una clase JAXB específica del receptor que amplíe la clase JAXB genérica.

Ejemplo:

Esta es la clase JAXB común que utiliza el remitente.

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

Esta es la clase JAXB específica del receptor que se usa cuando se desmarca el XML. La clase del receptor tiene una lógica específica para la aplicación del receptor.

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

Marshalling funciona como se esperaba. El problema es que no tiene nada de grave, aún así no es severo para Person a pesar de que JAXBContext utiliza el nombre del paquete del ReceiverPerson subclasificado.

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

Lo que quiero es desmarcar a ReceiverPerson . La única forma en que he podido hacer esto es eliminar @XmlRootElement de Person . Desafortunadamente, al hacer esto se evita que se calcule a Person . Es como si JAXB comenzara en la clase base y se abriera camino hasta encontrar el primer @XmlRootElement con el nombre apropiado. He intentado agregar un método createPerson () que devuelve ReceiverPerson a ObjectFactory pero eso no ayuda.

¿Fue útil?

Solución

Estás usando JAXB 2.0, ¿verdad? (desde JDK6)

Hay una clase:

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

cuál puede subclasificar y anular los siguientes métodos:

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

Ejemplo:

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
    }
}

El uso se realiza de la siguiente manera:

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

Estoy bastante seguro de que, al usar este concepto, usted puede controlar el proceso de ordenación / no demolición por usted mismo (incluida la elección del tipo [sub | super] correcto para construir).

Otros consejos

El siguiente fragmento de código es un método de una prueba Junit 4 con una luz verde:

@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);
}

La parte importante es el uso del método JAXBContext.newInstance (Class ... classesToBeBound) para el contexto no riguroso:

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

Con esta llamada, JAXB calculará un cierre de referencia en las clases especificadas y reconocerá RecieverPerson . La prueba pasa. Y si cambia el orden de los parámetros, obtendrá un java.lang.ClassCastException (por lo que deben pasar en este orden).

Subclase Persona dos veces, una para el receptor y una para el remitente, y solo coloque el XmlRootElement en estas subclases (dejando la superclase, Person , sin un XmlRootElement). Tenga en cuenta que tanto el remitente como el receptor comparten las mismas clases base 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
}

[probado y confirmado para trabajar con JAXB]. Evita el problema que observa, cuando varias clases en la jerarquía de herencia tienen la anotación XmlRootElement.

Podría decirse que también es un enfoque más ordenado y más OO, porque separa el modelo de datos común, por lo que no es una " solución alternativa " en absoluto.

Cree un ObjectFactory personalizado para crear una instancia de la clase deseada durante la desalificación. Ejemplo:

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();
    }
}

No estoy seguro de por qué querrías hacer esto ... no me parece tan seguro.

Considere que lo que sucedería en ReceiverPerson tiene variables de instancia adicionales ... entonces usted terminaría con (supongo) que esas variables son nulas, 0 o falsas ... y qué sucede si no se permite nulo o el número debe ser mayor que 0?

Creo que lo que probablemente quieras hacer es leer en la Persona y luego construir un nuevo ReceiverPerson a partir de eso (probablemente proporcionar un constructor que tome una Persona).

El punto clave es " cuando se desmarca, JAXB comienza en la clase base y se abre camino " ;, aunque usted especifica ReceiverPerson cuando desmarca, tiene la clase Persona anotado con @XmlRootElement , por lo que se eliminará a Person .

Por lo tanto, debe eliminar @XmlRootElement en la clase Person , pero al hacer esto se evita que se marque a Person .

La solución es crear un DummyPerson que amplíe a Person y anotarlo con @XmlRootElement , esto hace que DummyPerson y ReceiverPerson en el mismo nivel , entonces puede colocar DummyPerson en lugar de Person y unmarshal xmlString a ReceiverPerson .

@XmlAccessorType(XmlAccessType.FIELD)
public class Person {
    public String name;
    public int age;
}

@XmlRootElement(name = "person")
public class DummyPerson extends Person {
}

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

Referencias:
Asistencia de herencia en JAXB

Ya que realmente tienes dos aplicaciones separadas, compílalas con diferentes versiones de la clase " Persona " - con la aplicación del receptor que no tiene @XmlRootElement (name = " person ") en Person . Esto no solo es feo, sino que elimina la capacidad de mantenimiento que deseaba al usar la misma definición de Persona tanto para el remitente como para el destinatario. Su única característica que redime es que funciona.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top