JAXB(七)——监听器
监听器
在进行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()));
}
(完)