Должен ли я объявить Джексона ObjectMapper как статическое поле?

361

Библиотечный Джексона ObjectMapperкласс , кажется поточно .

Означает ли это, что я должен объявить себя ObjectMapperкак статическое поле, как это

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

а не как поле уровня экземпляра, как это?

class Me {
    private final ObjectMapper mapper = new ObjectMapper();
}
Чеок Ян Ченг
источник

Ответы:

505

Да, это безопасно и рекомендуется.

Единственное предостережение на странице, на которую вы ссылались, это то, что вы не можете изменять конфигурацию маппера, как только он будет опубликован; но вы не меняете конфигурацию, так что все в порядке. Если вам нужно изменить конфигурацию, вы сделаете это из статического блока, и это будет хорошо.

РЕДАКТИРОВАТЬ : (2013/10)

С 2.0 и выше, выше можно дополнить, отметив, что есть еще лучший способ: использовать ObjectWriterи ObjectReaderобъекты, которые могут быть сконструированы ObjectMapper. Они являются полностью неизменяемыми, поточно-ориентированными, что означает, что теоретически невозможно вызвать проблемы с безопасностью потоков (которые могут возникнуть, ObjectMapperесли код попытается перенастроить экземпляр).

StaxMan
источник
23
@StaxMan: Я немного обеспокоен, если ObjectMapperпосле вызова вызывается потокобезопасность ObjectMapper#setDateFormat(). Известно, что SimpleDateFormatне является потокобезопасным , поэтому ObjectMapperне будет, если не будет клонироваться, например, SerializationConfigперед каждым writeValue()(я сомневаюсь). Не могли бы вы развенчать мой страх?
dma_k
49
DateFormatдействительно клонируется под капотом. Хорошие подозрения там, но вы прикрыты. :)
StaxMan
3
Я столкнулся со странным поведением во время модульных / интеграционных тестов большого корпоративного приложения. Поместив ObjectMapper в качестве статического атрибута класса final, я столкнулся с проблемами PermGen. Кто-нибудь захочет объяснить возможные причины? Я использовал версию 2.4.1 jackson-databind.
Alejo Ceballos
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/…
Абидемон
16
Я бы предложил альтернативу: ObjectMapperгде-то сохранять статичность , но только получать ObjectReader/ ObjectWriterэкземпляры (через вспомогательные методы), сохранять ссылки на них в других местах (или динамически вызывать). Эти объекты чтения / записи являются не только полностью поточно-ориентированными при реконфигурации, но и очень легкими (в случае экземпляров mapper). Таким образом, хранение тысяч ссылок не увеличивает объем используемой памяти.
StaxMan
Таким образом, вызов экземпляров ObjectReader не блокируется, т. Е., Скажем, objectReader.readTree вызывается в многопоточном приложении, потоки не будут блокироваться, ожидая другого потока, используя jackson 2.8.x
Xephonia
1

Хотя можно объявить статический ObjectMapper с точки зрения безопасности потоков, вы должны знать, что создание статических переменных Object в Java считается плохой практикой. Подробнее см. Почему статические переменные считаются злыми? (и, если хотите, мой ответ )

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

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

В случае ObjectMapper это хорошо, но в целом это плохая практика, и нет никакого преимущества в использовании одноэлементного шаблона или инверсии управления для управления вашими долгоживущими объектами.

JBCP
источник
27
Я бы предположил, что хотя статические синглтоны STATEFUL обычно являются признаком опасности, есть достаточно причин, по которым в этом случае имеет смысл разделять один (или небольшое количество) экземпляров. Для этого можно использовать инъекцию зависимости; но в то же время стоит спросить, существует ли реальная или потенциальная проблема, которую нужно решить. Это особенно относится к тестированию: то, что что-то может быть проблематичным, в некоторых случаях не означает, что оно предназначено для вашего использования. Итак: быть в курсе проблем, отлично. Предполагая, что «один размер подходит всем», не очень хорошо.
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

Harshit
источник
1
Обязательно ознакомьтесь с последними версиями (и, возможно, укажите версию, которую вы используете здесь). Были улучшены блокировки на основе сообщений о проблемах, и разрешение типов (f.ex) было полностью переписано для Jackson 2.7. Хотя в данном случае это TypeReferenceдовольно дорогая вещь: если возможно, ее разрешение JavaTypeпозволит избежать значительной обработки ( TypeReferenceк сожалению, ее нельзя кэшировать по причинам, которые я здесь не буду изучать), поскольку они «полностью разрешены» (супер-тип, универсальная типизация и т. д.).
StaxMan