用来记录一些原创性的总结
从面向对象的角度来讲,Java类的定义与XML的Schema文件的定义是一致的,都是用来定义具体对象的结构的。而具体的Java对象则将跟XML文件相对应,XML的具体内容则是由对应的Schema文件约束的。通过Java对象可以转换为XML,也可以通过对应的Java类定义转换为XML对应的Schema文件。同样的,我们可以通过XML转换为对应的Java对象,也可以通过XML对应的Schema文件转换对应的Java类定义。
schemegen是JDK自带的一个工具,用来基于java或class文件生成对应的schema文件。对应的类可以通过JAXB注解来定义XML映射关系。我们先来看一个简单的示例,假设有如下这些java类需要用来生成schema文件。
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "root")
public class RootObject {
    private String prop1;
    private String prop2;
    @XmlElementRef
    private SuperClass superClass;
    public String getProp1() {
        return prop1;
    }
    public void setProp1(String prop1) {
        this.prop1 = prop1;
    }
    public String getProp2() {
        return prop2;
    }
    public void setProp2(String prop2) {
        this.prop2 = prop2;
    }
    public SuperClass getSuperClass() {
        return superClass;
    }
    public void setSuperClass(SuperClass superClass) {
        this.superClass = superClass;
    }
}
public class SuperClass {
    private String superProp1;
    private String superProp2;
    public String getSuperProp1() {
        return superProp1;
    }
    public void setSuperProp1(String superProp1) {
        this.superProp1 = superProp1;
    }
    public String getSuperProp2() {
        return superProp2;
    }
    public void setSuperProp2(String superProp2) {
        this.superProp2 = superProp2;
    }
    
}
@XmlRootElement(name="sub1")
public class SubClass1 extends SuperClass {
    private String subProp1;
    private String subProp2;
    public String getSubProp1() {
        return subProp1;
    }
    public void setSubProp1(String subProp1) {
        this.subProp1 = subProp1;
    }
    public String getSubProp2() {
        return subProp2;
    }
    public void setSubProp2(String subProp2) {
        this.subProp2 = subProp2;
    }
    
}
@XmlRootElement(name="sub2")
public class SubClass2 extends SuperClass {
    private String subProp1;
    private String subProp2;
    public String getSubProp1() {
        return subProp1;
    }
    public void setSubProp1(String subProp1) {
        this.subProp1 = subProp1;
    }
    public String getSubProp2() {
        return subProp2;
    }
    public void setSubProp2(String subProp2) {
        this.subProp2 = subProp2;
    }
    
}
这些类都是放在com.elim.jaxb包下面的,所以在命令行窗口定位到源文件根路径,运行命令即可在当前目录生成一个schema1.xsd文件。因为我们在源文件里面没有指定schema,所以它们都将使用相同的默认schema,这时候生成的schema文件只有一个。如果我们在源文件中指定了多个不同的schema时,生成出来的也将有多个schema文件。
schemagen com/elim/jaxb/*.java
生成出来的schema文件内容如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root" type="rootObject"/>
  <xs:element name="sub1" type="subClass1"/>
  <xs:element name="sub2" type="subClass2"/>
  <xs:complexType name="rootObject">
    <xs:sequence>
      <xs:element name="prop1" type="xs:string" minOccurs="0"/>
      <xs:element name="prop2" type="xs:string" minOccurs="0"/>
      <xs:choice>
        <xs:element ref="sub1"/>
        <xs:element ref="sub2"/>
      </xs:choice>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="superClass">
    <xs:sequence>
      <xs:element name="superProp1" type="xs:string" minOccurs="0"/>
      <xs:element name="superProp2" type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="subClass1">
    <xs:complexContent>
      <xs:extension base="superClass">
        <xs:sequence>
          <xs:element name="subProp1" type="xs:string" minOccurs="0"/>
          <xs:element name="subProp2" type="xs:string" minOccurs="0"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="subClass2">
    <xs:complexContent>
      <xs:extension base="superClass">
        <xs:sequence>
          <xs:element name="subProp1" type="xs:string" minOccurs="0"/>
          <xs:element name="subProp2" type="xs:string" minOccurs="0"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
</xs:schema>
如果需要指定namespace,可以在具体的类上通过@XmlType或@XmlRootElement的namespace指定,如:
@XmlRootElement(name="sub1", namespace="http://elim.com/jaxb")
public class SubClass1 extends SuperClass {
    private String subProp1;
    private String subProp2;
    public String getSubProp1() {
        return subProp1;
    }
    public void setSubProp1(String subProp1) {
        this.subProp1 = subProp1;
    }
    public String getSubProp2() {
        return subProp2;
    }
    public void setSubProp2(String subProp2) {
        this.subProp2 = subProp2;
    }
    
}
如果整个包里面的类对应的schema都属于同一个namespace,则可以建立对应的package-info.java文件,然后通过@XmlSchema指定schema相关的信息,包括namespace,如:
@javax.xml.bind.annotation.XmlSchema(namespace="http://elim.com/jaxb",
    elementFormDefault=XmlNsForm.UNQUALIFIED, 
    attributeFormDefault=XmlNsForm.UNQUALIFIED)
package com.elim.jaxb;
import javax.xml.bind.annotation.XmlNsForm;
使用了上面的配置后生成的schema文件内容如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="unqualified" version="1.0" targetNamespace="http://elim.com/jaxb" xmlns:tns="http://elim.com/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root" type="tns:rootObject"/>
  <xs:element name="sub1" type="tns:subClass1"/>
  <xs:element name="sub2" type="tns:subClass2"/>
  <xs:complexType name="rootObject">
    <xs:sequence>
      <xs:element name="prop1" type="xs:string" minOccurs="0"/>
      <xs:element name="prop2" type="xs:string" minOccurs="0"/>
      <xs:choice>
        <xs:element ref="tns:sub1"/>
        <xs:element ref="tns:sub2"/>
      </xs:choice>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="superClass">
    <xs:sequence>
      <xs:element name="superProp1" type="xs:string" minOccurs="0"/>
      <xs:element name="superProp2" type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="subClass1">
    <xs:complexContent>
      <xs:extension base="tns:superClass">
        <xs:sequence>
          <xs:element name="subProp1" type="xs:string" minOccurs="0"/>
          <xs:element name="subProp2" type="xs:string" minOccurs="0"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="subClass2">
    <xs:complexContent>
      <xs:extension base="tns:superClass">
        <xs:sequence>
          <xs:element name="subProp1" type="xs:string" minOccurs="0"/>
          <xs:element name="subProp2" type="xs:string" minOccurs="0"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
</xs:schema>
上面的示例也可以单独指定需要生成schema的java类:
schemagen com/elim/jaxb/RootObject.java com/elim/jaxb/SuperClass.java com/elim/jaxb/SubClass1.java com/elim/jaxb/SubClass2.java
除了基于Java源文件生成schema外,也可以基于编译好的class文件生成schema文件,只需要把后面的java文件替换为class即可,但对于class文件我们只需要确保它们在classpath下,并且指定对应的class名称即可,所以对于上述的示例,如果改为使用class文件生成schema,我们只需要定位到class文件根目录,运行如下指令即可。不需要单独指定SuperClass,因为它已经可以被RootObject关联到了。
schemagen com.elim.jaxb.RootObject com.elim.jaxb.SubClass1 com.elim.jaxb.SubClass2
不管是基于java文件还是基于class文件生成schema,都需要确保它们中应用的class都是在类路径下的。
以上只是一个简单的示例,接下来我们来看以下schemagen的语法,其语法如下:
schemagen [options] javafiles
其中的options支持以下选项,为保持原文语义,直接摘自官方文档:
基于Schema文件生成对应的Java代码,可以通过JDK自带的xjc工具来生成。xjc的语法是:
xjc [options] schemaFile [-b bindinfo]
以下参数信息的说明摘自官方网站。
可选的options如下:
更详细的信息请参考官方文档。
以下是一份schema文档,我们可以通过xjc -p com.elim.jaxb schema.xsd来指定生成的类都放在com.elim.jaxb包中。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root" type="rootObject"/>
  <xs:element name="sub1" type="subClass1"/>
  <xs:element name="sub2" type="subClass2"/>
  <xs:complexType name="rootObject">
    <xs:sequence>
      <xs:element name="prop1" type="xs:string" minOccurs="0"/>
      <xs:element name="prop2" type="xs:string" minOccurs="0"/>
      <xs:choice>
        <xs:element ref="sub1"/>
        <xs:element ref="sub2"/>
      </xs:choice>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="superClass">
    <xs:sequence>
      <xs:element name="superProp1" type="xs:string" minOccurs="0"/>
      <xs:element name="superProp2" type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="subClass1">
    <xs:complexContent>
      <xs:extension base="superClass">
        <xs:sequence>
          <xs:element name="subProp1" type="xs:string" minOccurs="0"/>
          <xs:element name="subProp2" type="xs:string" minOccurs="0"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:complexType name="subClass2">
    <xs:complexContent>
      <xs:extension base="superClass">
        <xs:sequence>
          <xs:element name="subProp1" type="xs:string" minOccurs="0"/>
          <xs:element name="subProp2" type="xs:string" minOccurs="0"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
</xs:schema>
生成的java类如下:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "rootObject", propOrder = {
    "prop1",
    "prop2",
    "sub1",
    "sub2"
})
public class RootObject {
    protected String prop1;
    protected String prop2;
    @XmlElement(namespace = "http://elim.com/jaxb")
    protected SubClass1 sub1;
    @XmlElement(namespace = "http://elim.com/jaxb")
    protected SubClass2 sub2;
    public String getProp1() {
        return prop1;
    }
    public void setProp1(String value) {
        this.prop1 = value;
    }
    public String getProp2() {
        return prop2;
    }
    public void setProp2(String value) {
        this.prop2 = value;
    }
    public SubClass1 getSub1() {
        return sub1;
    }
    public void setSub1(SubClass1 value) {
        this.sub1 = value;
    }
    public SubClass2 getSub2() {
        return sub2;
    }
    public void setSub2(SubClass2 value) {
        this.sub2 = value;
    }
}
通过Schema生成的RootObject类跟我们原本定义的RootObject类还是有一点出入的,这可以根据我们的实际需要在生成的类的基础上再加以调整。
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "superClass", propOrder = {
    "superProp1",
    "superProp2"
})
@XmlSeeAlso({
    SubClass2 .class,
    SubClass1 .class
})
public class SuperClass {
    protected String superProp1;
    protected String superProp2;
    public String getSuperProp1() {
        return superProp1;
    }
    public void setSuperProp1(String value) {
        this.superProp1 = value;
    }
    public String getSuperProp2() {
        return superProp2;
    }
    public void setSuperProp2(String value) {
        this.superProp2 = value;
    }
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "subClass1", propOrder = {
    "subProp1",
    "subProp2"
})
public class SubClass1
    extends SuperClass
{
    protected String subProp1;
    protected String subProp2;
    public String getSubProp1() {
        return subProp1;
    }
    public void setSubProp1(String value) {
        this.subProp1 = value;
    }
    public String getSubProp2() {
        return subProp2;
    }
    public void setSubProp2(String value) {
        this.subProp2 = value;
    }
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "subClass2", propOrder = {
    "subProp1",
    "subProp2"
})
public class SubClass2
    extends SuperClass
{
    protected String subProp1;
    protected String subProp2;
    public String getSubProp1() {
        return subProp1;
    }
    public void setSubProp1(String value) {
        this.subProp1 = value;
    }
    public String getSubProp2() {
        return subProp2;
    }
    public void setSubProp2(String value) {
        this.subProp2 = value;
    }
}
下面是生成的package-info.java的内容。
@javax.xml.bind.annotation.XmlSchema(namespace = "http://elim.com/jaxb")
package com.elim.jaxb;
@XmlRegistry
public class ObjectFactory {
    private final static QName _Sub2_QNAME = new QName("http://elim.com/jaxb", "sub2");
    private final static QName _Root_QNAME = new QName("http://elim.com/jaxb", "root");
    private final static QName _Sub1_QNAME = new QName("http://elim.com/jaxb", "sub1");
    public ObjectFactory() {
    }
    public SubClass2 createSubClass2() {
        return new SubClass2();
    }
    public RootObject createRootObject() {
        return new RootObject();
    }
    public SubClass1 createSubClass1() {
        return new SubClass1();
    }
    public SuperClass createSuperClass() {
        return new SuperClass();
    }
    @XmlElementDecl(namespace = "http://elim.com/jaxb", name = "sub2")
    public JAXBElement<SubClass2> createSub2(SubClass2 value) {
        return new JAXBElement<SubClass2>(_Sub2_QNAME, SubClass2 .class, null, value);
    }
    @XmlElementDecl(namespace = "http://elim.com/jaxb", name = "root")
    public JAXBElement<RootObject> createRoot(RootObject value) {
        return new JAXBElement<RootObject>(_Root_QNAME, RootObject.class, null, value);
    }
    @XmlElementDecl(namespace = "http://elim.com/jaxb", name = "sub1")
    public JAXBElement<SubClass1> createSub1(SubClass1 value) {
        return new JAXBElement<SubClass1>(_Sub1_QNAME, SubClass1 .class, null, value);
    }
}
无论是通过Java生成Schema,还是通过Schema生成Java,生成出来的跟预期的可能有点小出入,但它们都是正确的,在保证正确的基础上,我们可以对生成出来的内容进行一些细微的调整。
Schema文件是XML内容的定义,在进行marshal和unmarshal时我们可以通过指定对应的Schema文件进行Schema校验,这样当对应的XML不满足Schema的规定时就会转换失败。下面的示例就是在unmarshal的时候指定了需要用来做校验的Schema文件,SchemaFactory不是线程安全的,但是Schema是线程安全的。在marshal的过程中也可以通过Marshaller指定对应的Schema。
@Test
public void testUnmarshal() throws Exception {
    JAXBContext jaxbContext = JAXBContext.newInstance(RootObject.class);
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    URL resource = this.getClass().getResource("/schema.xsd");
    Schema schema = schemaFactory.newSchema(resource);
    //指定需要用来做校验的Schema
    unmarshaller.setSchema(schema);
    URL xml = this.getClass().getResource("/test.xml");
    unmarshaller.unmarshal(xml);
}
(完)