Devo declarar o ObjectMapper de Jackson como um campo estático?

361

A ObjectMapperclasse da biblioteca Jackson parece ser segura para threads .

Isso significa que devo declarar meu ObjectMappercomo um campo estático como este

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

em vez de um campo no nível da instância como este?

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

Respostas:

505

Sim, isso é seguro e recomendado.

A única ressalva da página que você referiu é que você não pode modificar a configuração do mapeador depois que ele é compartilhado; mas você não está alterando a configuração. Se você precisasse alterar a configuração, faria isso a partir do bloco estático e também ficaria bem.

EDIT : (2013/10)

Com o 2.0 e acima, o acima pode ser aumentado, observando que existe uma maneira ainda melhor: uso ObjectWritere ObjectReaderobjetos pelos quais você pode construir ObjectMapper. Eles são totalmente imutáveis, seguros para threads, o que significa que nem é possível teoricamente causar problemas de segurança de threads (o que pode ocorrer ObjectMapperse o código tentar reconfigurar a instância).

StaxMan
fonte
23
@ StaxMan: Estou um pouco preocupado se ObjectMapperainda é seguro para threads depois de ObjectMapper#setDateFormat()ser chamado. Sabe-se que SimpleDateFormatnão é seguro para threads , portanto ObjectMapper, não será, a menos que seja clonado, por exemplo, SerializationConfigantes de cada um writeValue()(duvido). Você poderia desmascarar meu medo?
Dma_k 02/08
49
DateFormaté de fato clonado sob o capô. Boa suspeita, mas você está coberto. :)
StaxMan
3
Eu enfrentei comportamentos estranhos durante os testes de unidade / integração de um aplicativo corporativo de grande porte. Ao colocar o ObjectMapper como atributo de classe final estática, comecei a enfrentar problemas com o PermGen. Alguém se importaria em explicar as causas prováveis? Eu estava usando o jackson-databind versão 2.4.1.
Alejo Ceballos
2
@MiklosKrivan você já olhou ObjectMapper?! Métodos são nomeados writer()e reader()(e alguns readerFor(), writerFor()).
StaxMan
2
Não há mapper.with()chamada (uma vez que "with" em Jackson implica construção de uma nova instância e execução segura de thread). Mas com relação às alterações de configuração: nenhuma verificação é feita, portanto, o acesso à configuração ObjectMapperdeve ser protegido. Quanto a "copy ()": sim, isso cria uma nova cópia nova que pode ser totalmente (re) configurada, de acordo com as mesmas regras: configure-a totalmente primeiro, depois use, e isso é bom. Existe um custo não trivial associado (já que a cópia não pode usar nenhum dos manipuladores em cache), mas é o caminho seguro, sim.
StaxMan
53

Embora o ObjectMapper seja seguro para threads, eu desencorajaria fortemente de declará-lo como uma variável estática, especialmente em aplicativos multithread. Nem mesmo porque é uma prática ruim, mas porque você corre um grande risco de conflito. Estou contando isso por experiência própria. Criei um aplicativo com 4 threads idênticos que estavam obtendo e processando dados JSON de serviços da web. Meu aplicativo estava com frequência parado no seguinte comando, de acordo com o despejo de threads:

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

Além disso, o desempenho não foi bom. Quando substituí a variável estática pela variável baseada em instância, a paralisação desapareceu e o desempenho quadruplicou. Ou seja, 2,4 milhões de documentos JSON foram processados ​​em 40min.56seg., Em vez de 2,5 horas anteriormente.

Gary Greenberg
fonte
15
A resposta de Gary faz completamente sentido. Porém, a criação de uma ObjectMapperinstância para cada instância da classe pode impedir bloqueios, mas pode ser muito pesada no GC posteriormente (imagine uma instância do ObjectMapper para cada instância da classe que você criar). Uma abordagem do caminho do meio pode ser, em vez de manter apenas uma ObjectMapperinstância estática (pública) em todo o aplicativo, você pode declarar uma instância estática (privada) de ObjectMapper todas as classes . Isso reduzirá um bloqueio global (distribuindo a carga em classe) e também não criará nenhum novo objeto; portanto, também haverá luz no GC.
precisa saber é o seguinte
E, ObjectPool é claro, manter um é o melhor caminho a seguir - fornecendo o melhor GCe as melhores Lockperformances. Você pode consultar o link a seguir para ObjectPoolimplementação do apache-common . commons.apache.org/proper/commons-pool/api-1.6/org/apache/…
Abhidemon
16
Eu sugeriria uma alternativa: mantenha a estática em ObjectMapperalgum lugar, mas apenas obtenha ObjectReader/ ObjectWriterinstâncias (por meio de métodos auxiliares), mantenha referências a outras em outros lugares (ou chame dinamicamente). Esses objetos de leitor / gravador não são apenas reconfiguração wrt totalmente segura para thread, mas também muito leves (instâncias do mapeador wrt). Portanto, manter milhares de referências não adiciona muito uso de memória.
precisa saber é
Assim são chamadas às instâncias ObjectReader não bloqueiam ou seja dizer objectReader.readTree é chamado no aplicativo com vários segmentos, tópicos costuma ser bloqueado esperando em outro segmento, usando jackson 2.8.x
Xephonia
1

Embora seja seguro declarar um ObjectMapper estático em termos de segurança de encadeamento, você deve estar ciente de que a construção de variáveis ​​estáticas de Objeto em Java é considerada uma prática ruim. Para mais detalhes, consulte Por que variáveis ​​estáticas são consideradas más? (e se você quiser, minha resposta )

Em resumo, a estática deve ser evitada, pois dificulta a criação de testes de unidade concisos. Por exemplo, com um ObjectMapper final estático, você não pode trocar a serialização JSON por código fictício ou não operacional.

Além disso, uma final estática impede que você reconfigure o ObjectMapper em tempo de execução. Você pode não imaginar uma razão para isso agora, mas se você se prender a um padrão final estático, nada além de derrubar o carregador de classes permitirá que você o reinicialize.

No caso do ObjectMapper, tudo bem, mas, em geral, é uma prática ruim e não há vantagem em usar um padrão singleton ou inversão de controle para gerenciar seus objetos de vida longa.

JBCP
fonte
27
Eu sugeriria que, embora os singletons STATEFUL estáticos sejam tipicamente um sinal de perigo, há razões suficientes pelas quais, neste caso, compartilhar um único (ou pequeno número de) instâncias faz sentido. Pode-se querer usar injeção de dependência para isso; mas, ao mesmo tempo, vale a pena perguntar se há um problema real ou potencial a ser resolvido. Isso se aplica especialmente ao teste: apenas porque algo pode ser problemático em alguns casos, não significa que é para o seu uso. Então: estar ciente dos problemas, ótimo. Assumindo "tamanho único", não é tão bom.
precisa saber é o seguinte
3
Obviamente, é importante entender os problemas envolvidos em qualquer decisão de design e, se você puder fazer algo sem causar problemas ao seu caso de uso, por definição, não causará nenhum problema. No entanto, eu argumentaria que não há benefícios para o uso de instâncias estáticas e isso abre a porta para problemas significativos no futuro, à medida que seu código evolui ou é entregue a outros desenvolvedores que podem não entender suas decisões de design. Se sua estrutura suportar alternativas, não há motivo para evitar instâncias estáticas, certamente não há vantagens para elas.
JBCP #
11
Eu acho que essa discussão entra em tangentes muito gerais e menos úteis. Não tenho nenhum problema em sugerir que é bom suspeitar de singletons estáticos. Por acaso, sou familiar para uso neste caso em particular e não acho que se possa chegar a conclusões específicas a partir de um conjunto de diretrizes gerais. Então, vou deixar por isso mesmo.
StaxMan
11
Comentários tardios, mas o ObjectMapper em particular não discordaria dessa noção? Ela expõe readerFore writerForcria ObjectReadere ObjectWriterinstâncias sob demanda. Então, eu diria que coloque o mapeador com a configuração inicial em algum lugar estático e obtenha leitores / gravadores com a configuração por caso, conforme necessário.
22918 Carighan
1

Um truque que aprendi com este PR se você não deseja defini-lo como uma variável final estática, mas deseja economizar um pouco de sobrecarga e garantir a segurança do thread.

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 ao autor.

Henry Lin
fonte
2
Mas existe o risco de um vazamento de memória, pois ObjectMapperele será anexado ao encadeamento que pode fazer parte de um pool.
Kenston Choi 01/01/19
@KenstonChoi Não deve ser um problema, AFAIU. Os threads vêm e vão, os locais de threads vêm e vão com os threads. Dependendo da quantidade de threads simultâneos, você pode ou não pagar pela memória, mas não vejo "vazamentos".
Ivan Balashov
2
@IvanBalashov, mas se o encadeamento for criado / retornado de / para um pool de encadeamentos (por exemplo, contêineres como o Tomcat), ele permanecerá. Isso pode ser desejado em alguns casos, mas algo que precisamos estar cientes.
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)

O método _hashMapSuperInterfaceChain na classe com.fasterxml.jackson.databind.type.TypeFactory é sincronizado. Estou vendo contenda no mesmo em altas cargas.

Pode ser outro motivo para evitar um ObjectMapper estático

Harshit
fonte
11
Verifique as versões mais recentes (e talvez indique a versão que você usa aqui). Houve melhorias no bloqueio com base nos problemas relatados, e a resolução de tipo (ex.ex) foi totalmente reescrita para Jackson 2.7. Embora, neste caso, TypeReferenceseja algo um pouco caro de usar: se possível, resolvê-lo JavaTypeevitaria um pouco de processamento ( TypeReferences não pode ser - infelizmente - armazenado em cache por razões que não vou cavar aqui), pois eles são "totalmente resolvidos" (supertipo, digitação genérica etc.).
precisa saber é