Чи слід оголошувати Jackson's ObjectMapper статичним полем?


361

Здається,ObjectMapper клас бібліотеки Джексона є безпечним для потоків .

Чи означає це, що я повинен оголосити своє ObjectMapperстатичним полем, як це

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

а не як поле на рівні екземпляра, як це?

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

Відповіді:


505

Так, це безпечно і рекомендується.

Єдине застереження зі сторінки, на яку ви посилаєтесь, - це те, що ви не можете змінювати конфігурацію картографа, як тільки він є загальним; але ви не змінюєте конфігурацію, так що це добре. Якщо вам потрібно змінити конфігурацію, ви зробите це зі статичного блоку, і це буде добре.

EDIT : (2013/10)

З 2.0 і вище, вище можна доповнити, зазначивши, що існує ще кращий спосіб: використання ObjectWriterта ObjectReaderоб'єкти, за допомогою яких можна побудувати ObjectMapper. Вони повністю незмінні, безпечні для потоків, це означає, що навіть теоретично неможливо викликати проблеми безпеки потоку (що може виникнути, ObjectMapperякщо код намагається повторно налаштувати екземпляр).


23
@StaxMan: Я трохи занепокоєний, якщо ObjectMapperвсе-таки безпечний потік після ObjectMapper#setDateFormat()викликається. Відомо, що SimpleDateFormatце не безпечно для потоків , тому ObjectMapperне буде, якщо воно не клонується, наприклад, SerializationConfigперед кожним writeValue()(я сумніваюся). Не могли б ви розвінчувати мій страх?
dma_k

49
DateFormatсправді клонований під капотом. Хороші підозри там, але ви прикриті. :)
StaxMan

3
Я стикався з дивною поведінкою під час тестів на одиницю / інтеграцію великого корпоративного додатку. Під час розміщення ObjectMapper як статичного атрибута остаточного класу я почав стикатися з проблемами PermGen. Хтось би потурбував пояснити ймовірні причини? Я використовував jackson-databind версії 2.4.1.
Алехо Чебаллос

2
@MiklosKrivan ти взагалі дивився ObjectMapper?! Методи названі writer()та reader()(і деякі readerFor(), writerFor()).
StaxMan

2
Виклику немає mapper.with()(оскільки "з" у Джексона передбачає побудову нового примірника та безпечне виконання потоків). Але щодо змін конфігурації: перевірка не проводиться, тому доступ до конфігурації ObjectMapperповинен бути захищеним. Щодо "copy ()": так, це створює нову нову копію, яка може бути повністю (пере) налаштована відповідно до тих же правил: спочатку повністю налаштуйте її, потім використовуйте, і це нормально. Існує пов'язана нетривіальна вартість (оскільки копія не може використовувати жодного кешованого обробника), але це безпечний спосіб, так.
StaxMan

53

Незважаючи на те, що ObjectMapper є безпечним для потоків, я б сильно не рекомендував оголошувати його статичною змінною, особливо в багатопотоковому застосуванні. Навіть не тому, що це погана практика, а тому, що ви ризикуєте тупиком. Я це кажу з власного досвіду. Я створив додаток із 4 однаковими потоками, які отримували та обробляли дані JSON з веб-служб. Моя програма часто затримувала наступну команду відповідно до дампу потоку:

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

Окрім цього, продуктивність була непоганою. Коли я замінив статичну змінну змінною, що базується на екземплярі, зупинення зникло і продуктивність збільшилася вчетверо. Тобто 2,4 мільйона документів JSON було оброблено за 40 хв.56 сек., А не за 2,5 години раніше.


15
Відповідь Гері повністю має сенс. Але якщо створити ObjectMapperекземпляр для кожного екземпляра класу, це може запобігти блокуванню, але згодом може бути дуже важким для GC (уявіть один екземпляр ObjectMapper для кожного екземпляра створеного вами класу). Підхід середнього шляху може бути, замість того, щоб утримувати лише один (загальнодоступний) статичний ObjectMapperекземпляр у додатку, ви можете оголосити (приватний) статичний екземпляр ObjectMapper кожного класу . Це зменшить глобальний замок (розподіляючи навантаження по класу) і не створить жодного нового об'єкта, отже, і світла на GC.
Абхідемон

І звичайно, підтримання ObjectPool найкращого шляху, з яким ви можете піти - тим самим даючи найкращі результати GCта Lockвиступи. Ви можете посилатися на наступне посилання для реалізації apache-common ObjectPool. commons.apache.org/proper/commons-pool/api-1.6/org/apache/…
Abhidemon

16
Я б запропонував альтернативу: зберігати статику ObjectMapperдесь, але лише отримувати ObjectReader/ ObjectWriterекземпляри (за допомогою допоміжних методів), зберігати посилання на ті, що в інших місцях (або динамічно викликати). Ці об'єкти для читання / запису не тільки повністю захищені від реконфігурації wrt, але і дуже легкі (екземпляри wrt mapper). Тому зберігання тисяч посилань не збільшує багато пам'яті.
StaxMan

Отже, виклики до ObjectReader екземплярів не блокують, тобто скажіть objectReader.readTree викликається в багатопотоковому додатку, нитки не будуть заблоковані в очікуванні іншої теми, використовуючи jackson 2.8.x
Xephonia

1

Хоча оголосити статичний ObjectMapper безпечно з точки зору безпеки потоку, ви повинні знати, що побудова статичних змінних об’єктів на Java вважається поганою практикою. Детальніше див. Чому статичні змінні вважаються злими? (і якщо ви хочете, моя відповідь )

Коротше кажучи, статики слід уникати, оскільки це ускладнює написання стислих одиничних тестів. Наприклад, за допомогою статичного кінцевого ObjectMapper ви не можете замінити серіалізацію JSON на манекенний код або на неоперацію.

Крім того, статичний фінал заважає вам коли-небудь перенастроювати ObjectMapper під час виконання. Ви можете не передбачити причину для цього зараз, але якщо ви зафіксуєтеся в статичному кінцевому шаблоні, нічого, крім зриву завантажувача, не дозволить вам його ініціалізувати.

У випадку з ObjectMapper його штрафують, але в цілому це погана практика, і немає переваги, ніж використання однотонної картини або інверсії управління для управління вашими довгоживучими об'єктами.


27
Я б припустив, що хоча статичні СТАТЕФУЛЬНІ одинакові типово є ознакою небезпеки, є достатньо причин, чому в цьому випадку спільне використання окремих (або невеликої кількості) випадків має сенс. Для цього можна скористатися ін'єкцією залежності; але в той же час варто запитати, чи існує реальна чи потенційна проблема для вирішення. Особливо це стосується тестування: тільки те, що в деяких випадках щось може бути проблематичним, не означає, що це для вашого використання. Отже: усвідомлення проблем, чудово. Якщо припустити, що «один розмір підходить усім», не так добре.
StaxMan

3
Очевидно, що розуміння проблем, пов'язаних з будь-яким дизайнерським рішенням, є важливим, і якщо ви можете щось зробити, не створюючи проблем для вашого використання, ви, за визначенням, не виникнете жодних проблем. Однак я запевняю, що використання статичних примірників немає ніяких переваг, і це відкриває двері до значних проблем у майбутньому, коли ваш код розвиватиметься або передається іншим розробникам, які можуть не розуміти ваших дизайнерських рішень. Якщо ваш фреймворк підтримує альтернативи, немає причин не уникати статичних екземплярів, вони, безумовно, не мають для них переваг.
JBCP

11
Я думаю, що це обговорення переходить у дуже загальні та менш корисні дотики. У мене немає жодних проблем припускати, що добре стати підозрілим до статичних одинаків. Мені просто здається, що я дуже звичний для використання в цьому конкретному випадку, і я не думаю, що можна отримати конкретні висновки з набору загальних вказівок. Тож я це залишу.
StaxMan

1
Пізній коментар, але чи не особливо ObjectMapper не погодився з цим поняттям? Він викриває readerForі writerForякі створюють ObjectReaderта ObjectWriterінстанції на вимогу. Тож я б сказав, покладіть картограф з початковою конфігурацією кудись статичним, а потім заведіть читачів / авторів з конфігурацією для кожного випадку, як вам це потрібно?
Каріган

1

Трюк, який я дізнався з цього PR, якщо ви не хочете визначати його як статичну остаточну змінну, але хочете зберегти трохи накладних витрат та гарантувати безпеку нитки.

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

заслуга автора.


2
Але є ризик витоку пам'яті, оскільки засіб ObjectMapperбуде приєднано до потоку, який може бути частиною пулу.
Кенстон Чой

@KenstonChoi Не повинно бути проблем, AFAIU. Нитки приходять і йдуть, місцеві жителі ниток приходять і йдуть з нитками. Залежно від кількості одночасних потоків ви можете або не можете дозволити собі пам'ять, але я не бачу "витоків".
Іван Балашов

2
@IvanBalashov, але якщо нитка створена / повернута з / до пулу потоків (наприклад, контейнери типу Tomcat), вона залишається. Це може бути бажано в деяких випадках, але щось, що нам потрібно знати.
Кенстон Чой

-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)

Метод _hashMapSuperInterfaceChain у класі com.fasterxml.jackson.databind.type.TypeFactory синхронізований. Я бачу суперечки на те саме при великих навантаженнях.

Можливо, ще одна причина уникати статичного ObjectMapper


1
Не забудьте перевірити останні версії (і, можливо, вкажіть версію, яку ви тут використовуєте). Було вдосконалено блокування на основі повідомлених проблем, і вирішення типу (f.ex) було повністю переписано для Jackson 2.7. Хоча в даному випадку TypeReferenceвикористовувати трохи дорогу річ: якщо можливо, її вирішення JavaTypeдозволить уникнути зовсім небагато обробки ( TypeReferences не може бути - на жаль - кешоване з причин, які я тут не вникаю), оскільки вони є "повністю вирішеними" (супер-тип, загальне введення тексту тощо).
StaxMan
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.