herança JAXB, desempacotar a subclasse da classe empacotada
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.
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.