JAXB 线程安全问题解决办法

项目开发中涉及到对象与XML的转换,考虑到代码的简洁性,一直在使用java内置的JAXB来完成这项任务

一直在用的方法:

static Map<String,Marshaller> mars
static Map<String,Unmarshaller> umars

这样做的好处是对于相同的类名,不用重复创建marshaller。

但在一个银行项目上线中发现,经常会发生ArrayIndexOutOfBoundsException,经过google一番搜索,发现问题在于marshaller的marshal方法不是线程安全的,而JAXBContext.newInstance()是线程安全的。为了解决这一问题,需要将代码进行改造

为了尽量少的重复创建marshaller对象(创建marshaller对象的操作官方认为是比较耗CPU的)这里使用对象池的概念,开发包使用commons-pool,这里用到了KeyedObjectPool

static KeyedObjectPool marPool = new GenericKeyedObjectPool(new JaxbMarshallerFactory());
static KeyedObjectPool unmarPool = new GenericKeyedObjectPool(new JaxbUnmarshallerFactory());
class JaxbUnmarshallerFactory extends JaxbMarshallerFactory{
    @Override
    public Object makeObject(Object key) throws Exception {
        Class<?> clazz = (Class<?>)key;
        if(contextMap.containsKey(key)){
            JAXBContext context = JAXBContext.newInstance(clazz);
            contextMap.put(clazz, context);
        }
        return contextMap.get(clazz).createUnmarshaller();        
    }
}
class JaxbMarshallerFactory implements KeyedPoolableObjectFactory{

    protected Map<Class<?>,JAXBContext> contextMap = new HashMap<Class<?>,JAXBContext>();
    
    @Override
    public Object makeObject(Object key) throws Exception {
        Class<?> clazz = (Class<?>)key;
        if(!contextMap.containsKey(clazz)){
            JAXBContext context = JAXBContext.newInstance(clazz);
            contextMap.put(clazz, context);
        }
        return contextMap.get(clazz).createMarshaller();        
    }

    @Override
    public void destroyObject(Object key, Object obj) throws Exception {
        contextMap.remove(key);
    }

    @Override
    public boolean validateObject(Object key, Object obj) {            
        return true;
    }

    @Override
    public void activateObject(Object key, Object obj) throws Exception {
        // System.out.println("user "+Thread.currentThread()+" borrow "+obj.hashCode());
    }

    @Override
    public void passivateObject(Object key, Object obj) throws Exception {            
        // System.out.println("user "+Thread.currentThread()+" return "+obj.hashCode());
    }    
}

这里定义了两个对象池工厂,分别保存marshaller和unmarshaller,感觉应该可以合并到一个工厂中,后面的实践中再加以改进。

在使用时应该注意borrowObject并使用完成后一定要记得returnObject,这样才能达到对象池的效果

public static void toOutputStream(Object o,OutputStream os) throws JAXBException{        
    Marshaller mar;
    try {
        // 从线程池中借出
        mar = (Marshaller)marPool.borrowObject(o.getClass());
        // 转换对象到XML
        mar.marshal(o, os);
        // 归还到对象池
        marPool.returnObject(o.getClass(), mar);
    } catch (Exception e) {            
        logger.error(e);
    }    
}


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。