Czy powinienem zadeklarować ObjectMapper Jacksona jako pole statyczne?


361

Jackson biblioteki ObjectMapperklasa wydaje się być bezpieczne dla wątków .

Czy to oznacza, że ​​powinienem zadeklarować swoje ObjectMapperpole jako takie

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

zamiast takiego pola na poziomie instancji jak to?

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

Odpowiedzi:


505

Tak, jest to bezpieczne i zalecane.

Jedynym zastrzeżeniem ze strony, do której się odwoływałeś, jest to, że nie możesz modyfikować konfiguracji mapera po jego udostępnieniu; ale nie zmieniasz konfiguracji, więc nie ma sprawy. Gdybyś musiał zmienić konfigurację, zrobiłbyś to z bloku statycznego i byłoby dobrze.

EDYCJA : (2013/10)

W wersji 2.0 i nowszej powyższe można rozszerzyć, zauważając, że istnieje jeszcze lepszy sposób: użycie ObjectWriteri ObjectReaderobiekty, które można zbudować ObjectMapper. Są w pełni niezmienne, bezpieczne dla wątków, co oznacza, że ​​teoretycznie nie jest możliwe spowodowanie problemów z bezpieczeństwem wątków (co może się zdarzyć, ObjectMapperjeśli kod spróbuje ponownie skonfigurować instancję).


23
@StaxMan: Jestem trochę zaniepokojony, czy ObjectMapperpo ObjectMapper#setDateFormat()wywołaniu nadal jest bezpieczny dla wątków . Wiadomo, że SimpleDateFormatnie jest bezpieczny dla wątków , a więc ObjectMappernie będzie, dopóki nie sklonuje np. SerializationConfigPrzed każdym writeValue()(wątpię). Czy możesz obalić mój strach?
dma_k,

49
DateFormatjest rzeczywiście sklonowany pod maską. Dobre podejrzenia, ale jesteś objęty ochroną. :)
StaxMan,

3
Napotkałem dziwne zachowania podczas testów jednostkowych / integracyjnych dużej aplikacji korporacyjnej. Umieszczając ObjectMapper jako statyczny atrybut klasy finalnej, zacząłem napotykać problemy PermGen. Czy ktoś chciałby wyjaśnić prawdopodobne przyczyny? Korzystałem z jackson-databind w wersji 2.4.1.
Alejo Ceballos,

2
@MiklosKrivan ObjectMapperw ogóle na ciebie patrzyłeś ?! Metody są nazwane writer()i reader()(a niektóre readerFor(), writerFor()).
StaxMan

2
Nie ma mapper.with()wywołania (ponieważ „z” w Jacksonie oznacza budowę nowej instancji i wykonanie bezpieczne dla wątków). Ale w odniesieniu do zmian w konfiguracji: nie dokonuje się sprawdzania, więc dostęp do konfiguracji ObjectMappermusi być chroniony. Jeśli chodzi o „copy ()”: tak, to tworzy nową, nową kopię, która może być w pełni (ponownie) skonfigurowana zgodnie z tymi samymi regułami: najpierw ją najpierw skonfiguruj, a potem użyj i wszystko jest w porządku. Powiązane są nietrywialne koszty (ponieważ kopia nie może korzystać z żadnej z buforowanych procedur obsługi), ale jest to bezpieczny sposób, tak.
StaxMan,

53

Chociaż ObjectMapper jest bezpieczny dla wątków, zdecydowanie odradzałbym deklarowanie go jako zmiennej statycznej, szczególnie w aplikacjach wielowątkowych. Nawet dlatego, że jest to zła praktyka, ale ponieważ istnieje duże ryzyko zakleszczenia. Mówię to z własnego doświadczenia. Stworzyłem aplikację z 4 identycznymi wątkami, które pobierały i przetwarzały dane JSON z usług internetowych. Moja aplikacja często zwlekała z następującą komendą, zgodnie ze zrzutem wątku:

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

Poza tym wydajność nie była dobra. Kiedy zastąpiłem zmienną statyczną zmienną opartą na instancji, przeciągnięcie zniknęło, a wydajność czterokrotnie. To znaczy 2,4 miliona dokumentów JSON zostało przetworzonych w 40 minut 56 sekund, zamiast 2,5 godziny wcześniej.


15
Odpowiedź Gary'ego ma całkowicie sens. Ale tworzenie ObjectMapperinstancji dla każdej instancji klasy może zapobiegać blokadom, ale może być naprawdę ciężkie dla GC później (wyobraź sobie jedną instancję ObjectMapper dla każdej instancji tworzonej klasy). Podejściem środkowej ścieżki może być, zamiast utrzymywania tylko jednej (publicznej) ObjectMapperinstancji statycznej w aplikacji, możesz zadeklarować (prywatną) instancję statyczną ObjectMapper w każdej klasie . Zmniejszy to globalną blokadę (poprzez dystrybucję obciążenia według klasy obciążenia) i nie stworzy też żadnego nowego obiektu, stąd też światło na GC.
Abhidemon,

I oczywiście utrzymanie ObjectPool jest najlepszym sposobem, w jaki możesz iść - tym samym dając najlepsze GCi Lockwystępy. Możesz zapoznać się z poniższym linkiem do ObjectPoolimplementacji apache-common . commons.apache.org/proper/commons-pool/api-1.6/org/apache/…
Abhidemon

16
Sugerowałbym alternatywę: trzymaj ObjectMappergdzieś statyczny , ale tylko get ObjectReader/ ObjectWriterinstancje (za pomocą metod pomocniczych), zachowuj odniesienia do tych w innych miejscach (lub dynamicznie wywołuj). Te obiekty do odczytu / zapisu są nie tylko w pełni bezpieczne dla wątków rekonfiguracji wrt, ale także bardzo lekkie (instancje wrt mapper). Zachowanie tysięcy referencji nie powoduje dużego zużycia pamięci.
StaxMan

Więc są wywołania do instancji ObjectReader, które nie blokują, tj. Powiedzmy, że objectReader.readTree jest wywoływany w aplikacji wielowątkowej, wątki nie będą blokowane, czekając na inny wątek, używając jackson 2.8.x
Xephonia 30.01.2019

1

Chociaż deklarowanie statycznego obiektu ObjectMapper jest bezpieczne pod względem bezpieczeństwa wątków, należy pamiętać, że konstruowanie statycznych zmiennych Object w Javie jest uważane za złą praktykę. Aby uzyskać więcej informacji, zobacz Dlaczego zmienne statyczne są uważane za złe? (a jeśli chcesz, moja odpowiedź )

Krótko mówiąc, należy unikać statyki, ponieważ utrudniają pisanie zwięzłych testów jednostkowych. Na przykład przy statycznym ostatecznym ObjectMapper nie można zamienić serializacji JSON na kod fikcyjny lub brak operacji.

Ponadto statyczny finał uniemożliwia kiedykolwiek ponowną konfigurację ObjectMapper w czasie wykonywania. Być może nie wyobrażasz sobie teraz powodu, ale jeśli zablokujesz się w statycznym ostatecznym wzorze, nic poza wyburzeniem programu ładującego klasy nie pozwoli ci go ponownie zainicjować.

W przypadku ObjectMapper jest to w porządku, ale ogólnie jest to zła praktyka i nie ma przewagi nad użyciem wzorca singletonu lub odwrócenia kontroli do zarządzania długowiecznymi obiektami.


27
Sugerowałbym, że chociaż statyczne STANOWNE singletony są zwykle znakiem niebezpieczeństwa, istnieje wystarczające powody, dla których w tym przypadku współużytkowanie jednej (lub niewielkiej liczby) instancji ma sens. Można do tego użyć zastrzyku zależności; ale jednocześnie warto zapytać, czy istnieje rzeczywisty, czy potencjalny problem do rozwiązania. Dotyczy to zwłaszcza testowania: tylko dlatego, że coś może być problematyczne w niektórych przypadkach, nie oznacza, że ​​jest to do użytku. Tak więc: świadomość problemów, świetnie. Zakładając, że „jeden rozmiar pasuje do wszystkich”, nie tak dobrze.
StaxMan,

3
Oczywiście zrozumienie problemów związanych z każdą decyzją projektową jest ważne, a jeśli możesz zrobić coś bez powodowania problemów w twoim przypadku, z definicji nie spowoduje to żadnych problemów. Jednak twierdzę, że nie ma żadnych korzyści z używania instancji statycznych i otwiera drzwi do poważnych problemów w przyszłości, gdy twój kod ewoluuje lub jest przekazywany innym programistom, którzy mogą nie rozumieć twoich decyzji projektowych. Jeśli Twój framework obsługuje alternatywy, nie ma powodu, aby nie unikać wystąpień statycznych, z pewnością nie mają one żadnych zalet.
JBCP,

11
Myślę, że ta dyskusja przechodzi w bardzo ogólne i mniej przydatne styczne. Nie mam problemu z sugerowaniem, że dobrze jest być podejrzliwym w stosunku do statycznych singletonów. Po prostu jestem bardzo zaznajomiony z użytkowaniem tego konkretnego przypadku i nie sądzę, aby można było wyciągnąć konkretne wnioski z zestawu ogólnych wytycznych. Więc zostawię to przy tym.
StaxMan,

1
Późny komentarz, ale czy ObjectMapper w szczególności nie zgadzałby się z tym pojęciem? Ujawnia readerFori writerForktóre tworzą ObjectReaderi ObjectWriterwystąpienia na żądanie. Więc powiedziałbym, żeby umieścić program mapujący z początkową konfiguracją gdzieś statycznie, a następnie uzyskać czytelników / pisarzy z konfiguracją dla poszczególnych skrzynek, jeśli ich potrzebujesz?
Carighan

1

Sztuczka, której nauczyłem się z tego PR, jeśli nie chcesz zdefiniować jej jako statycznej zmiennej końcowej, ale chcesz zaoszczędzić trochę narzutów i zagwarantować bezpieczeństwo wątku.

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

podziękowania dla autora.


2
Istnieje jednak ryzyko wycieku pamięci, ponieważ ObjectMapperzostanie on dołączony do wątku, który może być częścią puli.
Kenston Choi

@KenstonChoi Nie powinno być problemu, AFAIU. Wątki przychodzą i odchodzą, miejscowi wątki przychodzą i odchodzą z wątkami. W zależności od liczby jednoczesnych wątków możesz sobie pozwolić na pamięć, ale nie widzę „wycieków”.
Ivan Balashov

2
@IvanBalashov, ale jeśli wątek zostanie utworzony / zwrócony z / do puli wątków (np. Pojemników takich jak Tomcat), pozostaje. W niektórych przypadkach może to być pożądane, ale musimy być tego świadomi.
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)

Metoda _hashMapSuperInterfaceChain w klasie com.fasterxml.jackson.databind.type.TypeFactory jest zsynchronizowana. Widzę spór o to samo przy dużych obciążeniach.

Może być kolejnym powodem do uniknięcia statycznego ObjectMapper


1
Sprawdź najnowsze wersje (i być może wskaż wersję, której używasz tutaj). Poprawiono blokowanie w oparciu o zgłoszone problemy, a rozdzielczość typu (np.) Została w pełni przepisana dla Jackson 2.7. Chociaż w tym przypadku TypeReferencejest to nieco kosztowna rzecz w użyciu: jeśli to możliwe, rozwiązanie jej JavaTypepozwoli uniknąć sporo przetwarzania ( TypeReferencenie można - niestety - buforować z powodów, których nie będę tu kopać), ponieważ one są „w pełni rozwiązane” (super-type, ogólne pisanie itp.).
StaxMan
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.