2009-03-06 34 views
39

Tôi đang sử dụng JAXB để đọc và viết XML. Những gì tôi muốn là sử dụng một lớp JAXB cơ sở cho marshalling và một lớp JAXB được thừa hưởng cho unmarshalling. Điều này là để cho phép một ứng dụng Java của người gửi gửi XML tới một ứng dụng Java nhận khác. Người gửi và người nhận sẽ chia sẻ một thư viện JAXB chung. Tôi muốn người nhận unmarshall XML vào một lớp JAXB người nhận cụ thể mở rộng lớp JAXB chung.Thừa kế JAXB, không đối sánh với lớp con của lớp được sắp xếp thứ tự

Ví dụ:

Đây là lớp JAXB thường được người gửi sử dụng.

@XmlRootElement(name="person") 
public class Person { 
    public String name; 
    public int age; 
} 

Đây là lớp JAXB cụ thể của người nhận được sử dụng khi unmarshalling XML. Lớp người nhận có logic cụ thể cho ứng dụng người nhận.

@XmlRootElement(name="person") 
public class ReceiverPerson extends Person { 
    public doReceiverSpecificStuff() ... 
} 

Thao tác Marshall hoạt động như mong đợi. Vấn đề là với unmarshalling, nó vẫn unmarshals để Person bất chấp JAXBContext sử dụng tên gói của subclassed ReceiverPerson.

JAXBContext jaxbContext = JAXBContext.newInstance(package name of ReceiverPerson); 

Điều tôi muốn là unmarshall thành ReceiverPerson. Cách duy nhất tôi có thể thực hiện việc này là xóa @XmlRootElement từ Person. Thật không may làm điều này ngăn không cho Person bị trùng hợp. Nó giống như JAXB bắt đầu ở lớp cơ sở và làm việc theo cách của nó xuống cho đến khi nó tìm thấy @XmlRootElement đầu tiên với tên thích hợp. Tôi đã thử thêm phương thức createPerson() trả về ReceiverPerson đến ObjectFactory nhưng không hiệu quả.

Trả lời

19

Bạn đang sử dụng JAXB 2.0 phải không? (Kể từ JDK6)

Có một lớp:

javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType> 

mà người ta có thể phân lớp, và ghi đè phương pháp sau đây:

public abstract BoundType unmarshal(ValueType v) throws Exception; 
public abstract ValueType marshal(BoundType v) throws Exception; 

Ví dụ:

public class YourNiceAdapter 
     extends XmlAdapter<ReceiverPerson,Person>{ 

    @Override public Person unmarshal(ReceiverPerson v){ 
     return v; 
    } 
    @Override public ReceiverPerson marshal(Person v){ 
     return new ReceiverPerson(v); // you must provide such c-tor 
    } 
} 

Cách sử dụng được thực hiện bằng như sau:

@Your_favorite_JAXB_Annotations_Go_Here 
class SomeClass{ 
    @XmlJavaTypeAdapter(YourNiceAdapter.class) 
    Person hello; // field to unmarshal 
} 

Tôi khá chắc chắn, bằng cách sử dụng khái niệm này, bạn có thể kiểm soát quá trình marshalling/unmarshalling của chính bạn (bao gồm cả lựa chọn đúng [sub | super] type để xây dựng).

+0

Tôi đã thử điều này và nó không hoạt động. Không có vấn đề gì tôi cố gắng, ObjectFactory hoặc XmlAdapter của tôi không bao giờ được gọi. Từ những gì tôi đã đọc JAXB của Sun giải quyết thông qua các tham chiếu lớp tĩnh. Có vẻ như việc triển khai Glassfish có nhiều hứa hẹn hơn https://jaxb.dev.java.net/guide/Adding_behaviors.html –

+0

XmlJavaAdapters phù hợp để làm cho các lớp không được chú thích JAXB có thể sử dụng được với JAXB. Bởi vì Person và ReceiverPerson (và/hoặc superlass của họ, nếu có) có chú thích JAXB, nó không hoạt động. Bạn cần Người và Người nhận không có JAXB, cộng với bộ điều hợp cho cả hai. –

+0

Bạn đã thử đổi kiểu cho marshall | unmarshall? [...] mở rộng XmlAdapter [...] @Override công khai Người nhận không trùng hợp (Người v) { trả lại Người nhận mới (v); } @Override người công khai soái (Người nhận v) { trả lại v; } –

3

Tôi không chắc tại sao bạn lại muốn làm điều này ... dường như không an toàn đối với tôi.

Hãy xem xét điều gì sẽ xảy ra trong ReceiverPerson có các biến mẫu bổ sung ... sau đó bạn sẽ kết thúc (tôi đoán) các biến đó là null, 0 hoặc false ... và nếu null không được phép hoặc số phải lớn hơn 0?

Tôi nghĩ rằng những gì bạn có thể muốn làm là đọc trong Person và sau đó xây dựng một người nhận mới từ đó (có thể cung cấp một nhà xây dựng mà có một người).

+0

Bộ thu không có biến số bổ sung. Mục đích của nó là để làm công cụ thu cụ thể. –

+0

Bạn vẫn sẽ phải, ở một mức độ nào đó, tạo ra nó như là một lớp khác ... và cách làm thông thường là thông qua hàm tạo có kiểu bạn muốn chuyển đổi. Ngoài ra, chỉ vì đó là trường hợp bây giờ không có nghĩa là bạn sẽ không thêm vars trong tương lai. – TofuBeer

+2

Yikes, đó là cách để suy đoán nhiều về những gì một lớp học có thể làm trong tương lai. Nếu nó đủ tốt cho ngày hôm nay thì nó đủ tốt, và bạn luôn có thể cấu trúc lại sau này ... –

-1

Vì bạn thực sự có hai ứng dụng riêng biệt, hãy biên dịch chúng bằng các phiên bản khác nhau của lớp "Người" - với ứng dụng người nhận không có @XmlRootElement(name="person") trên Person. Không chỉ xấu xí này, mà còn đánh bại khả năng bảo trì mà bạn muốn sử dụng cùng một định nghĩa về Cá nhân cho cả người gửi và người nhận. Một tính năng mua lại của nó là nó hoạt động.

+0

Tôi muốn cả hai ứng dụng chia sẻ các lớp JAXB cơ sở chung. –

+0

@Steve: nhưng giải pháp thứ hai ("gọn gàng") của tôi chia sẻ các lớp JAXB cơ sở chung ... có vẻ như bạn chỉ đọc đoạn đầu tiên và tôi không rõ là tôi đã đưa ra hai giải pháp. Tôi sẽ chỉnh sửa. – 13ren

+0

Tôi đã tách riêng "cách gọn gàng" thành câu trả lời mới. – 13ren

17

Đoạn sau đây là một phương pháp xét nghiệm Junit 4 với một ánh sáng màu xanh lá cây:

@Test 
public void testUnmarshallFromParentToChild() throws JAXBException { 
    Person person = new Person(); 
    int age = 30; 
    String name = "Foo"; 
    person.name = name; 
    person.age= age; 

    // Marshalling 
    JAXBContext context = JAXBContext.newInstance(person.getClass()); 
    Marshaller marshaller = context.createMarshaller(); 

    StringWriter writer = new StringWriter(); 
    marshaller.marshal(person, writer); 

    String outString = writer.toString(); 

    assertTrue(outString.contains("</person")); 

    // Unmarshalling 
    context = JAXBContext.newInstance(Person.class, RecieverPerson.class); 
    Unmarshaller unmarshaller = context.createUnmarshaller(); 
    StringReader reader = new StringReader(outString); 
    RecieverPerson reciever = (RecieverPerson)unmarshaller.unmarshal(reader); 

    assertEquals(name, reciever.name); 
    assertEquals(age, reciever.age); 
} 

Phần quan trọng là việc sử dụng các phương pháp JAXBContext.newInstance(Class... classesToBeBound) cho bối cảnh unmarshalling:

context = JAXBContext.newInstance(Person.class, RecieverPerson.class); 

Với cuộc gọi này, JAXB sẽ tính toán điểm đóng tham chiếu trên các lớp được chỉ định và sẽ nhận ra RecieverPerson. Bài kiểm tra đã qua. Và nếu bạn thay đổi thứ tự tham số, bạn sẽ nhận được java.lang.ClassCastException (vì vậy họ phải được chuyển theo thứ tự này).

12

Lớp con Người hai lần, một lần cho người nhận và một lần cho người gửi, và chỉ đặt XmlRootElement trên các lớp con này (để lại siêu lớp, Person, không có XmlRootElement). Lưu ý rằng người gửi và người nhận đều chia sẻ cùng một lớp cơ sở JAXB.

@XmlRootElement(name="person") 
public class ReceiverPerson extends Person { 
    // receiver specific code 
} 

@XmlRootElement(name="person") 
public class SenderPerson extends Person { 
    // sender specific code (if any) 
} 

// note: no @XmlRootElement here 
public class Person { 
    // data model + jaxb annotations here 
} 

[đã kiểm tra và xác nhận để làm việc với JAXB]. Nó phá vỡ vấn đề bạn lưu ý, khi nhiều lớp trong hệ thống phân cấp thừa kế có chú thích XmlRootElement.

Điều này cũng được cho là một cách tiếp cận nhiều hơn và nhiều hơn nữa, bởi vì nó tách ra mô hình dữ liệu chung, do đó, nó không phải là một "workaround" ở tất cả.

+0

Có vẻ như giải pháp tốt nhất nhưng không hiệu quả đối với tôi, có lẽ vì trường hợp của tôi hơi phức tạp. Tôi cần phải chuyển đổi các cá thể lớp của lớp 'Nhóm' có chứa cả hai danh sách của SenderPerson và ReceiverPerson. giống như vậy @XmlRootElement Nhóm công khai { Danh sách công khai người gửi; Danh sách công khai người nhận; } –

+0

Tôi có thể xác nhận rằng giải pháp này cũng phù hợp với tôi. – icfantv

6

Tạo một ObjectFactory tùy chỉnh để khởi tạo lớp mong muốn trong khi không điều chỉnh. Ví dụ:

JAXBContext context = JAXBContext.newInstance("com.whatever.mypackage"); 
Unmarshaller unmarshaller = context.createUnmarshaller(); 
unmarshaller.setProperty("com.sun.xml.internal.bind.ObjectFactory", new ReceiverPersonObjectFactory()); 
return unmarshaller; 

public class ReceiverPersonObjectFactory extends ObjectFactory { 
    public Person createPerson() { 
     return new ReceiverPerson(); 
    } 
} 
+0

Điều đó dường như với tôi như một cách rất thông minh vì nó đòi hỏi phải tạo ra từng đối tượng chỉ một lần - so với giải pháp XmlAdpater nơi bạn lần đầu tạo cá thể lớp kiểu ràng buộc và sau đó sao chép tất cả các thuộc tính sang thể hiện lớp giá trị. Nhưng câu hỏi đặt ra là: Liệu điều này có làm việc với các JRE khác với Sun/Oracle như OpenJDK không? – Robert

+0

+1 là giải pháp duy nhất cho tôi. Tôi đang cố gắng sử dụng loại thay thế cho subclassing (implClass) trong JAXB 2. * trong một tập tin xsd và các lớp unmarshaled được tạo ra những người khác hơn một trong quy định trong implClass. câu trả lời của vocaro giải quyết vấn đề! – Tatera

+0

Cập nhật nhỏ cho trường hợp khi JAXB là một triển khai bên ngoài (không phải từ SDK): thuộc tính được gọi là "" com.sun.xml.bind.ObjectFactory "'. –

Các vấn đề liên quan