234390216 阅读(81) 评论(0)

监听器

在进行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()));
}

(完)