2011-11-22 26 views
5

Estoy usando etiquetas @XmlID y @XmlIDREF para hacer referencia a un objeto de otro. Funcionó bien en Java 6 incluso con clases heredadas. El código de ejemplo que he creado se ve así. clase base etiquetas usadas:XmlID/XmlIDREF y herencia en la última versión de JAXB (Java 7)

@XmlRootElement 
@XmlAccessorType(FIELD) 
public class Module { 
    Module() {} 

    @XmlIDREF 
    private Module other; 

    @XmlID 
    private String id; 

    public Module(String id, Module other) { 
     this.id = id; 
     this.other = other; 
    } 
} 

clase heredada:

contenedores para estas clases:

@XmlRootElement 
public class Script { 
    Script() {} 
    public Script(Collection<Module> modules) { 
     this.modules = modules; 
    } 

    @XmlElementWrapper 
    @XmlElementRef 
    Collection<Module> modules = new ArrayList<Module>(); 
} 

Cuando se ejecuta este código de ejemplo:

public class JaxbTest { 

    private Script createScript() { 
     Module m1 = new Module("Module1", null); 
     Module m2 = new TheModule("Module2", m1, "featured module"); 
     Module m3 = new Module("Module3", m2); 
     return new Script(Arrays.asList(m1, m2, m3)); 
    } 

    private String marshal(Script script) throws Exception { 
     JAXBContext context = JAXBContext.newInstance(Module.class, Script.class, TheModule.class); 
     Writer writer = new StringWriter(); 
     context.createMarshaller().marshal(script, writer); 
     return writer.toString(); 
    } 

    private void runTest() throws Exception { 
     Script script = createScript(); 

     System.out.println(marshal(script)); 
    } 

    public static void main(String[] args) throws Exception { 
     new JaxbTest().runTest(); 
    } 
} 

recibo XML, en Java 6:

<script> 
    <modules> 
    <module> 
     <id>Module1</id> 
    </module> 
    <theModule> 
     <other>Module1</other> 
     <id>Module2</id> 
     <feature>featured module</feature> 
    </theModule> 
    <module> 
     <other>Module2</other> 
     <id>Module3</id> 
    </module> 
    </modules> 
</script> 

Nota que la referencia a los m2 (ejemplo TheModule) es de serie como se esperaba. Pero cuando el mismo código se ejecuta en Java 7 (JAXB 2.2.4-1) Recibo:

<script> 
    <modules> 
    <module> 
     <id>Module1</id> 
    </module> 
    <theModule> 
     <other>Module1</other> 
     <id>Module2</id> 
     <feature>featured module</feature> 
    </theModule> 
    <module> 
     <other xsi:type="theModule" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
     <other>Module1</other> 
     <id>Module2</id> 
     <feature>featured module</feature> 
     </other> 
     <id>Module3</id> 
    </module> 
    </modules> 
</script> 

Así se puede ver que en el último JAXB @XmlIDREF para el módulo heredado no está funcionando!

Respuesta

2

Esta respuesta es mal. En lugar de confiar en él, lea la documentación para JAXBContext.newInstance(...), consulte this answer y los comentarios a continuación.


Creo que confundes JAXB con la siguiente línea.

JAXBContext.newInstance(Module.class, Script.class, TheModule.class); 

que usted le indique que desea realizar una serie de los tipos XML Script, Module y TheModule. JAXB tratará los objetos de este último tipo de una manera especial , porque ya ha suministrado su clase base: le agrega un atributo discriminatorio. De esta forma, estos dos tipos se pueden diferenciar en el XML serializado.

Try suministrar solamente Script y Module, la clase base para todos los módulos.

JAXBContext.newInstance(Module.class, Script.class); 

De hecho, se puede omitir por completo Module. JAXB deducirá tipos en el objeto Script que intente serializar a partir del contexto.

Este comportamiento por cierto no es exactamente relacionado con Java 6. Está relacionado con la implementación de JAXB en uso (está bien, está bien, casi lo mismo, lo sé). En mis proyectos, utilizo JAXB 2.2.4-1, que reproduce el problema en Java 6 y 7 también.

Ah, y una cosa más: en lugar de crear un StringWriter ya organizar los objetos en que, a continuación, enviar su contenido a System.out puede utilizar el siguiente formato XML para enviar a stdout.

Marshaller marshaller = context.createMarshaller(); 
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); 
marshaller.marshal(script, System.out); 

Tal vez esto pueda facilitar (un poquito) más pruebas.

+0

Gracias por su respuesta. Tengo algunas dudas. Javadoc para JAXBContext.newInstance (Class ...) establece que quien llama debe listar las clases de nivel superior. "Nuevo contexto reconocerá todas las clases especificadas, pero también reconocerá cualquier clase que están directamente/indirectamente hace referencia ** ** estáticamente (sic!) De las clases especificadas. Las subclases de clases referenciadas ni clases @XmlTransient hace referencia no están registrados JAXBContext. ". Si aplicamos esta afirmación con el ejemplo de Konstantin, TheModule clase descendiente no será descubierto automático. \ –

+0

@AndyMalakov Parece que tienes razón. No recuerdo exactamente si he leído la documentación de 'newInstance' en aquel momento cuando respondí la pregunta, pero recuerdo que probé un código y que lo que he escrito parecía verificar. Como el OP no respondió a mi respuesta de ninguna manera, me olvidé por completo de esto. De hecho, puedo ver que he malinterpretado la pregunta/problema real, por lo que mi respuesta es totalmente inactiva. Actualizaré mi respuesta para reflejar esto. ¡Gracias por el aporte! –

0

Esto parece ser un error conocido en JAXB. Tuve el mismo problema y lo resolví al no usar XmlIDRef, sino usar postDeserialize con una ID personalizada.

Aquí está un informe de error:

http://java.net/jira/browse/JAXB-870

que no podía usar la sugerencia de Kohányi Robert. ¿Eso funcionó para ti?

0

Si todavía está atascado con Java7 en 2017, entonces no hay una manera fácil de evitar esto. El problema es que al serializar con un XmlREFId, Jaxb no puede hacer frente a las subclases. Una solución es utilizar un adaptador y devolver una nueva instancia de la clase base con el mismo ID que el que está siendo serializado (se utilizará únicamente la identificación, por lo que no tiene que molestarse con los otros campos):

public class ModuleXmlAdapter extends XmlAdapter<Module, Module> { 
    @Override 
    public Module unmarshal(Module v) { 
     // there is no problem with deserializing - return as is 
     return v; 
    } 

    @Override 
    public Module marshal(Module v) { 
     if (v == null) { 
      return null; 
     } 
     // here is the trick: 
     return new Module(v.getId(), null); 
    } 
} 

a continuación, sólo tiene que utilizar ese adaptador siempre que sea necesario:

public class Module { 
    @XmlIDREF 
    @XmlJavaTypeAdapter(ModuleXmlAdapter.class) 
    private Module other; 

    //... 
} 
Cuestiones relacionadas