公司的运营部门要求所有上线的component有个链接,进行heartbeat检测。
要求如下:
提供一个http url这个http url 返回xml来显示server的状态,格式如下<?xml version="1.0" encoding="UTF-8"?> <admin> <status> <name>Server</name> <value>[ok|fail]</value> </status> <status> <name>Error</name> <value>msg</value> </status> </admi
heartbeat需要check哪些内容(db/io/thread/cpu/memory/file handler...),这里不作讨论。我看过的code中,有以下实现:
打开一个socket端口(非web app),如果系统自检 ok, 返回一个OK的xml字符串。如果自检失败,返回fail的xml字符串。String ok = ''<admin> ... <value>ok</value> ... </admin>"; String fail = "<admin>... <value>fail</value> ... </admin>";
定义一个servlet(web app),其他同上。为heartbeat定义一个wsdl(web app, deploy in axis2 aar ), 提供一个heartbeat service,比如:http://host/app/UserService (domain service) http://host/app/HeartbeatService (heartbeat service)
使用axis2的RawXMLINOutMessageReceiver或者AbstractInOutMessageReceiver(web app, deploy in axis2 aar),定义一个POJOs的service, 返回OMElement(可以从xml转换得到)。本质和3相同,不过是少了wsdlOMElement documentElement = AXIOMUtil.stringToOM(xml);
按照生成xml的方法,上面四种方式归结为3种
raw string定义xsd使用DOM Tree (OMElement)。使用DOM Tree给人的感觉和raw string没啥区别。这里就出现了JAXB的第一个应用场景: 需要生成xml,xml相对简单:定义xsd有大材小用之嫌,代码中字符串拼接又太ugly。
Java Architecture for XML Binding(JAXB) 是一种OX(object <-> xml mapping)工具。主要提供了两个功能:将 java对象编码序列化(marshal)成xml,或者反之,从xml反序列化到java对象。一图以蔽之:
xml schema 与 classes(interfaces) 之间的相互转换(为下面的转换做铺垫)xml document 和 instance object 之间的相互转换xml是比java,c++, python...等编程语言更通用的描述语言,从语言相互翻译的角度而言,上面两种映射天然如此。
回到本文开头heartbeat xml生成的问题,
首先定义HeartBeat类,包含status。selfCheck方法检测server的各种状态,这里仅仅是sample stub code,直接返回一个HeartBeatimport java.util.ArrayList; import java.util.List; public class HeartBeat { public List<Status> status = new ArrayList<Status>(); static class Status { public String name; public String value; } public static HeartBeat selfCheck() { HeartBeat heartbeat = new HeartBeat(); Status ok = new Status(); ok.name = "app1"; ok.value = "OK"; Status fail = new Status(); fail.name = "app2"; fail.value = "fail"; heartbeat.status.add(ok); heartbeat.status.add(fail); return heartbeat; } }
使用JAXB将HeartBeat实例对象marshal成xmlpublic static void marshal(HeartBeat heartbeat) throws Exception { JAXBContext context = JAXBContext.newInstance(HeartBeat.class); Marshaller m = context.createMarshaller(); m.setProperty("jaxb.formatted.output", true); QName qname = new QName("admin"); JAXBElement<HeartBeat> element = new JAXBElement<HeartBeat>(qname, HeartBeat.class, heartbeat); m.marshal(element, System.out); } public static void main(String[] args) throws Exception { HeartBeat heartbeat = HeartBeat.selfCheck(); marshal(heartbeat); }
生成的xml如下<admin> <status> <name>app1</name> <value>OK</value> </status> <status> <name>app2</name> <value>fail</value> </status> </admin>
说明:
Status被定义成static inner class而不是inner class,否则会报错is a non-static inner class, and JAXB can’t handle those.
JAXBElement中Qname的local part为admin,因为xml的root elment从admin开始
在上面的例子中,Heartbeat类定义上未加任何的annotation,就直接被JAXB marshal。理论上,我们可以仿照上一篇A generic JAXB marshal/unmarshal XmlType中的泛型方法定义,将marshal方法定义成一个可marhal任意java对象的通用方法。不过在实际中,我们很少直接handle未加任何jaxb annotation的对象,主要有循环引用,内部类等问题。
上面的例子可以看出jaxb相当简单易用(点此从一个实例看jaxb的强大),秉承JDK API的一贯风格。
如果遵循contract first的原则,在解决与xml相关的问题时,schema first.
首先定义heartbeat的schema如下(由xml->xsd在线工具得到):<?xml version="1.0" encoding="utf-16"?> <xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified" version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="admin"> <xsd:complexType> <xsd:sequence> <xsd:element maxOccurs="unbounded" name="status"> <xsd:complexType> <xsd:sequence> <xsd:element name="name" type="xsd:string" /> <xsd:element name="value" type="xsd:string" /> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
使用xjc(jdk工具,与java/javac在同一目录,在CMD命令窗口可直接使用),生成java类:xjc heartbeat.xsdAdmin
import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "status" }) @XmlRootElement(name = "admin") public class Admin { @XmlElement(required = true) protected List<Admin.Status> status; /** * Gets the value of the status property. * * <p> * This accessor method returns a reference to the live list, * not a snapshot. Therefore any modification you make to the * returned list will be present inside the JAXB object. * This is why there is not a <CODE>set</CODE> method for the status property. * * <p> * For example, to add a new item, do as follows: * <pre> * getStatus().add(newItem); * </pre> * * * <p> * Objects of the following type(s) are allowed in the list * {@link Admin.Status } * * */ public List<Admin.Status> getStatus() { if (status == null) { status = new ArrayList<Admin.Status>(); } return this.status; } /** * <p>Java class for anonymous complex type. * * <p>The following schema fragment specifies the expected content contained within this class. * * <pre> * <complexType> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <sequence> * <element name="name" type="{http://www.w3.org/2001/XMLSchema}int"/> * <element name="value" type="{http://www.w3.org/2001/XMLSchema}int"/> * </sequence> * </restriction> * </complexContent> * </complexType> * </pre> * * */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "name", "value" }) public static class Status { protected int name; protected int value; /** * Gets the value of the name property. * */ public int getName() { return name; } /** * Sets the value of the name property. * */ public void setName(int value) { this.name = value; } /** * Gets the value of the value property. * */ public int getValue() { return value; } /** * Sets the value of the value property. * */ public void setValue(int value) { this.value = value; } } }
ObjectFactory
@XmlRegistry public class ObjectFactory { /** * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: generated * */ public ObjectFactory() { } /** * Create an instance of {@link Admin.Status } * */ public Admin.Status createAdminStatus() { return new Admin.Status(); } /** * Create an instance of {@link Admin } * */ public Admin createAdmin() { return new Admin(); } }
使用jaxb,将admin marshal。比前面少了两行new JAXBElement对象的代码JAXBContext context = JAXBContext.newInstance("com.xx.heartbeat"); //package Marshaller m = context.createMarshaller(); m.setProperty("jaxb.formatted.output", true); m.marshal(admin, System.out);
class定义中使用的Annotation和xml schema定义一一对应,说明如下:
@XmlRootElement(name = "admin")
//xml 文档根节点
<xsd:element name="admin">
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"status"
})
@XmlElement(required = true)
protected List<Admin.Status> status;
//nested 的complexType,所以XmlType的name为空;有Order indicator
sequence,所以有propOrder来指定序列化顺序
//maxOccurs为unbounded,所以status定义为List,默认minOccurs为1,所以添加了required = true
//XmlAccessorType 表明了哪些field需要被序列化。
<xsd:complexType> <xsd:sequence>
<xsd:element maxOccurs="unbounded" name="status">
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"name",
"value"
})
//同上
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type="xsd:int" />
<xsd:element name="value" type="xsd:int" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
如果你习惯先写java class,或者已经有很多写好的java code(有没有jaxb annotation都成)。schemagen(同xjc,jdk工具)是帮助你快速定义schema的不二选择。
对上面生成的Admin.java使用schemagen(schemagen Admin.java),生成的schema和前面的定义一模一样