¿Debo declarar el ObjectMapper de Jackson como un campo estático?

361

La biblioteca de Jackson ObjectMapper clase de parece ser segura para subprocesos .

¿Esto significa que debería declarar mi ObjectMappercomo un campo estático como este

class Me {
    private static final ObjectMapper mapper = new ObjectMapper();
}

en lugar de como un campo de nivel de instancia como este?

class Me {
    private final ObjectMapper mapper = new ObjectMapper();
}
Cheok Yan Cheng
fuente

Respuestas:

505

Sí, eso es seguro y recomendado.

La única advertencia de la página que mencionó es que no puede modificar la configuración del mapeador una vez que se comparte; pero no está cambiando la configuración, así que está bien. Si necesita cambiar la configuración, lo haría desde el bloque estático y también estaría bien.

EDITAR : (2013/10)

Con 2.0 y versiones superiores, se puede aumentar la versión anterior al señalar que hay una forma aún mejor: el uso ObjectWritery los ObjectReaderobjetos, que pueden ser construidos por ObjectMapper. Son completamente inmutables, seguros para subprocesos, lo que significa que ni siquiera es teóricamente posible causar problemas de seguridad de subprocesos (que pueden ocurrir ObjectMappersi el código intenta reconfigurar la instancia).

StaxMan
fuente
23
@StaxMan: Estoy un poco preocupado si ObjectMappertodavía es seguro para subprocesos después de que ObjectMapper#setDateFormat()se llama. Se sabe que SimpleDateFormatno es seguro para subprocesos , por ObjectMapperlo que no lo será a menos que clone, por ejemplo, SerializationConfigantes de cada uno writeValue()(dudo). ¿Podrías desacreditar mi miedo?
dma_k
49
DateFormatde hecho está clonado debajo del capó. Buena sospecha allí, pero estás cubierto. :)
StaxMan
3
Me he enfrentado a comportamientos extraños durante las pruebas de unidad / integración de una aplicación empresarial grande. Al poner ObjectMapper como atributo de clase final estático, comencé a enfrentar problemas de PermGen. ¿A alguien le importaría explicar las causas probables? Estaba usando la versión 2.4.1 de jackson-databind.
Alejo Ceballos
2
@MiklosKrivan has mirado ObjectMapperen absoluto ?! Los métodos se nombran writer()y reader()(y algunos readerFor(), writerFor()).
StaxMan
2
No hay mapper.with()llamada (ya que "con" en Jackson implica la construcción de una nueva instancia y una ejecución segura para subprocesos). Pero con respecto a los cambios de configuración: no se realiza ninguna comprobación, por lo que se ObjectMapperdebe proteger el acceso a la configuración . En cuanto a "copy ()": sí, eso crea una nueva copia nueva que puede ser completamente (re) configurada, de acuerdo con las mismas reglas: configúrela completamente primero, luego úsela, y eso está bien. Hay un costo no trivial asociado (ya que la copia no puede usar ninguno de los controladores en caché), pero es la forma segura, sí.
StaxMan
53

Aunque ObjectMapper es seguro para subprocesos, desaconsejaría declararlo como una variable estática, especialmente en aplicaciones multiproceso. Ni siquiera porque sea una mala práctica, sino porque corres un gran riesgo de bloqueo. Lo digo desde mi propia experiencia. Creé una aplicación con 4 hilos idénticos que obtenían y procesaban datos JSON de los servicios web. Mi aplicación se detenía con frecuencia en el siguiente comando, de acuerdo con el volcado de subprocesos:

Map aPage = mapper.readValue(reader, Map.class);

Además de eso, el rendimiento no fue bueno. Cuando reemplacé la variable estática con la variable basada en la instancia, el bloqueo desapareció y el rendimiento se cuadruplicó. Es decir, 2,4 millones de documentos JSON se procesaron en 40min.56seg., En lugar de 2.5 horas antes.

Gary Greenberg
fuente
15
La respuesta de Gary tiene mucho sentido. Sin embargo, crear una ObjectMapperinstancia para cada instancia de clase puede evitar bloqueos, pero puede ser muy pesado en GC más adelante (imagine una instancia de ObjectMapper para cada instancia de la clase que cree). Un enfoque de ruta intermedia puede ser, en lugar de mantener solo una ObjectMapperinstancia estática (pública) en la aplicación, puede declarar una instancia estática (privada) ObjectMapper en cada clase . Esto reducirá un bloqueo global (al distribuir la carga a nivel de clase) y tampoco creará ningún objeto nuevo, por lo tanto, también se iluminará el GC.
Abhidemon
Y, por supuesto, mantener una ObjectPool es la mejor manera de avanzar, brindando lo mejor GCy el mejor Lockrendimiento. Puede consultar el siguiente enlace para la ObjectPoolimplementación de apache-common . commons.apache.org/proper/commons-pool/api-1.6/org/apache/…
Abhidemon
16
Sugeriría una alternativa: mantener estático en ObjectMapperalgún lugar, pero solo obtener ObjectReader/ ObjectWriterinstancias (a través de métodos auxiliares), retener referencias a aquellos en otros lugares (o llamar dinámicamente). Estos objetos de lector / escritor no solo son una reconfiguración de wrt totalmente segura para subprocesos, sino que también son muy livianos (instancias de mapeador de wrt). Por lo tanto, mantener miles de referencias no agrega mucho uso de memoria.
StaxMan
Entonces, las llamadas a las instancias de ObjectReader no se bloquean, es decir, se llama a objectReader.readTree en una aplicación multiproceso, los hilos no se bloquearán esperando en otro hilo, usando jackson 2.8.x
Xephonia
1

Aunque es seguro declarar un ObjectMapper estático en términos de seguridad de subprocesos, debe tener en cuenta que la construcción de variables Object estáticas en Java se considera una mala práctica. Para obtener más detalles, consulte ¿Por qué las variables estáticas se consideran malas? (y si quieres, mi respuesta )

En resumen, las estadísticas deben evitarse porque dificultan la redacción de pruebas unitarias concisas. Por ejemplo, con un ObjectMapper final estático, no puede intercambiar la serialización JSON por código ficticio o no operativo.

Además, una final estática le impide volver a configurar ObjectMapper en tiempo de ejecución. Puede que no imagines una razón para eso ahora, pero si te encierras en un patrón final estático, nada menos que derribar el cargador de clases te permitirá reiniciarlo.

En el caso de ObjectMapper está bien, pero en general es una mala práctica y no hay ninguna ventaja sobre el uso de un patrón singleton o inversión de control para administrar sus objetos de larga duración.

JBCP
fuente
27
Sugeriría que, aunque los tonos ESTÁTICOS ESTÁTICOS suelen ser una señal de peligro, existen suficientes razones por las que, en este caso, compartir una sola (o una pequeña cantidad de) casos tiene sentido. Uno puede querer usar la inyección de dependencia para eso; pero al mismo tiempo vale la pena preguntar si hay un problema real o potencial para resolver. Esto se aplica especialmente a las pruebas: el hecho de que algo pueda ser problemático en algunos casos no significa que sea para su uso. Entonces: ser consciente de los problemas, genial. Suponiendo que "una talla para todos", no es tan bueno.
StaxMan
3
Obviamente, es importante comprender los problemas relacionados con cualquier decisión de diseño, y si puede hacer algo sin causar problemas para su caso de uso, por definición no causará ningún problema. Sin embargo, diría que no hay beneficios para el uso de instancias estáticas y abre la puerta a problemas importantes en el futuro a medida que su código evoluciona o se entrega a otros desarrolladores que podrían no entender sus decisiones de diseño. Si su marco admite alternativas, no hay razón para no evitar instancias estáticas, ciertamente no hay ventajas para ellos.
JBCP
11
Creo que esta discusión entra en tangentes muy generales y menos útiles. No tengo ningún problema para sugerir que es bueno sospechar de los singletons estáticos. Resulta que estoy muy familiarizado con el uso para este caso particular y no creo que se pueda llegar a conclusiones específicas del conjunto de pautas generales. Así que lo dejaré así.
StaxMan
1
Comentario tardío, pero ¿ObjectMapper en particular no estaría en desacuerdo con esta noción? Expone readerFory writerForque crea ObjectReadere ObjectWriterinstancias bajo demanda. ¿Entonces diría que coloque el mapeador con la configuración inicial en algún lugar estático, luego obtenga lectores / escritores con la configuración por caso cuando los necesite?
Carighan
1

Un truco que aprendí de este PR si no quieres definirlo como una variable final estática pero quieres ahorrar un poco de sobrecarga y garantizar un hilo seguro.

private static final ThreadLocal<ObjectMapper> om = new ThreadLocal<ObjectMapper>() {
    @Override
    protected ObjectMapper initialValue() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return objectMapper;
    }
};

public static ObjectMapper getObjectMapper() {
    return om.get();
}

crédito al autor.

Henry Lin
fuente
2
Pero existe el riesgo de una pérdida de memoria, ya ObjectMapperque se conectará al hilo que puede ser parte de un grupo.
Kenston Choi
@KenstonChoi No debería ser un problema, AFAIU. Los hilos van y vienen, los locales de hilos van y vienen con los hilos. Dependiendo de la cantidad de hilos simultáneos, puede permitirse o no la memoria, pero no veo "fugas".
Ivan Balashov
2
@IvanBalashov, pero si el subproceso se crea / devuelve desde / a un grupo de subprocesos (por ejemplo, contenedores como Tomcat), permanece. Esto puede ser deseable en algunos casos, pero es algo que debemos tener en cuenta.
Kenston Choi
-1

com.fasterxml.jackson.databind.type.TypeFactory._hashMapSuperInterfaceChain (HierarchicType)

com.fasterxml.jackson.databind.type.TypeFactory._findSuperInterfaceChain(Type, Class)
  com.fasterxml.jackson.databind.type.TypeFactory._findSuperTypeChain(Class, Class)
     com.fasterxml.jackson.databind.type.TypeFactory.findTypeParameters(Class, Class, TypeBindings)
        com.fasterxml.jackson.databind.type.TypeFactory.findTypeParameters(JavaType, Class)
           com.fasterxml.jackson.databind.type.TypeFactory._fromParamType(ParameterizedType, TypeBindings)
              com.fasterxml.jackson.databind.type.TypeFactory._constructType(Type, TypeBindings)
                 com.fasterxml.jackson.databind.type.TypeFactory.constructType(TypeReference)
                    com.fasterxml.jackson.databind.ObjectMapper.convertValue(Object, TypeReference)

El método _hashMapSuperInterfaceChain en la clase com.fasterxml.jackson.databind.type.TypeFactory está sincronizado. Estoy viendo contenciones sobre lo mismo en grandes cargas.

Puede ser otra razón para evitar un ObjectMapper estático

Harshit
fuente
1
Asegúrese de revisar las últimas versiones (y tal vez indique la versión que usa aquí). Se han realizado mejoras en el bloqueo en función de los problemas informados, y la resolución de tipo (f.ex) se reescribió por completo para Jackson 2.7. Aunque en este caso,TypeReference es algo un poco costoso de usar: si es posible, resolverlo JavaTypeevitaría una gran cantidad de procesamiento ( TypeReferencedesafortunadamente, los s no se pueden almacenar en caché por razones que no voy a investigar aquí), ya que están "totalmente resueltos" (supertipo, tipificación genérica, etc.).
StaxMan