tipos - this en java
¿Por qué JAXB necesita un constructor sin argumentos para el cálculo? (4)
Si intenta calcular una clase que hace referencia a un tipo complejo que no tiene un constructor sin argumentos, como por ejemplo:
import java.sql.Date;
@XmlRootElement(name = "Foo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
int i;
Date d; //java.sql.Date does not have a no-arg constructor
}
con la implementación JAXB que es parte de Java, como sigue:
Foo foo = new Foo();
JAXBContext jc = JAXBContext.newInstance(Foo.class);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(foo, baos);
JAXB lanzará un
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions java.sql.Date does not have a no-arg default constructor
Ahora, comprendo por qué JAXB necesita un constructor sin argumentos para que no sea riguroso, porque necesita instanciar el objeto. Pero, ¿por qué JAXB necesita un constructor sin argumentos mientras se prepara?
Además, otro problema, ¿por qué la implementación de Java JAXB lanza una excepción si el campo es nulo y no va a ser ordenado de todos modos?
¿Me estoy perdiendo algo o son simplemente malas opciones de implementación en la implementación de Java JAXB?
Algunos marcos de empresa y de inyección de dependencias utilizan la Class.newInstance() reflexión Class.newInstance() para crear una nueva instancia de sus clases. Este método requiere un constructor público sin argumentos para poder crear una instancia del objeto.
Cuando una implementación de JAXB (JSR-222) inicializa sus metadatos, se asegura de que sea compatible tanto con la clasificación como con la no severa.
Para las clases POJO que no tienen un constructor sin XmlAdapter
puede usar un XmlAdapter
nivel de tipo para manejarlo:
java.sql.Date
no es compatible de forma predeterminada (aunque en EclipseLink JAXB (MOXy) sí lo es). Esto también se puede manejar usando un XmlAdapter
especificado a través de @XmlJavaTypeAdapter
en el campo, la propiedad o el nivel del paquete:
- http://blog.bdoughan.com/2011/05/jaxb-and-joda-time-dates-and-times.html
- http://blog.bdoughan.com/2011/01/jaxb-and-datetime-properties.html
Además, otro problema, ¿por qué la implementación de Java JAXB lanza una excepción si el campo es nulo y no va a ser ordenado de todos modos?
¿Qué excepción estás viendo? Normalmente, cuando un campo es nulo, no se incluye en el resultado XML, a menos que esté anotado con @XmlElement(nillable=true)
en cuyo caso el elemento incluirá xsi:nil="true"
.
ACTUALIZAR
Podrías hacer lo siguiente:
SqlDateAdapter
A continuación se muestra un XmlAdapter
que se convertirá de java.sql.Date
que su implementación JAXB no sabe cómo manejar a un java.util.Date
lo que hace:
package forum9268074;
import javax.xml.bind.annotation.adapters.*;
public class SqlDateAdapter extends XmlAdapter<java.util.Date, java.sql.Date> {
@Override
public java.util.Date marshal(java.sql.Date sqlDate) throws Exception {
if(null == sqlDate) {
return null;
}
return new java.util.Date(sqlDate.getTime());
}
@Override
public java.sql.Date unmarshal(java.util.Date utilDate) throws Exception {
if(null == utilDate) {
return null;
}
return new java.sql.Date(utilDate.getTime());
}
}
Foo
El XmlAdapter
se registra a través de la anotación @XmlJavaTypeAdapter
:
package forum9268074;
import java.sql.Date;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlRootElement(name = "Foo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
int i;
@XmlJavaTypeAdapter(SqlDateAdapter.class)
Date d; //java.sql.Date does not have a no-arg constructor
}
Para responder a su pregunta: creo que este es un diseño pobre en JAXB (o quizás en implementaciones de JAXB). La existencia de un constructor sin argumentos se valida durante la creación de JAXBContext
y, por lo tanto, se aplica independientemente de si desea utilizar JAXB para realizar comparaciones o no atenuantes. Hubiera sido genial si JAXB aplazara este tipo de verificación a JAXBContext.createUnmarshaller()
. Creo que sería interesante profundizar si este diseño es realmente obligatorio por la especificación o si es un diseño de implementación en JAXB-RI.
Pero de hecho hay una solución.
JAXB en realidad no necesita un constructor sin argumentos para el cálculo. A continuación, asumiré que está utilizando JAXB únicamente para la ordenación, no para nada desagradable. También asumo que tienes control sobre el objeto inmutable que deseas hacer para que puedas cambiarlo. Si este no es el caso, la única forma de avanzar es XmlAdapter
como se describe en otras respuestas.
Supongamos que tienes una clase, Customer
, que es un objeto inmutable. La creación de instancias se realiza mediante un patrón de generador o métodos estáticos.
public class Customer {
private final String firstName;
private final String lastName;
private Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// Object created via builder pattern
public static CustomerBuilder createBuilder() {
...
}
// getters here ...
}
Es cierto que, de forma predeterminada, no puede hacer que JAXB elimine todo este objeto. Obtendrá el error " .... El cliente no tiene un constructor predeterminado sin argumentos ".
Hay al menos dos formas de resolver esto. Ambos confían en poner un método o un constructor únicamente para hacer feliz la introspección de JAXB.
Solución 1
En este método, le decimos a JAXB que hay un método de fábrica estático que puede usar para instanciar una instancia de la clase. Sabemos, pero JAXB no, que de hecho nunca se utilizará. El truco es la anotación factoryMethod
con el parámetro factoryMethod
. Así es cómo:
@XmlType(factoryMethod="createInstanceJAXB")
public class Customer {
...
private static CustomerImpl createInstanceJAXB() { // makes JAXB happy, will never be invoked
return null; // ...therefore it doesn''t matter what it returns
}
...
}
No importa si el método es privado como en el ejemplo. JAXB todavía lo aceptará. Tu IDE marcará el método como no utilizado si lo haces privado, pero sigo prefiriendo el privado.
Solucion 2
En esta solución, agregamos un constructor privado sin argumentos que simplemente pasa nulo al constructor real.
public class Customer {
...
private Customer() { // makes JAXB happy, will never be invoked
this(null, null); // ...therefore it doesn''t matter what it creates
}
...
}
No importa si el constructor es privado como en el ejemplo. JAXB todavía lo aceptará.
Resumen
Ambas soluciones satisfacen el deseo de JAXB de crear instancias sin argumentos. Es una vergüenza que tengamos que hacer esto, cuando sabemos por nosotros mismos que solo necesitamos ordenar, no unmarshal.
Debo admitir que no sé en qué medida se trata de un hack que solo funcionará con JAXB-RI y no, por ejemplo, con EclipseLink MOXy. Definitivamente funciona con JAXB-RI.
Parece que tienes la impresión de que el código de introspección JAXB tendrá rutas de acción específicas para la inicialización. si es así, eso resultaría en una gran cantidad de código duplicado y sería una implementación deficiente. Me imagino que el código JAXB tiene una rutina común que examina una clase modelo la primera vez que se necesita y valida que sigue todas las convenciones necesarias. en esta situación, está fallando porque uno de los miembros no tiene el constructor sin argumentos requerido. lo más probable es que la lógica de inicialización no sea específica de marshall / unmarshall y también es muy poco probable que tenga en cuenta la instancia de objeto actual.