Pergunta

Eu estou usando JAXB para ler e XML gravação. O que eu quero é usar uma classe JAXB base para triagem e uma classe JAXB herdado para unmarshalling. Isto é para permitir um remetente aplicativo Java para XML de envio para outro aplicativo Java receptor. O emissor eo receptor irá compartilhar uma biblioteca JAXB comum. Eu quero que o receptor para desempacotar o XML em uma classe JAXB receptor específico que estende a classe JAXB genérico.

Exemplo:

Esta é a classe JAXB comum que é usado pelo remetente.

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

Esta é a classe receptor específico JAXB usado quando unmarshalling o XML. A classe receptor tem específica lógica para o aplicativo receptor.

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

Marshalling funciona como esperado. O problema é com unmarshalling, ainda unmarshals para Person apesar da JAXBContext usando o nome do pacote do ReceiverPerson subclasse.

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

O que eu quero é desempacotar para ReceiverPerson. A única maneira que eu fui capaz de fazer isso é remover @XmlRootElement de Person. Infelizmente fazendo isso Person impede de ser empacotado. É como se JAXB começa na classe base e trabalha o seu caminho até encontrar o primeiro @XmlRootElement com o nome apropriado. Eu tentei adicionar um método createPerson() que retorna ReceiverPerson para ObjectFactory mas isso não ajuda.

Foi útil?

Solução

Você está usando JAXB direito 2.0? (Desde JDK6)

Há uma classe:

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

que se pode subclasse e substituir seguintes métodos:

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

Exemplo:

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

O uso é feito por como a seguir:

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

Eu tenho certeza que, usando este conceito que você pode controlar o processo de triagem / unmarshalling por si mesmo (incluindo a escolha do correto [sub | Super] Tipo de construção).

Outras dicas

O seguinte fragmento é um método de teste de um JUnit4 com uma 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);
}

A parte importante é o uso do método JAXBContext.newInstance(Class... classesToBeBound) para o contexto unmarshalling:

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

Com esta chamada, JAXB irá computar um fechamento de referência sobre as classes especificadas e reconhecerá RecieverPerson. O teste passa. E se você alterar a ordem de parâmetros, você vai ter um java.lang.ClassCastException (para que eles deve ser passado nesta ordem).

subclasse Pessoa duas vezes, uma para o receptor e uma vez para o remetente, e só colocar o XmlRootElement nesses subclassses (deixando a superclasse, Person, sem um XmlRootElement). Note-se que o emissor eo receptor ambos compartilham as classes mesma 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
}

[testado e confirmado para trabalhar com JAXB]. Ele contorna o problema que você notar, quando várias classes na hierarquia de herança tem a anotação XmlRootElement.

Este é sem dúvida também uma mais puro e mais abordagem OO, porque separa o modelo de dados comum, por isso não é uma "solução alternativa" em tudo.

Criar um ObjectFactory personalizado para instanciar a classe desejada durante unmarshalling. Exemplo:

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

Eu não tenho certeza por que você iria querer fazer isso ... ele não parece tão seguro para mim.

Considere o que aconteceria em ReceiverPerson tem variáveis ??de instância adicionais ... então você iria acabar com (eu acho) essas variáveis ??sendo nula, 0, ou falso ... e que se nulo não é permitido ou o número deve ser maior que 0?

Eu acho que você provavelmente vai querer fazer é ler na pessoa e, em seguida, construir um novo ReceiverPerson de que (provavelmente fornecer um construtor que leva uma pessoa).

O ponto-chave é "quando unmarshalling, JAXB começa na classe base e trabalha o seu caminho", embora você especificar ReceiverPerson quando unmarshalling, você tem classe Person anotado com @XmlRootElement, por isso vai desempacotar para Person.

Então, você precisa remover @XmlRootElement em Person classe, mas fazendo isso Person impede de ser empacotado.

A solução é criar um DummyPerson que se estende Pessoa e anotá-lo com @XmlRootElement, isto faz DummyPerson e ReceiverPerson no mesmo nível , então você pode Marechal DummyPerson vez de Person e xmlString unmarshal para 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();
}

Referências:
suporta herança em JAXB

Uma vez que você realmente tem dois aplicativos separados, compilá-los com diferentes versões da classe "Person" - com o aplicativo receptor não ter @XmlRootElement(name="person") em Person. Não só isto é feio, mas derrota a manutenção você queria de utilizar a mesma definição de Pessoa para o remetente eo destinatário. Sua característica redentora é que ele funciona.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top