JAXB: Isn't it possible to use an XmlAdapter without @XmlJavaTypeAdapter?

前端 未结 2 791
耶瑟儿~
耶瑟儿~ 2020-12-30 00:16

Can\'t I register a bunch of XmlAdapters to Marshaller|Unmarshaller so that I wouldn\'t need to specify @XmlJavaTypeAdapter

相关标签:
2条回答
  • 2020-12-30 00:43

    You can use the package-info.java

    That's called "package level".

    Example : put a package-info.java in the same package that the class you want to marshall/unmarshall.

    Suppose you have a class mypackage.model.A and an adapter CalendarAdapter in mypackage.adapter. Declare a package-info.java file in mypackage.model containing :

    @XmlJavaTypeAdapters({
        @XmlJavaTypeAdapter(value = CalendarAdapter.class, type = Calendar.class)
    })
    package mypackage.model;
    
    import java.util.Calendar;
    import mypackage.adapter.CalendarAdapter;
    

    All field of type Calendar in the class A will be marshalled or unmarshalled with the CalendarAdapter.

    You can find useful informations there : http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html

    0 讨论(0)
  • 2020-12-30 00:46

    This is a quite a good question !

    The short answer is that no, using setAdapter on marshaller / unmarshaller does not mean that you don't have to use @XmlJavaTypeAdapter.

    Let me explain this with a hypothetical (yet valid!) scenario.

    Consider in a web application, one fetches an event in the form of xml having following schema:

    <xs:element name="event" >
        <xs:complexType>
            <xs:sequence>
               <!-- Avoiding other elements for concentrating on our adapter -->
                <xs:element name="performedBy" type="xs:string" />   
            </xs:sequence> 
        </xs:complexType>  
    </xs:element>
    

    Equivalent to this, your model will look like:

    @XmlRootElement(name="event")
    @XmlType(name="")
    public class Event {
    
         @XmlElement(required=true)
         protected String performedBy;
    }
    

    Now the application is already having a bean called User which maintains the detailed information about the user.

    public class User {
    
        private String id;
        private String firstName;
        private String lastName;
    
        ..
    }
    

    Note that this User is not known to your JAXB Context. For simplicity we have User as POJO, but it can be any Valid Java Class.

    What application architect want is Event's performedBy should be represented as User to gain full details.

    Here is where @XmlJavaTypeAdapter comes into picture

    JAXBContext is aware about performedBy as xs:string , but it has to be represented as User in memory in Java.

    Modified model looks like:

    @XmlRootElement(name="event")
    @XmlType(name="")
    public class Event {
    
         @XmlElement(required=true)
         @XmlJavaTypeAdapter(UserAdapter.class) 
         protected User performedBy;
    }
    

    UserAdapter.java:

    public class UserAdapter extends XmlAdapter<String, User> {
    
         public String marshal(User boundType) throws   Exception {
                 ..   
         } 
    
         public User unmarshal(String valueType) throws Exception {
                 ..
         } 
    }
    

    The Adapter's definition says that -

    1. BoundType is User (In Memeory representation)
    2. ValueType is String (The data type JAXB Context is aware of)

    Coming to back to your question -

    I find it somewhat redundant.

    BTW, someMarshaller.setAdapter(...) seem not to do anything.

    Consider that our Adapter requires a class called UserContext in order to marshal / unmarshal sucessfully.

    public class UserAdapter extends XmlAdapter<String, User> {
    
         private UserContext userContext;
    
         public String marshal(User boundType) throws   Exception {
              return boundType.getId();
         } 
    
         public User unmarshal(String valueType) throws Exception {
              return userContext.findUserById(valueType);
         } 
    }
    

    Now the question is how will UserAdapter will fetch an instace of UserContext ?? As a good design one should always supply it while it has got instantiated ..

    public class UserAdapter extends XmlAdapter<String, User> {
    
         private UserContext userContext;
    
         public UserAdapter(UserContext userContext) {
            this.userContext = userContext;  
         } 
    
         public String marshal(User boundType) throws   Exception {
              return boundType.getId();
         } 
    
         public User unmarshal(String valueType) throws Exception {
              return userContext.findUserById(valueType);
         } 
    }
    

    But JAXB Runtime can only accept Adapter with No-args constructor .. (Obviously JAXBContext does not know about application specific model)

    So thankfully there is an option :D

    You can tell your unmarshaller to use given instance of UserAdapter rather than instating it by own its own.

    public class Test {
    
    public static void main(String... args) { 
        JAXBContext context = JAXBContext.getInstance(Event.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();
    
          UserContext userContext = null; // fetch it from some where
          unmarshaller.setAdapter(UserAdapter.class, new UserAdapter(userContext));
    
          Event event = (Event) unmarshaller.unmarshal(..);
       }
    }
    

    setAdapter method is available on both Marshaller & Unmarshaller

    Note:

    1. setAdapter on marshaller / unmarshaller does not mean that you don't have to use @XmlJavaTypeAdapter.

      @XmlRootElement(name="event")
      @XmlType(name="")
      public class Event {
      
          @XmlElement(required=true)
          // @XmlJavaTypeAdapter(UserAdapter.class) 
          protected User performedBy;
      }
      

      If you omit this JAXB runtime has no clue that User is your Bound Type & Value Type is something else. It will try to marshal User as is & u will end up having wrong xml (or validation failure if enabled)

    2. While we have taken a scenario where Adapter is required to be with arguments, hence use setAdapter method.

      Some adavanced usages are also there, where in even if you have default no-arg constructor, yet you provide an instance of the Adapter

      May this adapter is configured with data, which marshal / unmarshal operation is using !

    0 讨论(0)
提交回复
热议问题