Haruskah saya mendeklarasikan ObjectMapper Jackson sebagai bidang statis?

361

The Jackson perpustakaan ObjectMapperkelas tampaknya thread aman .

Apakah ini berarti bahwa saya harus menyatakan bidang saya ObjectMapperstatis seperti ini

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

alih-alih sebagai bidang tingkat instance seperti ini?

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

Jawaban:

505

Ya, itu aman dan direkomendasikan.

Satu-satunya peringatan dari halaman yang Anda referensikan adalah Anda tidak dapat memodifikasi konfigurasi mapper setelah dibagikan; tetapi Anda tidak mengubah konfigurasi sehingga tidak masalah. Jika Anda memang perlu mengubah konfigurasi, Anda akan melakukannya dari blok statis dan itu akan baik-baik saja.

EDIT : (2013/10)

Dengan 2.0 dan di atas, di atas dapat ditambah dengan mencatat bahwa ada cara yang lebih baik: penggunaan ObjectWriterdan ObjectReaderobjek, yang dapat dibangun oleh ObjectMapper. Mereka sepenuhnya tidak dapat diubah, thread-safe, yang berarti bahwa bahkan secara teori tidak mungkin menyebabkan masalah keamanan thread (yang dapat terjadi dengan ObjectMapperjika kode mencoba mengkonfigurasi ulang instance).

StaxMan
sumber
23
@StaxMan: Saya sedikit khawatir jika ObjectMappermasih aman setelah ObjectMapper#setDateFormat()dipanggil. Diketahui bahwa SimpleDateFormatini tidak aman untuk thread , jadi ObjectMappertidak akan kecuali itu mengklon misalnya SerializationConfigsebelum masing-masing writeValue()(saya ragu). Bisakah Anda menghilangkan rasa takut saya?
dma_k
49
DateFormatmemang dikloning di bawah tenda. Kecurigaan bagus di sana, tetapi Anda dilindungi. :)
StaxMan
3
Saya menghadapi perilaku aneh selama pengujian unit / integrasi aplikasi perusahaan besar. Ketika menempatkan ObjectMapper sebagai atribut kelas akhir statis, saya mulai menghadapi masalah PermGen. Adakah yang mau menjelaskan kemungkinan penyebabnya? Saya menggunakan jackson-databind versi 2.4.1.
Alejo Ceballos
2
@MiklosKrivan, apakah Anda sudah melihatnya ObjectMapper? Metode diberi nama writer()dan reader()(dan beberapa readerFor(), writerFor()).
StaxMan
2
Tidak ada mapper.with()panggilan (karena "dengan" di Jackson menyiratkan pembangunan instance baru, dan eksekusi thread-safe). Tetapi mengenai perubahan konfigurasi: tidak ada pemeriksaan yang dilakukan, jadi akses konfigurasi ObjectMapperharus dijaga. Adapun "salin ()": ya, itu membuat salinan baru yang baru yang mungkin sepenuhnya (kembali) dikonfigurasi, sesuai dengan aturan yang sama: konfigurasikan sepenuhnya terlebih dahulu, kemudian gunakan, dan itu baik-baik saja. Ada biaya non-sepele yang terkait (karena salinan tidak dapat menggunakan penangan dalam cache apa pun), tetapi ini adalah cara yang aman, ya.
StaxMan
53

Meskipun ObjectMapper aman untuk di-thread, saya sangat tidak menyarankan untuk mendeklarasikannya sebagai variabel statis, terutama dalam aplikasi multithreaded. Bukan karena itu adalah praktik yang buruk, tetapi karena Anda menghadapi risiko besar mengalami kebuntuan. Saya menceritakannya dari pengalaman saya sendiri. Saya membuat aplikasi dengan 4 utas identik yang mendapatkan dan memproses data JSON dari layanan web. Aplikasi saya sering mengulur-ulur pada perintah berikut, menurut thread dump:

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

Selain itu, kinerjanya juga tidak bagus. Ketika saya mengganti variabel statis dengan variabel berbasis instance, mengulur-ulur menghilang dan kinerja empat kali lipat. Yaitu 2,4 juta dokumen JSON diproses dalam 40 menit.56sec., Bukan 2.5 jam sebelumnya.

Gary Greenberg
sumber
15
Jawaban Gary sepenuhnya masuk akal. Tetapi pergi dengan membuat ObjectMapperinstance untuk setiap instance kelas dapat mencegah kunci tetapi bisa sangat berat pada GC nanti (bayangkan satu instance ObjectMapper untuk setiap instance dari kelas yang Anda buat). Pendekatan jalur tengah dapat, alih-alih menyimpan hanya satu ObjectMapperinstance statis (publik) di seluruh aplikasi, Anda dapat mendeklarasikan instance statis (pribadi) ObjectMapper di setiap kelas . Ini akan mengurangi kunci global (dengan mendistribusikan load-bijaksana), dan tidak akan membuat objek baru juga, karenanya menyalakan GC juga.
Abhidemon
Dan tentu saja, mempertahankan an ObjectPool adalah cara terbaik yang dapat Anda lakukan - dengan demikian memberikan yang terbaik GCdan Lockpertunjukan. Anda dapat merujuk ke tautan berikut untuk ObjectPoolimplementasi apache-common . commons.apache.org/proper/commons-pool/api-1.6/org/apache/…
Abhidemon
16
Saya akan menyarankan alternatif: tetap statis di ObjectMappersuatu tempat, tetapi hanya mendapatkan ObjectReader/ ObjectWritercontoh (melalui metode pembantu), mempertahankan referensi ke yang ada di tempat lain (atau panggilan dinamis). Objek pembaca / penulis ini tidak hanya sepenuhnya konfigurasi ulang WRT yang aman, tetapi juga sangat ringan (contoh instance mapper). Jadi menjaga ribuan referensi tidak menambah banyak penggunaan memori.
StaxMan
Begitu juga panggilan ke instance ObjectReader tidak memblokir yaitu mengatakan objectReader.readTree dipanggil dalam aplikasi multithreaded, utas tidak akan diblokir menunggu di utas lain, menggunakan jackson 2.8.x
Xephonia
1

Meskipun aman untuk mendeklarasikan ObjectMapper statis dalam hal keamanan utas, Anda harus menyadari bahwa membangun variabel Objek statis di Jawa dianggap praktik buruk. Untuk detail lebih lanjut, lihat Mengapa variabel statis dianggap jahat? (dan jika Anda mau, jawabanku )

Singkatnya, statika harus dihindari karena menyulitkan untuk menulis tes unit ringkas. Misalnya, dengan ObjectMapper final statis, Anda tidak dapat menukar keluar serialisasi JSON untuk kode dummy atau larangan.

Selain itu, final statis mencegah Anda mengkonfigurasi ulang ObjectMapper saat runtime. Anda mungkin tidak membayangkan alasan untuk itu sekarang, tetapi jika Anda mengunci diri Anda sendiri ke dalam pola final statis, tidak akan merusak classloader yang akan membuat Anda menginisialisasi ulang itu.

Dalam kasus ObjectMapper baik-baik saja, tetapi secara umum itu adalah praktik yang buruk dan tidak ada keuntungan dibandingkan menggunakan pola tunggal atau inversi-of-control untuk mengelola objek berumur panjang Anda.

JBCP
sumber
27
Saya menyarankan bahwa meskipun lajang statis STATEFUL biasanya merupakan tanda bahaya, ada cukup alasan mengapa dalam kasus ini berbagi satu (atau, sejumlah kecil) contoh masuk akal. Seseorang mungkin ingin menggunakan Injeksi Ketergantungan untuk itu; tetapi pada saat yang sama perlu ditanyakan apakah ada masalah aktual atau potensial untuk dipecahkan. Ini terutama berlaku untuk pengujian: hanya karena sesuatu mungkin bermasalah dalam beberapa kasus tidak berarti itu untuk penggunaan Anda. Jadi: menyadari masalah, bagus. Dengan asumsi "satu ukuran cocok untuk semua", tidak begitu baik.
StaxMan
3
Jelas memahami masalah yang terlibat dengan keputusan desain adalah penting, dan jika Anda dapat melakukan sesuatu tanpa menyebabkan masalah untuk kasus penggunaan Anda, menurut definisi Anda, Anda tidak akan menyebabkan masalah. Namun saya berpendapat tidak ada manfaat untuk penggunaan contoh statis dan membuka pintu untuk masalah yang signifikan di masa depan karena kode Anda berkembang atau diserahkan kepada pengembang lain yang mungkin tidak memahami keputusan desain Anda. Jika kerangka kerja Anda mendukung alternatif, tidak ada alasan untuk tidak menghindari contoh statis, tentu saja tidak ada keuntungan bagi mereka.
JBCP
11
Saya pikir diskusi ini masuk ke garis singgung yang sangat umum dan kurang bermanfaat. Saya tidak punya masalah menyarankan bahwa itu baik untuk curiga terhadap lajang statis. Saya kebetulan sangat terbiasa dengan penggunaan untuk kasus khusus ini dan saya tidak berpikir seseorang dapat mencapai kesimpulan spesifik dari seperangkat pedoman umum. Jadi saya akan berhenti di situ.
StaxMan
1
Komentar terlambat, tetapi bukankah ObjectMapper khususnya tidak setuju dengan gagasan ini? Itu memperlihatkan readerFordan writerForyang membuat ObjectReaderdan ObjectWritercontoh pada permintaan. Jadi saya akan mengatakan meletakkan mapper dengan konfigurasi awal di suatu tempat statis, lalu dapatkan pembaca / penulis dengan konfigurasi per-kasus saat Anda membutuhkannya?
Carighan
1

Trik yang saya pelajari dari PR ini jika Anda tidak ingin mendefinisikannya sebagai variabel final statis tetapi ingin menyimpan sedikit overhead dan menjamin thread aman.

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

kredit kepada penulis.

Henry Lin
sumber
2
Tapi ada risiko kebocoran memori karena ObjectMapperakan melekat pada utas yang mungkin menjadi bagian dari kolam.
Kenston Choi
@ KenstonChoi Seharusnya tidak menjadi masalah, AFAIU. Utas datang dan pergi, utas lokal datang dan pergi dengan utas. Tergantung pada jumlah utas simultan Anda mungkin atau mungkin tidak mampu memori, tapi saya tidak melihat "kebocoran".
Ivan Balashov
2
@IvanBalashov, tetapi jika utas dibuat / dikembalikan dari / ke utas kumpulan (misalnya, wadah seperti Tomcat), ia tetap. Ini mungkin diinginkan dalam beberapa kasus, tetapi sesuatu yang perlu kita waspadai.
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)

Metode _hashMapSuperInterfaceChain di kelas com.fasterxml.jackson.databind.type.TypeFactory disinkronkan. Saya melihat pertengkaran pada saat yang sama pada beban tinggi.

Mungkin alasan lain untuk menghindari ObjectMapper statis

Harshit
sumber
1
Pastikan untuk memeriksa versi terbaru (dan mungkin menunjukkan versi yang Anda gunakan di sini). Ada perbaikan untuk penguncian berdasarkan masalah yang dilaporkan, dan jenis resolusi (f.ex) sepenuhnya ditulis ulang untuk Jackson 2.7. Meskipun dalam hal ini, TypeReferenceadalah hal yang agak mahal untuk digunakan: jika mungkin, menyelesaikannya untuk JavaTypemenghindari sedikit pemrosesan ( TypeReferences tidak dapat - sayangnya - di-cache karena alasan bahwa saya tidak akan menggali ke sini), karena mereka "sepenuhnya teratasi" (super-type, generic typing dll).
StaxMan