Tôi có nên khai báo ObjectMapper của Jackson là trường tĩnh không?


361

ObjectMapperLớp học của thư viện Jackson dường như là chủ đề an toàn .

Điều này có nghĩa là tôi nên khai báo tôi ObjectMapperlà một trường tĩnh như thế này

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

thay vì như một trường cấp thể hiện như thế này?

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

Câu trả lời:


505

Vâng, đó là an toàn và khuyến khích.

Nhắc nhở duy nhất từ ​​trang bạn đã giới thiệu là bạn không thể sửa đổi cấu hình của trình ánh xạ một khi nó được chia sẻ; nhưng bạn không thay đổi cấu hình như vậy là tốt. Nếu bạn cần thay đổi cấu hình, bạn sẽ làm điều đó từ khối tĩnh và nó cũng sẽ ổn.

EDIT : (2013/10)

Với 2.0 trở lên, ở trên có thể được tăng cường bằng cách lưu ý rằng có một cách thậm chí còn tốt hơn: sử dụng ObjectWriterObjectReadercác đối tượng, có thể được xây dựng bằng ObjectMapper. Chúng hoàn toàn không thay đổi, an toàn luồng, có nghĩa là về mặt lý thuyết không thể gây ra sự cố an toàn luồng (có thể xảy ra ObjectMappernếu mã cố gắng định cấu hình lại cá thể).


23
@StaxMan: Tôi hơi lo ngại nếu ObjectMappervẫn an toàn sau khi ObjectMapper#setDateFormat()được gọi. Nó được biết rằng SimpleDateFormatkhông phải là chủ đề an toàn , do đó ObjectMappersẽ không được trừ khi nó nhân bản, ví dụ SerializationConfigtrước mỗi writeValue()(tôi nghi ngờ). Bạn có thể làm giảm nỗi sợ hãi của tôi?
dma_k

49
DateFormatthực sự được nhân bản dưới mui xe. Nghi ngờ tốt ở đó, nhưng bạn được bảo hiểm. :)
StaxMan

3
Tôi đã phải đối mặt với các hành vi lạ trong các bài kiểm tra đơn vị / tích hợp của một ứng dụng doanh nghiệp lớn. Khi đặt ObjectMapper làm thuộc tính lớp cuối cùng tĩnh, tôi bắt đầu đối mặt với các vấn đề PermGen. Bất cứ ai sẽ quan tâm để giải thích nguyên nhân có thể xảy ra? Tôi đã sử dụng jackson-databind phiên bản 2.4.1.
Alejo Ceballos

2
@MiklosKrivan bạn đã xem xét chưa ObjectMapper?! Các phương thức được đặt tên writer()reader()(và một số readerFor(), writerFor()).
StaxMan

2
Không có mapper.with()cuộc gọi nào (vì "với" trong Jackson ngụ ý xây dựng một thể hiện mới và thực thi an toàn luồng). Nhưng liên quan đến thay đổi cấu hình: không có kiểm tra nào được thực hiện, do đó, quyền truy cập cấu hình ObjectMapperphải được bảo vệ. Đối với "copy ()": có, điều đó tạo ra một bản sao mới có thể được cấu hình đầy đủ (theo), theo cùng một quy tắc: cấu hình đầy đủ trước, sau đó sử dụng, và điều đó là tốt. Có chi phí không hề nhỏ liên quan (vì bản sao không thể sử dụng bất kỳ trình xử lý được lưu trong bộ nhớ cache nào), nhưng đó là cách an toàn, vâng.
StaxMan

53

Mặc dù ObjectMapper là luồng an toàn, tôi không khuyến khích mạnh mẽ tuyên bố nó là biến tĩnh, đặc biệt là trong ứng dụng đa luồng. Thậm chí không phải vì nó là một thực hành tồi, mà bởi vì bạn đang có nguy cơ bế tắc nặng nề. Tôi đang nói với nó từ kinh nghiệm của riêng tôi. Tôi đã tạo một ứng dụng với 4 luồng giống hệt nhau để nhận và xử lý dữ liệu JSON từ các dịch vụ web. Ứng dụng của tôi thường xuyên bị đình trệ trong lệnh sau, theo kết xuất luồng:

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

Bên cạnh đó, hiệu suất không tốt. Khi tôi thay thế biến tĩnh bằng biến dựa trên thể hiện, sự ngưng trệ biến mất và hiệu suất tăng gấp bốn lần. Tức là 2,4 triệu tài liệu JSON đã được xử lý trong 40 phút.56 giây, thay vì 2,5 giờ trước đó.


15
Câu trả lời của Gary hoàn toàn có ý nghĩa. Nhưng với việc tạo một ObjectMappercá thể tạo cho mọi cá thể lớp có thể ngăn chặn các khóa nhưng có thể thực sự nặng nề đối với GC sau này (hãy tưởng tượng một cá thể ObjectMapper cho mọi phiên bản của lớp bạn tạo). Một cách tiếp cận đường giữa có thể, thay vì chỉ giữ một ObjectMapperthể hiện tĩnh (công khai) trên ứng dụng, bạn có thể khai báo một thể hiện tĩnh (riêng tư) ObjectMapper trong mỗi lớp . Điều này sẽ làm giảm khóa toàn cầu (bằng cách phân phối lớp tải thông minh) và sẽ không tạo ra bất kỳ đối tượng mới nào, do đó cũng sáng lên trên GC.
Abhidemon

Và dĩ nhiên, việc duy trì một ObjectPool là cách tốt nhất mà bạn có thể đi với - do đó cho là tốt nhất GCLockbiểu diễn. Bạn có thể tham khảo liên kết sau để ObjectPoolthực hiện apache-common . commons.apache.org/proper/commons-pool/api-1.6/org/apache/ory
Abhidemon

16
Tôi sẽ đề xuất một giải pháp thay thế: giữ tĩnh ObjectMapperở đâu đó, nhưng chỉ nhận ObjectReader/ ObjectWritertrường hợp (thông qua các phương thức của trình trợ giúp), giữ lại các tham chiếu đến những nơi khác (hoặc gọi động). Các đối tượng người đọc / người viết này không chỉ cấu hình lại wrt an toàn chủ đề hoàn toàn, mà còn rất nhẹ (các trường hợp ánh xạ wrt). Vì vậy, việc giữ hàng ngàn tài liệu tham khảo không thêm nhiều bộ nhớ sử dụng.
StaxMan

Vì vậy, hãy gọi các đối tượng ObjectReader không chặn, ví dụ như objectReader.readTree được gọi trong ứng dụng đa luồng, các luồng sẽ không bị chặn chờ trên một luồng khác, sử dụng jackson 2.8.x
Xephonia

1

Mặc dù an toàn khi khai báo ObjectMapper tĩnh về mặt an toàn của luồng, bạn nên lưu ý rằng việc xây dựng các biến Object tĩnh trong Java được coi là thực tiễn xấu. Để biết thêm chi tiết, xem Tại sao các biến tĩnh được coi là xấu? (và nếu bạn thích, câu trả lời của tôi )

Nói tóm lại, nên tránh các số liệu thống kê vì khó viết bài kiểm tra đơn vị ngắn gọn. Ví dụ, với ObjectMapper tĩnh cuối cùng, bạn không thể trao đổi tuần tự hóa JSON cho mã giả hoặc không có op.

Ngoài ra, một trận chung kết tĩnh ngăn bạn không bao giờ cấu hình lại ObjectMapper khi chạy. Bây giờ bạn có thể không hình dung ra một lý do cho điều đó, nhưng nếu bạn tự khóa mình vào một mẫu cuối cùng tĩnh, không có gì xé nát trình nạp lớp sẽ cho phép bạn khởi tạo lại nó.

Trong trường hợp ObjectMapper thì tốt, nhưng nói chung, đó là một thực tiễn tồi và không có lợi thế nào khi sử dụng một mẫu đơn hoặc điều khiển đảo ngược để quản lý các đối tượng tồn tại lâu dài của bạn.


27
Tôi sẽ đề nghị rằng mặc dù các singleton STATEFUL tĩnh thường là một dấu hiệu nguy hiểm, nhưng có đủ lý do tại sao trong trường hợp này, việc chia sẻ một (hoặc một số lượng nhỏ) trường hợp có ý nghĩa. Người ta có thể muốn sử dụng Dependency Injection cho điều đó; nhưng đồng thời cũng đáng để hỏi liệu có vấn đề thực tế hay tiềm năng nào cần giải quyết hay không. Điều này đặc biệt áp dụng cho thử nghiệm: chỉ vì một số thứ có thể có vấn đề trong một số trường hợp không có nghĩa là nó dành cho việc sử dụng của bạn. Vì vậy: nhận thức được vấn đề, tuyệt vời. Giả sử "một kích thước phù hợp với tất cả", không tốt lắm.
StaxMan

3
Rõ ràng hiểu được các vấn đề liên quan đến bất kỳ quyết định thiết kế nào là quan trọng và nếu bạn có thể làm gì đó mà không gây ra sự cố cho trường hợp sử dụng của bạn, theo định nghĩa, bạn sẽ không gây ra bất kỳ vấn đề nào. Tuy nhiên, tôi cho rằng sẽ không có lợi ích gì khi sử dụng các thể hiện tĩnh và nó sẽ mở ra những rắc rối đáng kể trong tương lai khi mã của bạn phát triển hoặc được trao cho các nhà phát triển khác có thể không hiểu các quyết định thiết kế của bạn. Nếu khung của bạn hỗ trợ các lựa chọn thay thế, không có lý do gì để không tránh các trường hợp tĩnh, chắc chắn không có lợi thế nào cho chúng.
JBCP

11
Tôi nghĩ rằng cuộc thảo luận này đi vào các tiếp tuyến rất chung chung và ít hữu ích. Tôi không có vấn đề gì khi đề xuất rằng thật tốt khi nghi ngờ các singletons tĩnh. Tôi tình cờ rất quen thuộc với việc sử dụng cho trường hợp cụ thể này và tôi không nghĩ người ta có thể đưa ra kết luận cụ thể từ bộ hướng dẫn chung. Vì vậy, tôi sẽ để nó ở đó.
StaxMan

1
Nhận xét muộn, nhưng ObjectMapper không đặc biệt không đồng ý với khái niệm này? Nó phơi bày readerForwriterFortạo ra ObjectReadervà các ObjectWritertrường hợp theo yêu cầu. Vì vậy, tôi muốn đặt trình ánh xạ với cấu hình ban đầu ở một nơi tĩnh, sau đó lấy độc giả / người viết với cấu hình theo từng trường hợp khi bạn cần chúng?
Carighan

1

Một mẹo tôi đã học được từ PR này nếu bạn không muốn định nghĩa nó là biến cuối cùng tĩnh nhưng muốn tiết kiệm một chút chi phí và đảm bảo an toàn cho chuỗi.

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

tín dụng cho tác giả.


2
Nhưng có nguy cơ rò rỉ bộ nhớ vì ObjectMappernó sẽ được gắn vào luồng có thể là một phần của nhóm.
Kenston Choi

@KenstonChoi Không nên là một vấn đề, AFAIU. Các chủ đề đến và đi, chủ đề địa phương đến và đi với các chủ đề. Tùy thuộc vào số lượng luồng đồng thời bạn có thể có hoặc không đủ khả năng cho bộ nhớ, nhưng tôi không thấy "rò rỉ".
Ivan Balashov

2
@IvanBalashov, nhưng nếu luồng được tạo / trả về từ / đến nhóm luồng (ví dụ: các thùng chứa như Tomcat), nó sẽ ở lại. Điều này có thể được mong muốn trong một số trường hợp, nhưng một cái gì đó chúng ta cần phải nhận thức được.
Kenston Choi

-1

com.fasterxml.jackson.databind.type.TypeFactory._hashMapSuperInterfaceChain (HVELicType)

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)

Phương thức _hashMapSuperInterfaceChain trong lớp com.fasterxml.jackson.databind.type.TypeFactory được đồng bộ hóa. Đang thấy sự tranh chấp trên cùng ở tải cao.

Có thể là một lý do khác để tránh ObjectMapper tĩnh


1
Hãy chắc chắn kiểm tra các phiên bản mới nhất (và có lẽ chỉ ra phiên bản bạn sử dụng ở đây). Đã có những cải tiến để khóa dựa trên các vấn đề được báo cáo và độ phân giải loại (f.ex) đã được viết lại hoàn toàn cho Jackson 2.7. Mặc dù trong trường hợp này, TypeReferenceviệc sử dụng hơi tốn kém: nếu có thể, việc giải quyết nó JavaTypesẽ tránh được một chút xử lý ( TypeReferencekhông thể - không may - được lưu trong bộ nhớ cache vì những lý do mà tôi sẽ không đào sâu vào đây), vì chúng là "giải quyết đầy đủ" (siêu kiểu, gõ chung, v.v.).
StaxMan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.