JAXB 创建上下文和编组器的成本

这个问题有点理论化,创建 JAXB 上下文、编组器和解组器的成本是多少?

我发现,对于所有封送处理操作,保持相同的 JAXB 上下文和可能相同的封送处理程序,而不是对每个封送处理创建上下文和封送处理程序,可能会使我的代码受益。

那么创建 JAXB 上下文和编组器/解组器的成本是多少呢?是否可以为每个封送处理操作创建上下文 + 封送处理程序,或者最好避免它?

55773 次浏览

注意: 我是 < strong > EclipseLink JAXB (MOXy) 的主管和 JAXB2(< strong > JSR-222 )专家组的成员。

JAXBContext是线程安全的,应该只创建一次并重用,以避免多次初始化元数据的成本。MarshallerUnmarshaller不是线程安全的,但是创建它们是轻量级的,每个操作都可以创建它们。

遗憾的是,javadoc 中没有具体描述这一点。我能说的是,Spring 使用全局 JAXBContext,在线程之间共享,而它为每个封送处理操作创建一个新的封送处理程序,代码中有一个 Javadoc 注释表示 JAXB 封送处理程序不一定是线程安全的。

这一页也说了同样的话: https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#other-miscellaneous-topics-performance-and-thread-safety

我认为创建 JAXBContext 是一项代价高昂的操作,因为它涉及到扫描类和包以获得注释。但是测量是了解的最好方法。

理想情况下,您应该有一个单一的 JAXBContextMarshallerUnmarshaller的本地实例。

JAXBContext实例是线程安全的,而 MarshallerUnmarshaller实例是 没有线程安全的,不应该跨线程共享。

我通常用 ThreadLocal类模式解决这样的问题。 考虑到您需要为每个 Class 使用不同的编组器这一事实,您可以将其与 singleton映射模式组合起来。

为了节省您15分钟的工作时间。

它允许您访问以下实例..。

Marshaller m = Jaxb.get(SomeClass.class).getMarshaller();
Unmarshaller um = Jaxb.get(SomeClass.class).getUnmarshaller();

您需要的代码是一个小的 Jaxb 类,它看起来如下:

public class Jaxb
{
// singleton pattern: one instance per class.
private static Map<Class,Jaxb> singletonMap = new HashMap<>();
private Class clazz;


// thread-local pattern: one marshaller/unmarshaller instance per thread
private ThreadLocal<Marshaller> marshallerThreadLocal = new ThreadLocal<>();
private ThreadLocal<Unmarshaller> unmarshallerThreadLocal = new ThreadLocal<>();


// The static singleton getter needs to be thread-safe too,
// so this method is marked as synchronized.
public static synchronized Jaxb get(Class clazz)
{
Jaxb jaxb =  singletonMap.get(clazz);
if (jaxb == null)
{
jaxb = new Jaxb(clazz);
singletonMap.put(clazz, jaxb);
}
return jaxb;
}


// the constructor needs to be private,
// because all instances need to be created with the get method.
private Jaxb(Class clazz)
{
this.clazz = clazz;
}


/**
* Gets/Creates a marshaller (thread-safe)
* @throws JAXBException
*/
public Marshaller getMarshaller() throws JAXBException
{
Marshaller m = marshallerThreadLocal.get();
if (m == null)
{
JAXBContext jc = JAXBContext.newInstance(clazz);
m = jc.createMarshaller();
marshallerThreadLocal.set(m);
}
return m;
}


/**
* Gets/Creates an unmarshaller (thread-safe)
* @throws JAXBException
*/
public Unmarshaller getUnmarshaller() throws JAXBException
{
Unmarshaller um = unmarshallerThreadLocal.get();
if (um == null)
{
JAXBContext jc = JAXBContext.newInstance(clazz);
um = jc.createUnmarshaller();
unmarshallerThreadLocal.set(um);
}
return um;
}
}

我用以下方法解决了这个问题:

  • 共享线程安全 JAXBContext和线程本地 联合国军司令
  • (所以理论上,访问 联合国调查员实例的线程数和访问它们的线程数一样多)
  • 只在 联合国调查员初始化时使用同步。
public class MyClassConstructor {
private final ThreadLocal<Unmarshaller> unmarshallerThreadLocal = new ThreadLocal<Unmarshaller>() {
protected synchronized Unmarshaller initialValue() {
try {
return jaxbContext.createUnmarshaller();
} catch (JAXBException e) {
throw new IllegalStateException("Unable to create unmarshaller");
}
}
};
private final ThreadLocal<Marshaller> marshallerThreadLocal = new ThreadLocal<Marshaller>() {
protected synchronized Marshaller initialValue() {
try {
return jaxbContext.createMarshaller();
} catch (JAXBException e) {
throw new IllegalStateException("Unable to create marshaller");
}
}
};


private final JAXBContext jaxbContext;


private MyClassConstructor(){
try {
jaxbContext = JAXBContext.newInstance(Entity.class);
} catch (JAXBException e) {
throw new IllegalStateException("Unable to initialize");
}
}
}

那更好了!基于上面提到的好的解决方案,在构造函数中创建上下文 就一次,并将其保存为类。

换掉这句话:

  private Class clazz;

用这个:

  private JAXBContext jc;

这个的主构造函数是:

  private Jaxb(Class clazz)
{
this.jc = JAXBContext.newInstance(clazz);
}

因此,在 getMarshall/getUnmaraller 中,您可以删除以下行:

  JAXBContext jc = JAXBContext.newInstance(clazz);

在我的例子中,这种改进使得处理时间从60 ~ 70ms 下降到仅仅5 ~ 10ms

JAXB 2.2(JSR-222)在“4.2 JAXBContext”章节中说:

为了避免创建 JAXBContext实例所涉及的开销,请使用 JAXB 应用程序是 鼓励重用 JAXBContext 实例.An 抽象类 JAXBContext < strong > 的实现必须是 线程安全的 ,因此,应用程序中的多个线程可以共享 相同的 JAXBContext 实例。

[..]

JAXBContext 类的设计是不可变的,因此是线程安全的。 考虑到可能需要的动态处理量 当创建 JAXBContxt 的新实例时,建议使用 JAXBContext 实例跨线程共享并作为 尽可能提高应用程序的性能。

遗憾的是,该规范没有对 UnmarshallerMarshaller的线程安全性做出任何声明。因此,最好假设它们并非如此。

在枚举中创建 JAXBContext 并在应用程序 Thread 中访问它以创建 Marshall/Unmaraller 就足够了。

public enum MyApplicationJAXBContext {


REQ(Request.class), RESP(Response.class);


private final JAXBContext jaxbContext;


MyApplicationJAXBContext(Class contextClass) {


try {
jaxbContext = JAXBContext.newInstance(contextClass);
} catch (JAXBException e)
throw new RunTimeException(e);
// Lets caller decide what to do ?
}
}


public JAXBContext getJaxbContext() {
return jaxbContext;
}
}




public class MyAppCallable implements Callable<Response> {


private final Request req;
public MyAppCallable(Request req) {
this.req = req;
}




public Response call() {


Marshaller marshaller = MyApplicationJAXBContext.REQ.getJaxbContext().createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// Anything else you want to configure properties
Unmarshaller unmarshaller = MyApplicationJAXBContext.RESP.getJaxbContext().createUnmarshaller();


/**
All other logic you want to do after req/rsp usage and return Response
**/


}


}