Elim的博客

用来记录一些原创性的总结


Elim

Java与Schema相互转换

从面向对象的角度来讲,Java类的定义与XML的Schema文件的定义是一致的,都是用来定义具体对象的结构的。而具体的Java对象则将跟XML文件相对应,XML的具体内容则是由对应的Schema文件约束的。通过Java对象可以转换为XML,也可以通过对应的Java类定义转换为XML对应的Schema文件。同样的,我们可以通过XML转换为对应的Java对象,也可以通过XML对应的Schema文件转换对应的Java类定义。

Java转换为Schema

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

基于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);
}

(完)