欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JAXB(七)——监听器

程序员文章站 2022-04-17 17:02:53
...

监听器

在进行marshal和unmarshal的时候JAXB为我们提供了对应的监听器,允许我们在marshal和unmarshal的过程中对当前对象做一些操作或者记录一些日志等。

marshal监听器

marshal过程中的监听器是对应的是Marshaller.Listener抽象类,其定义如下:

public static abstract class Listener {
    
    public void beforeMarshal(Object source) {

    }

    
    public void afterMarshal(Object source) {

    }
}

默认都是空实现,beforeMarshal方法用于在转换对象为XML之前回调,afterMarshal方法用于在转换对象为XML之后回调,参数source就是当前正在转换为XML的对象。监听器是通过Marshaller.setListener(Listener listener)来指定的,其会对当前Marshaller进行的对象中的每一个复杂对象转换为XML时回调。假设有下面这样的类定义:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="root")
public class OrgHolder {
    private Org org;

    public Org getOrg() {
        return org;
    }

    public void setOrg(Org org) {
        this.org = org;
    }
    
}

@XmlAccessorType(XmlAccessType.FIELD)
public class Org {

    private String no;
    private String name;
    
    public String getNo() {
        return no;
    }
    public void setNo(String no) {
        this.no = no;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
}

我们简单的定义如下这样的监听器,它只是简单的输出当前的动作:

public class GlobalMarshalListener extends Marshaller.Listener {

    @Override
    public void beforeMarshal(Object source) {
        System.out.println("马上要被marshal的对象是:" + source);
    }

    @Override
    public void afterMarshal(Object source) {
        System.out.println("刚刚被marshal的对象是:" + source);
    }
    
}

运行如下测试程序:

@Test
public void test() throws Exception {
    
    OrgHolder holder = this.buildOrgHolder();
    JAXBContext jaxbContext = JAXBContext.newInstance(OrgHolder.class);
    Marshaller marshaller = jaxbContext.createMarshaller();
    marshaller.setListener(new GlobalMarshalListener());//指定Listener,全局的,对当前Marshaller中所有的对象marshal都起作用
    StringWriter writer = new StringWriter();
    marshaller.marshal(holder, writer);
    
}

private OrgHolder buildOrgHolder() {
    OrgHolder holder = new OrgHolder();
    Org org = new Org();
    org.setNo("A001");
    org.setName("XXX");
    holder.setOrg(org);
    return holder;
}

我们会看到如下这样的输出:

马上要被marshal的对象是:com.elim.jaxb.OrgHolder@5577140b
马上要被marshal的对象是:com.elim.jaxb.OrgHolder@5577140b
马上要被marshal的对象是:com.elim.jaxb.Org@1c6b6478
刚刚被marshal的对象是:com.elim.jaxb.Org@1c6b6478
刚刚被marshal的对象是:com.elim.jaxb.OrgHolder@5577140b
刚刚被marshal的对象是:com.elim.jaxb.OrgHolder@5577140b

从输出中我们可以看到根对应的marshal过程中对应的监听器方法被调用了两次,所以需要确保监听器中进行的操作是幂等的。

unmarshal监听器

unmarshal过程中设置的监听器是Unmarshaller.Listener,其定义如下:

public static abstract class Listener {

    public void beforeUnmarshal(Object target, Object parent) {

    }

    public void afterUnmarshal(Object target, Object parent) {

    }

}

beforeUnmarshal方法将在当前对象被实例化,但是在XML转换为对象前调用,afterUnmarshal方法将在XML转换为对象后调用。参数target是当前正在被unmarshal的对象,parent是持有当前对象的引用的对象,即所谓的父对象。unmarshal过程中使用的Listener的示例如下:

public class GlobalUnmarshalListener extends Unmarshaller.Listener {

    @Override
    public void beforeUnmarshal(Object target, Object parent) {
        System.out.println("马上要被unmarshal的对象是:" + target + ",该对象的父级对象是:" + parent);
    }

    @Override
    public void afterUnmarshal(Object target, Object parent) {
        System.out.println("刚刚被unmarshal的对象是:" + target + ",该对象的父级对象是:" + parent);
    }
    
}

测试代码如下:

@Test
public void test() throws Exception {
    
    OrgHolder holder = this.buildOrgHolder();
    JAXBContext jaxbContext = JAXBContext.newInstance(OrgHolder.class);
    Marshaller marshaller = jaxbContext.createMarshaller();
    StringWriter writer = new StringWriter();
    marshaller.marshal(holder, writer);
    
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    unmarshaller.setListener(new GlobalUnmarshalListener());
    unmarshaller.unmarshal(new StringReader(writer.toString()));
    
}

输出如下:

马上要被unmarshal的对象是:com.elim.jaxb.OrgHolder@26ba2a48,该对象的父级对象是:null
马上要被unmarshal的对象是:com.elim.jaxb.Org@5f2050f6,该对象的父级对象是:com.elim.jaxb.OrgHolder@26ba2a48
刚刚被unmarshal的对象是:com.elim.jaxb.Org@5f2050f6,该对象的父级对象是:com.elim.jaxb.OrgHolder@26ba2a48
刚刚被unmarshal的对象是:com.elim.jaxb.OrgHolder@26ba2a48,该对象的父级对象是:null

上面的示例中虽然我们实现的监听器只是简单的输出了一些信息,但实际上我们可以使用它们来辅助XML和Java相互转换的过程,比如unmarshal时unmarshal的结果可能是org.w3c.dom.Element类型的对象,但我们可以通过Unmarshaller.Listener的afterUnmarshal方法把它变为一个我们最终需要的对象。具体的应用场景就要看具体的业务需要了。

实例回调方法

Unmarshaller.Listener和Marshaller.Listener作用的都是当前unmarshal或marshal中对应的所有的对象,对每个对象进行转换时都将进行调用,如果我们只是希望对某个具体的对象进行转换时进行监听,则可以使用实例级别的监听。 在使用JAXB进行对象和XML之间的相互转换时,如果对应的类按照JAXB的规范定义了一些回调方法,JAXB会在进行操作时调用对应的回调方法,对应的回调方法一共有四个。

  • beforeMarshal(Marshaller marshaller):在对当前对象marshal前调用
  • afterMarshal(Marshaller marshaller):在对当前对象marshal后调用
  • beforeUnmarshal(Unmarshaller unmarshaller, Object parent):在对当前对象进行unmarshal前调用
  • afterUnmarshal(Unmarshaller unmarshaller, Object parent):在对当前对象进行unmarshal后调用

需要注意的是这些方法定义必须与规定的一致:方法名必须一致;方法参数类型、个数和顺序必须一致;至于是否有抛出异常这些是不影响的。这些回调方法不需要四个都定义,可以只定义你感兴趣的回调方法。下面是一个Org类的定义,其中就定义对应的四个回调方法,

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="org")
public static class Org {
    private String no;
    private String name;
    public String getNo() {
        return no;
    }
    public void setNo(String no) {
        this.no = no;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    
    /**
     * 方法名必须是beforeMarshal,必须只接收一个Marshaller类型的参数,至于是否抛出异常JVM是不管的
     * @param marshaller
     */
    public void beforeMarshal(Marshaller marshaller) {
        System.out.println("马上要marshal本对象了");
    }
    
    public void afterMarshal(Marshaller marshaller) {
        System.out.println("该对象已经被marshal了");
    }
    
    public void beforeUnmarshal(Unmarshaller unmarshaller, Object parent) {
        System.out.println("马上要unmarshal该对象了,持有该对象的父级对象是:" + parent);
    }
    
    public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
        System.out.println("该对象已经unmarshal完成了,持有该对象的父级对象是:" + parent);
    }
    
}

进行测试如下:

@Test
public void test2() throws Exception {
    
    Org org = this.buildOrg();
    JAXBContext jaxbContext = JAXBContext.newInstance(Org.class);
    Marshaller marshaller = jaxbContext.createMarshaller();
    StringWriter writer = new StringWriter();
    marshaller.marshal(org, writer);

    System.out.println("--------------分界线--------------");

    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    unmarshaller.unmarshal(new StringReader(writer.toString()));
    
}

private Org buildOrg() {
    Org org = new Org();
    org.setNo("A001");
    org.setName("XXX");
    return org;
}

输出如下:

马上要marshal本对象了
马上要marshal本对象了
该对象已经被marshal了
该对象已经被marshal了
--------------分界线--------------
马上要unmarshal该对象了,持有该对象的父级对象是:null
该对象已经unmarshal完成了,持有该对象的父级对象是:null

跟全局的Marshaller.Listener一样,marshal相关的回调方法会被回调两次。

笔者曾经遇到过这样一个需求,一段XML节点下面的内容,有的时候是一段JSON字符串,有的时候是一段XML,类似于下面这样。data节点下面的内容有的时候是一段XML,有的时候是一段JSON,当然了实际情况下它们表示的内容不是一样的,笔者这里只是为了更好的说明问题才把它们弄成一样的。

<root><data>{'id': '1', 'name': 'ABCDE'}</data></root>
<root><data><user id="1"><name>ABCDE</name></user></data></root>

这种场景下就可以通过使用实例级别的unmarshal回调方法实现把JSON字符串和XML都转换为需要的User对象。那这个时候我们的代码可以是如下这样,其中的setData()只是用来接收最原始的XML内容的,即作为data节点的Java映射。afterUnmarshal会在unmarshal之后把data节点的内容根据内容的不同分别转换为对应的User对象,然后把它赋予realData属性,之后就可以通过getRealData()方法读取到对应的User对象,而不用管底层的User对象到底是如何转换来的。

@XmlRootElement(name="root")
public static class RootObj {
    
    private Element data;
    
    private User realData;
    
    @XmlAnyElement
    public void setData(Element data) {
        this.data = data;
    }
    
    public User getRealData() {
        return this.realData;
    }
    
    public void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
        if (this.data == null) {
            return;
        }
        Node node = this.data.getFirstChild();
        if (node.getNodeType() == Node.TEXT_NODE) {//直接是文本节点的是JSON
            String jsonData = ((Text)node).getData();
            this.realData = JSON.parseObject(jsonData, User.class);
        } else {
            Element ele = (Element) node;
            String id = ele.getAttribute("id");
            String name = ((Text)ele.getFirstChild().getFirstChild()).getData();
            User user = new User();
            user.setId(id);
            user.setName(name);
            this.realData = user;
        }
    }
    
}

测试代码如下:

@Test
public void testListener() throws Exception {
    JAXBContext jaxbContext = JAXBContext.newInstance(RootObj.class);
    String xml1 = "<root><data>{'id': '1', 'name': 'ABCDE'}</data></root>";
    String xml2 = "<root><data><user id=\"1\"><name>ABCDE</name></user></data></root>";
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    RootObj rootObj = (RootObj) unmarshaller.unmarshal(new StringReader(xml1));
    RootObj rootObj2 = (RootObj) unmarshaller.unmarshal(new StringReader(xml2));
    Assert.assertTrue(rootObj.getRealData().equals(rootObj2.getRealData()));
}

(完)