2009-03-06 41 views
39

Estoy usando JAXB para leer y escribir en XML. Lo que quiero es usar una clase JAXB base para clasificación y una clase JAXB heredada para desasignación. Esto es para permitir que una aplicación Java del remitente envíe XML a otra aplicación Java receptora. El emisor y el receptor compartirán una biblioteca JAXB común. Quiero que el receptor desmarque el XML en una clase JAXB específica del receptor que amplíe la clase JAXB genérica.Herencia de JAXB, unmarshal a la subclase de la clase marshaled

Ejemplo:

Esta es la clase JAXB común que se utiliza por 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 utiliza al desmaterializar el XML. La clase de receptor tiene 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 con la desasignación, todavía no coincide con Person a pesar de que JAXBContext usa el nombre del paquete de la subclase ReceiverPerson.

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

Lo que yo quiero es Resolver referencia a ReceiverPerson. La única forma que he podido hacer esto es eliminar @XmlRootElement de Person. Desafortunadamente, esto impide que se ordene el Person. Es como si JAXB comienza en la clase base y sigue su camino hasta que encuentra el primer @XmlRootElement con el nombre apropiado. He intentado agregar un método createPerson() que devuelve ReceiverPerson a ObjectFactory pero eso no ayuda.

Respuesta

19

Estás utilizando JAXB 2.0 ¿no? (Desde JDK6)

Hay una clase:

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

que uno puede subclase, y anular 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 
    } 
} 

uso es hecho por 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 marshalling/unmarshalling usted mismo (incluida la elección del tipo correcto [sub | super] para construir).

+0

Intenté esto y no funciona. No importa lo que intente, ObjectFactory o mi XmlAdapter nunca se llama. Por lo que he leído, JAXB de Sun se resuelve a través de referencias de clases estáticas. Parece que la implementación de Glassfish es más prometedora https://jaxb.dev.java.net/guide/Adding_behaviors.html –

+0

Los XmlJavaAdapters son adecuados para hacer que clases con anotaciones JAXB no se puedan usar con JAXB. Debido a que Person y ReceiverPerson (y/o su superclase, si lo hay) tienen anotaciones JAXB, no funciona. Necesita Persona y ReceiverPerson sin JAXB, además de adaptadores para ambos. –

+0

¿Has probado el intercambio de tipos para marshall | unmarshall? [...] extiende XmlAdapter [...] @Override public ReceiverPerson unmarshal (Person v) { return new ReceiverPerson (v); } @Override public Person mariscal (ReceiverPerson v) { return v; } –

3

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

Considere lo que sucedería en ReceiverPerson tiene variables de instancia adicionales ... entonces terminaría con (supongo) que las variables sean nulas, 0 o falsas ... y que si nulo no está permitido o el número debe ser mayor que 0?

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

+0

ReceiverPerson no tiene variables adicionales. Su propósito es hacer cosas específicas del receptor. –

+0

Todavía tendrá que, en algún nivel, crearlo como una clase diferente ... y la forma normal de hacerlo es a través de un constructor que toma el tipo que desea convertir. Además, solo porque ese sea el caso ahora no significa que no agregará vars en el futuro. – TofuBeer

+2

Sí, eso es una gran especulación sobre lo que una clase podría hacer en el futuro. Si es lo suficientemente bueno para hoy, entonces es lo suficientemente bueno, y siempre puedes refactorizar más tarde ... –

-1

Como realmente tiene dos aplicaciones separadas, compílelas con diferentes versiones de la clase "Persona" - con la aplicación del receptor sin @XmlRootElement(name="person") en Person. Esto no solo es feo, sino que también anula la capacidad de mantenimiento que usted desea utilizando la misma definición de Persona para el emisor y el receptor. Su única característica redentora es que funciona.

+0

Quiero que ambas aplicaciones compartan las clases base JAXB comunes. –

+0

@Steve: pero mi segunda solución ("forma ordenada") comparte clases JAXB base comunes ... parece que solo leíste el primer párrafo, y no estaba claro si estaba dando dos soluciones. Voy a editar – 13ren

+0

Separé "manera ordenada" en una nueva respuesta. – 13ren

17

El siguiente fragmento es un método de una prueba de 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 unmarshalling:

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 debe pasar en este orden).

12

Subclase Persona dos veces, una para el receptor y otra para el remitente, y solo coloca el XmlRootElement en estas subclases (dejando la superclase, Person, sin un XmlRootElement). Tenga en cuenta que tanto el emisor como el receptor comparten las mismas clases base de 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 este es un enfoque más ordenado y más OO, ya que separa el modelo de datos común, por lo que no es una "solución" en absoluto.

+0

Parece la mejor solución, pero no funciona para mí, tal vez porque mi caso es un poco complicado. Necesito convertir instancias de clase de 'Grupo' que contengan ambas listas de SenderPerson y ReceiverPerson. algo así @XmlRootElement public class Grupo { public list remitentes; public List receptores; } –

+0

Puedo confirmar que esta solución también me funciona. – icfantv

6

Cree una ObjectFactory personalizada para crear una instancia de la clase deseada durante el desemparejamiento. 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(); 
    } 
} 
+0

Eso me parece una manera muy inteligente, ya que requiere crear cada objeto solo una vez, en comparación con la solución XmlAdpater donde primero se crea la instancia de la clase de tipo enlazado y luego se copian todos los atributos a la instancia de la clase de valor. Pero la pregunta es: ¿Funciona esto también con JRE diferentes a Sun/Oracle como OpenJDK? – Robert

+0

+1 por ser la única solución para mí. Estoy tratando de usar la sustitución de tipo para subclases (implClass) en JAXB 2. * en un archivo xsd y las clases no asignadas son generadas distintas a la especificada en implClass. ¡La respuesta de vocaro resuelve el problema! – Tatera

+0

Actualización menor para el caso en que JAXB es una implementación externa (no una de SDK): la propiedad se llama '" com.sun.xml.bind.ObjectFactory "'. –

Cuestiones relacionadas