JiBX 的实操
- 介绍
- 基本概念
- BECL 介绍
- JiBX 基础概念
- 开发
- jar 依赖
- BECL
- JiBX
- POJO 实体类
- Order
- Address
- Customer
- Shipping
- OrderFactory 工厂类
- 使用ant 来生成XML和POJO对象的绑定关系
- idea 使用ant
- ant 脚本 build.xml
- ant 添加 build.xml
- binding.xml
- 报错问题解决
- 测试
- TestOrder
- 测试结果 如图:
- 错误解决
- Bug遗留
介绍
JiBX是一款非常优秀的XML(Extensible Markup Language)数据绑定框架。它提供灵活的绑定映射文件,实现数据对象与XML文件之间的转换,并不需要修改既有的Java类。并且转换效率极高。
XML 已经成为目前程序开发配置的重要组成部分了,JiBX 操作XML 的优点有:
(1)转换效率高
(2)配置绑定文件简单
(3)不需要操作xpath文件
(4)不需要写属性的get/set 方法
(5)对象属性名与XML文件element名可以不同等
基本概念
使用JiBX 绑定XML文档与Java对象需要分两步走:
(1)绑定XML文件,也就是映射XML文件与Java对象之间的对应关系
(2)在运行时,实现XML文件与Java实例之间的互相转换。我们要的就是能够 实现 这个功能。
BECL 介绍
在运行程序之前,需要先配置绑定文件并进行绑定,在绑定过程中它将会动态地修改程序中相应的class文件,主要是生成对应对象实例的方法和添加被绑定标记的属性JiBX_bindingList。 使用的技术是BECL(Byte Code Engineering Library),BCEL 是Apache Software Foundation的Jakarta 项目的一部分,也是目前Java classworking 最广泛使用的一种框架,它可以让我们深入JVM汇编语言进行操作。
JiBX 基础概念
Unmarshal 数据分解,将XML文件转换成Java对象
Marshal 数据编排 将Java对象编排成规范的XML文件。
JiBX 在 Unmarshal/Marshal 上如此高效,这要归功于使用了XPP(Xml Pull Parsing)技术,而不是使用基于树形方式,将整个文档写入内存,然后进行操作的DOM。也不是使用基于事件流的SAX。XPP使用的是不断增加的数据流处理方式,同时允许在解析XML文件时中断。
开发
jar 依赖
BECL
<dependency>
<groupId>org.apache.bcel</groupId>
<artifactId>bcel</artifactId>
<version>6.7.0</version>
</dependency>
JiBX
<dependency>
<groupId>org.jibx</groupId>
<artifactId>jibx-run</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>org.jibx</groupId>
<artifactId>jibx-extras</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>org.jibx</groupId>
<artifactId>jibx-bind</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>org.jibx</groupId>
<artifactId>jibx-tools</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>org.jibx</groupId>
<artifactId>jibx-schema</artifactId>
<version>1.4.2</version>
</dependency>
POJO 实体类
Order
public class Order {
private long orderNumber;
private Customer customer;
/**
* Billing address information
*/
private Address bilTo;
private String shipping;
/**
* Shipping address information. If missing ,the billing address is also
* used as the shipping address.
*/
private Address shipTo;
private Float total;
public long getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(long orderNumber) {
this.orderNumber = orderNumber;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public Address getBilTo() {
return bilTo;
}
public void setBilTo(Address bilTo) {
this.bilTo = bilTo;
}
public String getShipping() {
return shipping;
}
public void setShipping(String shipping) {
this.shipping = shipping;
}
public Address getShipTo() {
return shipTo;
}
public void setShipTo(Address shipTo) {
this.shipTo = shipTo;
}
public Float getTotal() {
return total;
}
public void setTotal(Float total) {
this.total = total;
}
@Override
public String toString() {
return "Order{" +
"orderNumber=" + orderNumber +
", customer=" + customer +
", bilTo=" + bilTo +
", shipping=" + shipping +
", shipTo=" + shipTo +
", total=" + total +
'}';
}
}
Address
public class Address {
/**
* First line of street information (required).
*/
private String street1;
/**
* Second line of street information (optional).
*/
private String street2;
private String city;
/**
* State abbreviation (required for the U.S. and Canada, optional otherwise )
*/
private String state;
/**
* Postal code (required for the U.S. and Canada ,optional otherwise ).
*/
private String postCode;
/**
* Country name (optional ,U.S. assumed if not supplied).
*/
private String country;
public String getStreet1() {
return street1;
}
public void setStreet1(String street1) {
this.street1 = street1;
}
public String getStreet2() {
return street2;
}
public void setStreet2(String street2) {
this.street2 = street2;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getPostCode() {
return postCode;
}
public void setPostCode(String postCode) {
this.postCode = postCode;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
public String toString() {
return "Address{" +
"street1='" + street1 + '\'' +
", street2='" + street2 + '\'' +
", city='" + city + '\'' +
", state='" + state + '\'' +
", postCode='" + postCode + '\'' +
", country='" + country + '\'' +
'}';
}
}
Customer
public class Customer {
private long customerNumber;
/**
* Personal name
*/
private String firstName;
/**
* Family name .
*/
private String lastName;
/**
* Middle name(s) if any.
*/
private List<String> middleNames;
public long getCustomerNumber() {
return customerNumber;
}
public void setCustomerNumber(long customerNumber) {
this.customerNumber = customerNumber;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public List<String> getMiddleNames() {
return middleNames;
}
public void setMiddleNames(List<String> middleNames) {
this.middleNames = middleNames;
}
@Override
public String toString() {
return "Customer{" +
"customerNumber=" + customerNumber +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", middleNames=" + middleNames +
'}';
}
}
Shipping
public class Shipping {
public final static String STANDARD_MAIL="STANDARD_MAIL";
public final static String PRIORITY_MAIL="PRIORITY_MAIL";
public final static String INTERNATIONAL_MAIL="StringINTERNATIONAL_MAIL";
public final static String DOMESTIC_EXPRESS="DOMESTIC_EXPRESS";
public final static String INTERNATIONAL_EXPRESS="INTERNATIONAL_EXPRESS";
}
OrderFactory 工厂类
public class OrderFactory {
public static Order create(int number){
Order order=new Order();
//orderNumber
order.setOrderNumber(number);
//total
order.setTotal(9999.999f);
//customer
Customer customer=new Customer();
customer.setFirstName("echo");
customer.setLastName("tong");
List<String>list=new ArrayList<>();
list.add("666");
// list.add("555");
customer.setCustomerNumber(123);
//customer.setMiddleNames(list);
order.setCustomer(customer);
//billTo
Address billTo=new Address();
billTo.setStreet1("西乡大道");
billTo.setCity("深圳市");
billTo.setState("广东省");
billTo.setPostCode("123321");
billTo.setCountry("中国");
order.setBilTo(billTo);
//shipping
order.setShipping(Shipping.INTERNATIONAL_MAIL);
//shipTo
Address shipTo=new Address();
shipTo.setStreet1("西乡大道");
shipTo.setCity("深圳市");
shipTo.setState("广东省");
shipTo.setPostCode("123321");
shipTo.setCountry("中国");
order.setShipTo(shipTo);
return order;
}
}
使用ant 来生成XML和POJO对象的绑定关系
idea 使用ant
idea 里面有自带的ant 工具。打开方式 View —Tool Windows–Ant
打开后,右上角就会有一个蚂蚁图标。如下图
ant 脚本 build.xml
需要修改的地方主要有两个:
一个是 jibx和bcel 依赖的路径。
另一个是POJO实体类的全路径
<?xml version="1.0" encoding="utf-8"?>
<project default="main" basedir=".">
<path id="classpath">
<dirset dir="${basedir}/target/classes" />
<!--由于不需要编译单元测试代码,就注掉了下面的内容-->
<!--<dirset dir="${basedir}/target/test-classes" />-->
<!--下面目录为本地maven仓库的jibx和bcel的jar包的绝对路径 同学们也可以采用相对路径-->
<fileset dir="D:/ProgramFiles/apache-maven-3.6.3/conf/repository/org/jibx/jibx-bind/1.4.2/" includes="*.jar" />
<fileset dir="D:/ProgramFiles/apache-maven-3.6.3/conf/repository/org/jibx/jibx-run/1.4.2/" includes="*.jar" />
<fileset dir="D:/ProgramFiles/apache-maven-3.6.3/conf/repository/org/apache/bcel/bcel/6.0/" includes="*.jar" />
</path>
<!--这个是主任务 , depends 依赖下面写的三个分任务 -->
<target name="main" depends="compile,bindgen,bind" description="Main target" />
<target name="compile" description="Compilation target">
<echo>Building file.</echo>
<!--相当于运行 javac命令进行源码编译-->
<javac srcdir="${basedir}/src/main/java" destdir="${basedir}/target/classes" includeantruntime="true" />
</target>
<target name="bindgen">
<echo message="Running BindGen tool" />
<!--
相当于运行Java命令生成binding.xml文件 类似于网上说的如下命令 ->
java -cp ..libx-tools.jar ..BindGen -t 生成文件保存地址 -v 需要绑定文件的class文件 完整包名.类名
-->
<java classpathref="classpath" fork="true" classname="org.jibx.binding.BindingGenerator">
<!-- 此处写需要生成映射文件的实体类的全类名-->
<arg value="echo.cn.http.xml.pojo.Address" />
<arg value="echo.cn.http.xml.pojo.Customer" />
<arg value="echo.cn.http.xml.pojo.Order" />
<arg value="echo.cn.http.xml.pojo.Shipping" />
</java>
</target>
<target name="bind">
<!--将实体类的class和xml映射文件进行绑定-->
<echo message="Running bind" />
<taskdef name="bind" classname="org.jibx.binding.ant.CompileTask">
<classpath refid="classpath">
</classpath>
</taskdef>
<bind binding="${basedir}/binding.xml">
<classpath refid="classpath"/>
</bind>
</target>
</project>
建议放在根目录下,先和我们保持一致,熟练后可以自定义修改。位置如下图所示:
ant 添加 build.xml
首先 在ant 里面添加build.xml.
然后依次执行 bindgen和bind命令。其中bindgen命令会生成binding.xml文件。即实体类和XML的绑定关系 的描述文件。
bind 命令 会动态的修改实例类的class文件,在里面添加对应的 JiBX_bindingList 属性值,并动态实现 IUnmarshallable和IMarshallable接口,同时会生成实体类的access文件等。
binding.xml
<?xml version="1.0" encoding="UTF-8"?>
<binding value-style="attribute">
<mapping class="echo.cn.http.xml.pojo.Address" name="address">
<value style="element" name="street1" field="street1" usage="optional"/>
<value style="element" name="street2" field="street2" usage="optional"/>
<value style="element" name="city" field="city" usage="optional"/>
<value style="element" name="state" field="state" usage="optional"/>
<value style="element" name="post-code" field="postCode" usage="optional"/>
<value style="element" name="country" field="country" usage="optional"/>
</mapping>
<mapping class="echo.cn.http.xml.pojo.Customer" name="customer">
<value name="customer-number" field="customerNumber"/>
<value style="element" name="first-name" field="firstName" usage="optional"/>
<value style="element" name="last-name" field="lastName" usage="optional"/>
<collection field="middleNames" usage="optional" factory="org.jibx.runtime.Utility.arrayListFactory"/>
</mapping>
<mapping class="echo.cn.http.xml.pojo.Order" name="order">
<value name="order-number" field="orderNumber"/>
<structure field="customer" usage="optional"/>
<structure field="bilTo" usage="optional"/>
<value style="element" name="shipping" field="shipping" usage="optional"/>
<structure field="shipTo" usage="optional"/>
<value name="total" field="total" usage="optional"/>
</mapping>
<mapping class="echo.cn.http.xml.pojo.Shipping" name="shipping"/>
</binding>
报错问题解决
bindgen 命令可能会报路径不存在。此时需要先编译,生成target目录。可以用Maven的compile命令先执行生成对应的目录即可。
测试
前面 铺垫工作做好了后,我们就来测试以下。
TestOrder
public class TestOrder {
private IBindingFactory factory=null;
private StringWriter writer=null;
private StringReader reader=null;
private final static String CHARSET_NAME="UTF-8";
private String encode2Xml(Order order) throws JiBXException, IOException {
factory= BindingDirectory.getFactory(Order.class);
writer=new StringWriter();
IMarshallingContext context=factory.createMarshallingContext();
context.setIndent(2);
// marshal 将实体类编码成XML字符
context.marshalDocument(order,CHARSET_NAME,null,writer);
String xmlStr=writer.toString();
writer.close();;
System.out.println(xmlStr.toString());
return xmlStr;
}
private Order decode2Order(String xmlBody) throws JiBXException {
reader =new StringReader(xmlBody);
IUnmarshallingContext context = factory.createUnmarshallingContext();
// unmarshalDocument 将XML 字符解码为POJO实体
Order order=(Order) context.unmarshalDocument(reader);
return order;
}
public static void main(String[] args) throws JiBXException, IOException {
TestOrder testOrder=new TestOrder();
Order order= OrderFactory.create(123);
String body=testOrder.encode2Xml(order);
Order order1=testOrder.decode2Order(body);
System.out.println(order1);
}
}
测试结果 如图:
说明 JiBX Unmarshal 数据分解 和 Marshal 数据编排 成功了。
错误解决
如果报缺少 JiBX_bindingList 。 说明之前的ant bind 命令执行没有成功。需要重新执行bind 命令。
Bug遗留
目前这个Order 实体里面有 Float ,String 和其他普通POJO 属性。 JiBX 是支持的。也是可用供大家学习实操的。
但是 不支持 集合属性和 枚举属性。
集合属性报 java.lang.String cannot be cast to org.jibx.runtime.IMarshallable 异常。 大家可以在Customer 给middleNames 属性赋值试下即可。
枚举属性报 该枚举没有可用的构造方法。大家可以将 Shipping 改为enum 试下。
如果有大神知道如何解决这两个问题,不胜感激。