ฉันควรประกาศ ObjectMapper ของ Jackson เป็นฟิลด์คงที่หรือไม่


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พวกมันไม่สามารถเปลี่ยนแปลงได้อย่างสมบูรณ์ thread-safe ซึ่งหมายความว่ามันเป็นไปไม่ได้ในทางทฤษฎีที่จะทำให้เกิดปัญหาความปลอดภัยของ thread (ซึ่งอาจเกิดขึ้นได้ObjectMapperหากรหัสพยายามตั้งค่าอินสแตนซ์ใหม่)


23
@StaxMan: ฉันเป็นกังวลเล็กน้อยถ้าObjectMapperยังคงปลอดภัยกระทู้หลังจากที่ObjectMapper#setDateFormat()ถูกเรียก เป็นที่ทราบกันดีว่าSimpleDateFormatไม่ปลอดภัยสำหรับเธรดดังนั้นObjectMapperจะไม่เป็นเช่นนั้นหากไม่ได้ทำการโคลนนิ่งเช่นSerializationConfigกันก่อนwriteValue()(ฉันสงสัย) คุณหักล้างความกลัวของฉันได้ไหม
dma_k

49
DateFormatถูกโคลนอย่างแท้จริงภายใต้ประทุน มีความสงสัยที่ดี แต่คุณได้รับความคุ้มครอง :)
StaxMan

3
ฉันพบพฤติกรรมแปลก ๆ ระหว่างการทดสอบหน่วย / การรวมระบบของแอปพลิเคชันองค์กรขนาดใหญ่ เมื่อวาง ObjectMapper เป็นแอตทริบิวต์คลาสสุดท้ายคงที่ฉันเริ่มประสบปัญหา PermGen ใครบ้างที่จะอธิบายสาเหตุที่เป็นไปได้ ฉันใช้ jackson-databind เวอร์ชัน 2.4.1
Alejo Ceballos

2
@ MiklosKrivan คุณเคยดูบ้างไหมObjectMapper?! วิธีการตั้งชื่อwriter()และreader()(และบางreaderFor(), writerFor())
StaxMan

2
ไม่มีการmapper.with()โทร (ตั้งแต่ "กับ" ในแจ็คสันแสดงถึงการสร้างอินสแตนซ์ใหม่และการดำเนินการที่ปลอดภัยสำหรับเธรด) แต่เกี่ยวกับการเปลี่ยนแปลงการกำหนดค่า: ไม่ทำการตรวจสอบดังนั้นการเข้าถึงการกำหนดค่าObjectMapperจะต้องได้รับการปกป้อง ในฐานะที่เป็น "copy ()": ใช่ว่าจะสร้างสำเนาใหม่ที่อาจได้รับการกำหนดค่าอย่างสมบูรณ์ (อีกครั้ง) ตามกฎเดียวกัน: กำหนดค่าทั้งหมดก่อนจากนั้นใช้และนั่นก็เป็นเรื่องปกติ มีค่าใช้จ่ายที่ไม่สำคัญ (เนื่องจากการคัดลอกไม่สามารถใช้ตัวจัดการแคชใด ๆ ได้) แต่เป็นวิธีที่ปลอดภัยใช่
StaxMan

53

แม้ว่า ObjectMapper จะปลอดภัยต่อเธรด แต่ฉันขอแนะนำอย่างยิ่งให้ประกาศเป็นตัวแปรคงที่โดยเฉพาะอย่างยิ่งในแอปพลิเคชันแบบมัลติเธรด ไม่ได้เป็นเพราะมันเป็นวิธีปฏิบัติที่ไม่ดี แต่เป็นเพราะคุณกำลังเสี่ยงต่อการเกิด deadlocking อย่างหนัก ฉันกำลังบอกจากประสบการณ์ของฉันเอง ฉันสร้างแอปพลิเคชันที่มี 4 เธรดที่เหมือนกันซึ่งกำลังรับและประมวลผลข้อมูล JSON จากบริการเว็บ แอปพลิเคชันของฉันถูกถ่วงบ่อยในคำสั่งต่อไปนี้ตามการถ่ายโอนข้อมูลเธรด:

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

นอกจากนั้นประสิทธิภาพยังไม่ดี เมื่อฉันแทนที่ตัวแปรสแตติกด้วยตัวแปรตามอินสแตนซ์การหยุดกลางคันหายไปและเพิ่มประสิทธิภาพเป็นสี่เท่า มีการประมวลผลเอกสาร JSON 2.4 ล้านฉบับใน 40 นาที 55 วินาทีหรือ 2.5 ชั่วโมงก่อนหน้านี้


15
คำตอบของ Gary สมบูรณ์สมเหตุสมผล แต่การสร้างObjectMapperอินสแตนซ์สำหรับทุกคลาสอินสแตนซ์อาจป้องกันการล็อก แต่อาจหนักมากใน GC ในภายหลัง (ลองนึกภาพ ObjectMapper หนึ่งอินสแตนซ์สำหรับทุกอินสแตนซ์ของคลาสที่คุณสร้าง) วิธีการทางสายกลางสามารถแทนการรักษาเพียงหนึ่ง (สาธารณะ) แบบคงที่ObjectMapperเช่นข้ามโปรแกรมที่คุณสามารถประกาศ (ส่วนตัว) คงที่ตัวอย่างของในทุกระดับObjectMapper สิ่งนี้จะลดล็อกส่วนกลาง (โดยกระจายโหลดคลาสที่ชาญฉลาด) และจะไม่สร้างวัตถุใหม่ใด ๆ เช่นกันดังนั้นจึงให้แสงกับ GC ด้วย
Abhidemon

และแน่นอนว่าการรักษา ObjectPool คือวิธีที่ดีที่สุดที่คุณสามารถไปได้ด้วย - ดังนั้นให้สิ่งที่ดีที่สุดGCและLockการแสดง คุณสามารถอ้างถึงลิงค์ต่อไปนี้สำหรับObjectPoolการใช้งาน ของ apache-common commons.apache.org/proper/commons-pool/api-1.6/org/apache/…
Abhidemon

16
ฉันขอแนะนำทางเลือกอื่น: รักษาความคงที่ObjectMapperอยู่ที่ไหนสักแห่ง แต่รับObjectReader/ ObjectWriterอินสแตนซ์เท่านั้น(ผ่านวิธีใช้ตัวช่วย) เก็บการอ้างอิงไปยังที่อื่น ๆ (หรือโทรแบบไดนามิก) วัตถุตัวอ่าน / ตัวเขียนเหล่านี้ไม่เพียง แต่กำหนดค่า wrt-safe แบบเต็มเธรดอีกครั้ง แต่ยังมีน้ำหนักเบามาก (อินสแตนซ์ wrt mapper) ดังนั้นการเก็บการอ้างอิงหลายพันรายการจึงไม่เพิ่มการใช้หน่วยความจำมากนัก
StaxMan

ดังนั้นการเรียกใช้งานอินสแตนซ์ ObjectReader ที่ไม่บล็อกคือว่า objectReader.readTree ถูกเรียกใช้ในแอปพลิเคชันแบบมัลติเธรดเธรดจะไม่ถูกบล็อกรออยู่ในเธรดอื่นโดยใช้แจ็คสัน 2.8.x
Xephonia

1

แม้ว่าจะปลอดภัยที่จะประกาศ ObjectMapper แบบสแตติกในแง่ของความปลอดภัยของเธรดคุณควรทราบว่าการสร้างตัวแปร Object แบบคงที่ใน Java นั้นถือว่าเป็นแนวปฏิบัติที่ไม่ดี สำหรับรายละเอียดเพิ่มเติมให้ดูที่ทำไมตัวแปรแบบคงที่จึงถือว่าเป็นความชั่ว (และหากคุณต้องการคำตอบของฉัน )

ในระยะสั้นควรหลีกเลี่ยงสถิตยศาสตร์เพราะทำให้ยากต่อการเขียนการทดสอบหน่วยรัดกุม ตัวอย่างเช่นด้วย ObjectMapper ขั้นสุดท้ายแบบคงที่คุณไม่สามารถสลับการจัดลำดับ JSON สำหรับรหัสจำลองหรือไม่มีตัวเลือก

นอกจากนี้สแตติกสุดท้ายจะป้องกันคุณจากการกำหนดค่า ObjectMapper ใหม่ขณะรันไทม์ คุณอาจไม่เห็นเหตุผลในตอนนี้ แต่ถ้าคุณล็อคตัวเองในรูปแบบสุดท้ายคงไม่มีอะไรขาดการแยก classloader จะช่วยให้คุณเริ่มต้นใหม่ได้

ในกรณีของ ObjectMapper มันดี แต่โดยทั่วไปแล้วมันเป็นแนวปฏิบัติที่ไม่ดีและไม่มีข้อได้เปรียบในการใช้รูปแบบซิงเกิลหรือแบบผกผันของการควบคุมเพื่อจัดการวัตถุที่มีอายุการใช้งานยาวนาน


27
ฉันขอแนะนำว่าแม้ว่าแบบซิงเกิล STATEFUL แบบคงที่มักเป็นสัญญาณอันตราย แต่ก็มีเหตุผลเพียงพอที่ในกรณีนี้การแบ่งปันอินสแตนซ์เดียว (หรือจำนวนน้อย) ก็สมเหตุสมผล หนึ่งอาจต้องการใช้การฉีดพึ่งพาสำหรับที่; แต่ในเวลาเดียวกันมันก็คุ้มค่าที่จะถามว่ามีปัญหาจริงหรือที่อาจเกิดขึ้นเพื่อแก้ไข โดยเฉพาะอย่างยิ่งนี้ใช้กับการทดสอบ: เพียงเพราะสิ่งที่อาจเป็นปัญหาในบางกรณีไม่ได้หมายความว่ามันมีไว้สำหรับการใช้งานของคุณ ดังนั้น: ระวังปัญหาดีมาก สมมติว่า "หนึ่งขนาดเหมาะกับทุกคน" ไม่ดีนัก
StaxMan

3
การเข้าใจปัญหาที่เกี่ยวข้องกับการตัดสินใจออกแบบเป็นสิ่งสำคัญและถ้าคุณสามารถทำอะไรได้โดยไม่ทำให้เกิดปัญหาสำหรับการใช้งานของคุณโดยนิยามคุณจะไม่ทำให้เกิดปัญหาใด ๆ อย่างไรก็ตามฉันจะโต้แย้งว่าไม่มีประโยชน์ในการใช้งานอินสแตนซ์แบบคงที่และจะเปิดประตูสู่ปัญหาที่สำคัญในอนาคตเป็นรหัสของคุณวิวัฒนาการหรือมอบให้กับนักพัฒนาอื่น ๆ ที่อาจไม่เข้าใจการตัดสินใจการออกแบบของคุณ หากกรอบงานของคุณรองรับทางเลือกไม่มีเหตุผลที่จะไม่หลีกเลี่ยงอินสแตนซ์แบบคงที่ไม่มีข้อได้เปรียบสำหรับพวกเขาแน่นอน
JBCP

11
ฉันคิดว่าการสนทนานี้เป็นการอภิปรายทั่วไปและมีประโยชน์น้อยกว่า ฉันไม่มีปัญหาที่จะบอกว่ามันเป็นเรื่องที่ดีที่จะสงสัยว่ามีซิงเกิลนิ่ง ๆ ฉันเพิ่งจะคุ้นเคยกับการใช้งานสำหรับกรณีนี้และฉันไม่คิดว่าใครจะสามารถสรุปได้จากชุดหลักเกณฑ์ทั่วไป ดังนั้นฉันจะทิ้งมันไว้
StaxMan

1
ความคิดเห็นที่ล่าช้า แต่ ObjectMapper จะไม่เห็นด้วยกับแนวคิดนี้เป็นพิเศษหรือไม่ มันเปิดเผยreaderForและwriterForที่สร้างObjectReaderและObjectWriterอินสแตนซ์ตามความต้องการ ดังนั้นฉันจะบอกว่าใส่ mapper ด้วยการกำหนดค่าเริ่มต้นที่ไหนสักแห่งที่คงที่จากนั้นรับผู้อ่าน / นักเขียนด้วยการกำหนดค่าต่อกรณีตามที่คุณต้องการ?
Carighan

1

เคล็ดลับที่ฉันเรียนรู้จากการประชาสัมพันธ์นี้ถ้าคุณไม่ต้องการกำหนดให้เป็นตัวแปรสุดท้าย แต่ต้องการประหยัดค่าใช้จ่ายเล็กน้อยและรับประกันความปลอดภัยของเธรด

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จะแนบไปกับเธรดซึ่งอาจเป็นส่วนหนึ่งของกลุ่ม
Kenston Choi

@KenstonChoi ไม่ควรมีปัญหา AFAIU เธรดมาและไป, ไอเดียเธรดมาและไปกับเธรด ขึ้นอยู่กับจำนวนของเธรดพร้อมกันคุณอาจหรือไม่จ่ายหน่วยความจำ แต่ฉันไม่เห็น "รั่ว"
Ivan Balashov

2
@IvanBalashov แต่ถ้าเธรดถูกสร้าง / ส่งคืนจาก / ไปยังเธรดพูล (เช่นคอนเทนเนอร์เช่น Tomcat) จะยังคงอยู่ นี่อาจเป็นที่ต้องการในบางกรณี แต่สิ่งที่เราต้องระวัง
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)

เมธอด _hashMapSuperInterfaceChain ในคลาสcom.fasterxml.jackson.databind.type.TypeFactoryถูกซิงโครไนซ์ ฉันเห็นการต่อสู้ในที่โหลดสูง

อาจเป็นอีกสาเหตุหนึ่งที่จะหลีกเลี่ยง ObjectMapper แบบคงที่


1
อย่าลืมตรวจสอบเวอร์ชั่นล่าสุด (และอาจระบุรุ่นที่คุณใช้ที่นี่) มีการปรับปรุงการล็อกตามปัญหาที่รายงานและการจำแนกประเภท (f.ex) ถูกเขียนใหม่ทั้งหมดสำหรับ Jackson 2.7 แม้ว่าในกรณีนี้TypeReferenceเป็นสิ่งที่มีราคาแพงที่จะใช้: ถ้าเป็นไปได้การแก้ไขเพื่อJavaTypeหลีกเลี่ยงการประมวลผลค่อนข้างน้อย ( TypeReferenceไม่สามารถ - น่าเสียดาย - แคชด้วยเหตุผลที่ฉันจะไม่ขุดลงที่นี่) เนื่องจากพวกเขา คือ "แก้ไขอย่างเต็มที่" (ชนิดพิเศษ, การพิมพ์ทั่วไปและอื่น ๆ )
StaxMan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.