JAXB: Невозможно ли использовать XmlAdapter без @XmlJavaTypeAdapter?

Не могу ли я зарегистрировать связку XmlAdapter до Marshaller | Unmarshaller, чтобы мне не нужно было указывать @XmlJavaTypeAdapter для каждой поданной, тип которой не поддерживается JAXB?

Я нахожу его несколько избыточным.

BTW, someMarshaller.setAdapter(...) похоже, ничего не делают.

Ответ 1

Это довольно хороший вопрос!

Короткий ответ: не, используя setAdapter в marshaller/unmarshaller, не означает, что вам не нужно использовать @XmlJavaTypeAdapter.

Позвольте мне объяснить это гипотетическим (но действительным!) сценарием.

Рассмотрим в веб-приложении одно событие в форме xml, имеющее следующую схему:

<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>

Эквивалентно этому, ваша модель будет выглядеть так:

@XmlRootElement(name="event")
@XmlType(name="")
public class Event {

     @XmlElement(required=true)
     protected String performedBy;
}

Теперь приложение уже имеет bean, называемый User, который поддерживает подробный информацию о пользователе.

public class User {

    private String id;
    private String firstName;
    private String lastName;

    ..
}

Обратите внимание, что этот User не известен вашему Контексту JAXB. Для простоты мы используем User как POJO, но это может быть любой допустимый класс Java.

Что хочет архитектор приложений Event performedBy должен быть представлен как User для получения полной информации.

Вот где @XmlJavaTypeAdapter входит в изображение

JAXBContext знает о performedBy как xs:string, но он должен быть представлен как User в памяти на Java.

Модифицированная модель выглядит следующим образом:

@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 {
             ..
     } 
}

В определении адаптера указано, что -

  • BoundType - Пользователь (в представлении Memeory)
  • ValueType - это строка (тип JAXB типа данных известен)

Возвращаясь к вашему вопросу -

Я нахожу его несколько лишним.

Кстати, someMarshaller.setAdapter(...), похоже, ничего не делает.

Учтите, что для нашего адаптера требуется класс UserContext, чтобы успешно выполнить маршальцию/демонтаж.

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

Теперь вопрос заключается в том, как UserAdapter будет извлекать инстанс UserContext?? Как хороший дизайн, он всегда должен поставлять его, пока он получил экземпляр.

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

Но время выполнения JAXB может принимать только адаптер с конструктором No-args.. (Очевидно, JAXBContext не знает о конкретной модели приложения)

К счастью, есть вариант: D

Вы можете указать unmarshaller использовать данный экземпляр UserAdapter, а не устанавливать его самостоятельно.

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 доступен как на Marshaller, так и на unmarshaller

Примечание:

  • setAdapter в marshaller/unmarshaller не означает, что вам не нужно использовать @XmlJavaTypeAdapter.

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

    Если вы опускаете эту среду исполнения JAXB, нет никакой информации о том, что Пользователь - ваш тип Bound Type и Value Type - это что-то еще. Он попытается создать маркер User как есть, и у вас будет неправильный xml (или сбой проверки, если он включен)

  • В то время как мы взяли сценарий, когда Адаптер должен быть с аргументами, следовательно используйте метод setAdapter.

    Некоторые adavanced-приложения также существуют там, где даже если у вас есть конструктор no-arg по умолчанию, но вы предоставляете экземпляр адаптера

    Пусть этот адаптер сконфигурирован с данными, которые использует маршал/немаршальная операция!

Ответ 2

Вы можете использовать package-info.java

Это называется "уровень пакета".

Пример: поставьте package.info.java в том же пакете, что класс, который вы хотите использовать для маршалла /unmarshall.

Предположим, у вас есть класс mypackage.model.A и адаптер CalendarAdapter в mypackage.adapter. Объявите файл package-info.java в mypackage.model, содержащий:

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

import java.util.Calendar;
import mypackage.adapter.CalendarAdapter;

Все поля типа Календарь в классе A будут распределены или неармаллированы с помощью CalendarAdapter.

Здесь вы можете найти полезную информацию: http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html