用来记录一些原创性的总结
摘要 本文主要介绍基于package创建JAXBContext,以及阐述JAXBContext存在的性能问题及其优化。
JAXBContext是我们在使用JAXB时的入口类,我们需要通过它的实例来建立XML和Java类之间的映射关系,需要通过它来创建用于转换Java对象到XML的Marshaller或是创建用于转换XML到Java对象的Unmarshaller。JAXBContext的实例需要通过JAXBContext.newInstance(..)方法产生,JAXBContext中定义了重载的5个newInstance(..)方法,定义如下:
public static JAXBContext newInstance( String contextPath );
public static JAXBContext newInstance( String contextPath, ClassLoader classLoader );
public static JAXBContext newInstance( String contextPath, ClassLoader classLoader, Map<String,?> properties );
public static JAXBContext newInstance( Class... classesToBeBound );
public static JAXBContext newInstance( Class[] classesToBeBound, Map<String,?> properties );
上述5个方法的区别在于是基于package的还是基于class的。contextPath用于指定需要绑定XML的Java类所在的包定义;classLoader用于指定使用的类加载器,未指定时将使用当前线程上下文所绑定的类加载器;properties用于指定JAXB实现者特定的属性;classesToBeBound用于直接指定需要绑定的Java类。
之前介绍的都是通过直接指定相关的Class作为入参的形式创建JAXBContext,如下所示:
JAXBContext jaxbContext = JAXBContext.newInstance(Response.class, Person.class);
这里着重介绍一下基于package创建JAXBContext的示例,如下所示:
JAXBContext jaxbContext = JAXBContext.newInstance("com.xxx.jaxb");
但是这时候并不是指定的包中所有的Class都会用来创建JAXBContext。按照JAXB的规范,我们需要在对应的包中创建一个jaxb.index文件,然后在其中指定创建JAXBContext时需要用到的Class,每个Class名称占一行,只需要写Class名称即可,如我们在创建JAXBContext时需要用到Response和Person类,我们就可以中jaxb.index文件中如下定义:
Response
Person
然后后续就可以用对应的JAXBContext来进行相应的Marshal和UnMarshal操作了。通过指定包名称创建JAXBContext的方式可以把程序中各种可能的场景中需要使用到的Class都初始化到一个JAXBContext中,这样可以保证这个JAXBContext可以在各种可能的场景下都能满足需求,JAXBContext的初始化是非常耗CPU的,重用JAXBContext有助于提升性能。另一方面我们如果在某一时刻调整使用的Class时可以不用修改源码,直接修改jaxb.index文件中定义的Class即可,这可比修改源码要简单的多。
为了保证文章的完整性,以下给出一个基于包名称初始化JAXBContext的完整示例。
@Test
public void testPerson() throws Exception {
Person person = new Person();
person.setId(1);
person.setName("张三");
Response response = this.newResponse(person);
JAXBContext jaxbContext = JAXBContext.newInstance("com.xxx.jaxb");
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(response, System.out);
}
private Response newResponse(Data realData) {
Response response = new Response();
response.setReturnCode(100);
response.setMessage("AAAA");
ResultData resultData = new ResultData();
resultData.setRealData(realData);
response.setResultData(resultData);
return response;
}
@XmlRootElement
public class Response {
private int returnCode;
private String message;
@XmlElement(name = "data")
private ResultData resultData;
//省略get/set
}
public class ResultData {
@XmlElementRef
private Data realData;
//省略get/set
}
public class Data {
}
@XmlRootElement(name="dept")
public class Person extends Data {
@XmlAttribute
private Integer id;
private String name;
//省略get/set
}
Response
Person
生成的XML如下:
<response>
<returnCode>100</returnCode>
<message>AAAA</message>
<data>
<dept id="1">
<name>张三</name>
</dept>
</data>
</response>
使用直接指定class创建JAXBContext的方式我们的代码可以是如下这样:
@Test
public void testPerson2() throws Exception {
Person person = new Person();
person.setId(1);
person.setName("张三");
Response response = this.newResponse(person);
JAXBContext jaxbContext = JAXBContext.newInstance(Person.class, Response.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(response, System.out);
}
通过package创建JAXBContext时也允许创建指定多个package,多个package之间通过英文冒号分隔,如下我们在创建JAXBContext时就同时指定了com.xxx.jaxb
和com.xxx.jaxb2
两个package。
JAXBContext jaxbContext = JAXBContext.newInstance("com.xxx.jaxb:com.xxx.jaxb2");
每次在创建JAXBContext实例时,JAXBContext内部都需要维护好Java类和XML之间的映射关系,这个操作是十分消耗性能的。幸运的是JAXBContext是线程安全的,可以共享。关于JAXBContext存在的性能问题笔者准备了如下测试代码:
@Test
public void testPerformance() throws Exception {
for (int j=0; j<5; j++) {
int times = 100000;
long t1 = System.currentTimeMillis();
for (int i=0; i<times; i++) {
JAXBContext jaxbContext = JAXBContext.newInstance(Root.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.marshal(new Root(), new StringWriter());
}
long t2 = System.currentTimeMillis();
System.out.println("不共用JAXBContext耗时:" + (t2-t1) + "ms");
long t3 = System.currentTimeMillis();
JAXBContext jaxbContext = JAXBContext.newInstance(Root.class);
for (int i=0; i<times; i++) {
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.marshal(new Root(), new StringWriter());
}
long t4 = System.currentTimeMillis();
System.out.println("共用JAXBContext耗时:" + (t4-t3) + "ms");
}
}
循环测试5次,每次分别在共享JAXBContext和不共享JAXBContext的情况下进行十万次marshal操作,由于这里我们不关心生成的XML,也避免输出到控制台或文件中的IO影响,这里每次都创建一个StringWriter,StringWriter是基于内存操作的。在笔者64位ubuntu17.10系统、处理器是Intel® Core™ i5-7500 CPU @ 3.40GHz × 4
、8GB内存的台式机下运行结果如下所示:
不共用JAXBContext耗时:17735ms
共用JAXBContext耗时:240ms
不共用JAXBContext耗时:16003ms
共用JAXBContext耗时:272ms
不共用JAXBContext耗时:15614ms
共用JAXBContext耗时:223ms
不共用JAXBContext耗时:15313ms
共用JAXBContext耗时:215ms
不共用JAXBContext耗时:15241ms
共用JAXBContext耗时:235ms
从测试结果我们可以很明显的看到共享JAXBContext的情况下性能有很明显的提升。共享JAXB时如果我们的所有的应用场景需要使用到的Class比较少时我们可以简单的在创建JAXBContext对象时传递所有需哟使用到的Class,把它保存起来之后都可以一直这样在我们的应用中使用它了。但是如果Class的定义在进行XML转换为Java对象时有冲突就不能这么做了,比如你有一个Person1和Person2两个Person类,它们映射的XML节点都是person,那么在把XML节点person转换为Java对象时是转换为Person1还是Person2呢?有这种场景获取Class相对较多时建议创建一个JaxbUtil工具类,所有的JAXBContext对象都由该工具类产生,在该工具类内部实现JAXBContext的重用,以下是一个简单的示例。
private static Map<List<Class<?>>, JAXBContext> jaxbContextMap = new ConcurrentHashMap<>();
public static JAXBContext getJAXBContext(Class<?> ...classes) throws JAXBException {
List<Class<?>> classList = new ArrayList<>(Arrays.asList(classes));
if (!jaxbContextMap.containsKey(classList)) {
JAXBContext jaxbContext = JAXBContext.newInstance(classes);
jaxbContextMap.put(classList, jaxbContext);
return jaxbContext;
}
return jaxbContextMap.get(classList);
}
需要注意的是虽然JAXBContext是线程安全的,但是它由它产生的Marshaller和Unmarshaller对象是线程不安全的,切勿进行重用。