JAXB:没有@XmlJavaTypeAdapter 就不能使用XmlAdapter 吗?

2022-01-19 00:00:00 java jaxb adapter

我不能将一堆 XmlAdapter 注册到 Marshaller|Unmarshaller 以便我不需要指定 @XmlJavaTypeAdapter 在每个字段上,其类型本身不支持 JAXB?

Can't I register a bunch of XmlAdapters to Marshaller|Unmarshaller so that I wouldn't need to specify @XmlJavaTypeAdapter on each filed, whose type isn't natively JAXB-supported?

我觉得它有点冗余.

顺便说一句,someMarshaller.setAdapter(...) 似乎什么也没做.

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

推荐答案

这是一个相当不错的问题!

This is a quite a good question !

简短的回答是 no,在 marshaller/unmarshaller 上使用 setAdapter 并不意味着您不必使用 @XmlJavaTypeAdapter.

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.

考虑在一个 Web 应用程序中,以具有以下模式的 xml 形式获取事件:

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

现在应用程序已经有了一个名为 User 的 bean,它维护了详细信息用户信息.

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;

    ..
}

请注意,您的 JAXB 上下文不知道此 User.为简单起见,我们将 User 作为 POJO,但它可以是任何有效的 Java 类.

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.

应用程序架构师想要的是 EventperformedBy 应该表示为 User以获得完整的细节.

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

这就是@XmlJavaTypeAdapter 出现的地方

Here is where @XmlJavaTypeAdapter comes into picture

JAXBContext 知道 performedByxs:string ,但它必须表示为User 在 Java 中的内存中.

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

修改后的模型如下:

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

     @XmlElement(required=true)
     @XmlJavaTypeAdapter(UserAdapter.class) 
     protected User performedBy;
}

UserAdapter.java:

UserAdapter.java:

public class UserAdapter extends XmlAdapter<String, User> {

     public String marshal(User boundType) throws   Exception {
             ..   
     } 

     public User unmarshal(String valueType) throws Exception {
             ..
     } 
}

适配器的定义说 -

  1. BoundType 是 User(在内存中表示)
  2. ValueType 是 String(JAXB 上下文知道的数据类型)

回到您的问题 -

我觉得有点多余.

顺便说一句,someMarshaller.setAdapter(...) 似乎什么也没做.

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

考虑到我们的适配器需要一个名为 UserContext 的类才能成功编组/解组.

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

现在的问题是 UserAdapter 将如何获取 UserContext 的实例?作为一个好的设计,应该总是在实例化时提供它..

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

但是 JAXB 运行时只能接受带有无参数构造函数的适配器..(显然 JAXBContext 不知道应用程序特定的模型)

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

幸好有一个选项:D

您可以告诉您的 unmarshaller 使用给定的 UserAdapter 实例,而不是自己安装它.

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 方法在 Marshaller解组器

注意:

  1. setAdapter on marshaller/unmarshaller 并不意味着你不必使用 @XmlJavaTypeAdapter.

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

如果您省略此 JAXB 运行时,则不知道 User 是您的绑定类型 &值类型是另一回事.它将尝试按原样编组 User你最终会得到错误的xml(如果启用,则验证失败)

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)

虽然我们已经采用了适配器需要带有参数的场景,因此使用 setAdapter 方法.

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 !

相关文章