Недавно пришлось распараллеливать одну штуку и наткнулся на очень неприятную особенность XmlBeans. Дело в том, что мы используем XmlBeans как... гм... парсер xml :) и объекты, которые он сгенерил, лежат у нас в конфигурации и используются многими потоками. В режиме только для чтения.
А XmlBeans в каждый сгенерённый метод всовывает вот такое:
/**
* Gets the "value" attribute
*/
public java.lang.String getValue()
{
synchronized (monitor())
{
check_orphaned();
org.apache.xmlbeans.SimpleValue target = null;
target = (org.apache.xmlbeans.SimpleValue)get_store().find_attribute_user(VALUE$0);
if (target == null)
{
return null;
}
return target.getStringValue();
}
}
Мягко говоря, такого от обычного getter метода мало кто ожидает. Особенно убил synchronized. Понятно, они защищают свою попу, к DOM дереву обращается всегда только один поток, но ведь можно дать юзеру выбор - хочу объект, из которого будет только браться, а писаться не будет - не надо синхронизировать.
В результате получается, что если объект конфигурации просто читать из множества потоков, то потоки иногда дожидаются секунды своей очереди, что я увидел в профайлере.
Как с этим бороться? Я выбрал просто способ (хотел сказать, элегантный, но язык не повернулся :). Таки вот, XmlBeans, молодец, он не просто генерит POJOs, как, например, JAXB. Он генерит интерфейсы, за что ему большое спасибо. И вот эта вся колбаса выше это реализация интерфейса. Так что ничто нам не мешает использовать другую реализацию. Так как классом у нас до чёртиков, то руками это делать не решился, а написал вот такое:
package com.anydoby.utilities;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
import org.apache.xmlbeans.XmlObject;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* This helper class allows copying of stuff from XmlObject instances to immutable POJOs which
* implement the same interface. Immutable here means that only get/is and sizeOf methods will be
* available.
*/
public class ImmutableUtils
{
private static final Pattern GETTER_PATTERN = Pattern.compile("(is|get)([A-Z].+)");
/**
* Makes an immutable copy of object which does not block when concurrently invoking getters.
*
* @param <E>
* @param object
* @return deep copy of the object
*/
@SuppressWarnings("unchecked")
public static <E extends XmlObject> E asImmutable(E object)
{
if (object == null)
{
return null;
}
try
{
Class< ? >[] interfaces = object.getClass().getInterfaces();
Assert.isTrue(interfaces.length == 1, "Only one interface is expected.");
String interfaze = interfaces[0].getName();
String immutableClassName = interfaze + "Immutable";
Class< ? > intfc = interfaces[0];
Class< ? > clazz = compileProxyClass(immutableClassName, intfc);
E copy = (E) clazz.getConstructors()[0].newInstance(object);
return copy;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
private static Class< ? > compileProxyClass(String immutableClassName, Class< ? > interfaze)
throws ClassNotFoundException, CannotCompileException, NotFoundException, InstantiationException,
IllegalAccessException
{
Class< ? > clazz;
try
{
clazz = Class.forName(immutableClassName);
}
catch (ClassNotFoundException e)
{
CtClass ctClass = createCopyClass(immutableClassName, interfaze);
clazz = ctClass.toClass();
}
return clazz;
}
public static List< ? > asImmutable(List< ? > list)
{
if (list == null)
{
return null;
}
List<Object> newList = new ArrayList<Object>();
for (Object e : list)
{
if (e instanceof XmlObject)
{
XmlObject object = asImmutable((XmlObject) e);
newList.add(object);
}
else
{
newList.add(e);
}
}
return newList;
}
public static Object asImmutable(Object o)
{
Object result;
if (o instanceof XmlObject)
{
result = asImmutable((XmlObject) o);
}
else if (o instanceof List)
{
result = asImmutable((List< ? >) o);
}
else
{
result = o;
}
return result;
}
private static CtClass createCopyClass(String className, Class< ? > intfc) throws CannotCompileException,
NotFoundException, InstantiationException, IllegalAccessException, ClassNotFoundException
{
ClassPool pool = ClassPool.getDefault();
pool.importPackage(ImmutableUtils.class.getName());
pool.importPackage(XmlObject.class.getName());
CtClass c = pool.makeClass(className);
CtClass xmlBeansInterface = pool.get(intfc.getName());
c.setInterfaces(new CtClass[]
{ xmlBeansInterface });
CtMethod[] methods = c.getMethods();
for (CtMethod ctMethod : methods)
{
String name = ctMethod.getName();
Matcher matcher = GETTER_PATTERN.matcher(name);
if ((matcher.matches() && !name.endsWith("Array")) || name.startsWith("sizeOf"))
{
CtClass declaringClass = ctMethod.getDeclaringClass();
if (declaringClass.getName().startsWith("org.apache.xmlbeans")
|| declaringClass.getName().startsWith("java.lang"))
{
continue;
}
CtClass returnType = ctMethod.getReturnType();
CtField field = new CtField(returnType, name, c);
c.addField(field);
CtMethod method = CtNewMethod.getter(name, field);
c.addMethod(method);
}
}
CtConstructor constructor = new CtConstructor(new CtClass[]
{ xmlBeansInterface }, c);
CtField[] fields = c.getDeclaredFields();
CtField field = new CtField(xmlBeansInterface, "delegate", c);
c.addField(field);
StringBuilder ctor = new StringBuilder();
ctor.append("{\n");
ctor.append("this.delegate = $1;\n");
for (CtField ctField : fields)
{
CtClass type = ctField.getType();
if (isSafeToAssignAsIs(type))
{
ctor.append("this." + ctField.getName() + " = $1." + ctField.getName() + "();");
}
else
{
ctor.append("this." + ctField.getName() + " = (" + ctField.getType().getName()
+ ")ImmutableUtils.asImmutable($1." + ctField.getName() + "());");
}
ctor.append('\n');
}
ctor.append("}");
constructor.setBody(ctor.toString());
c.addConstructor(constructor);
StringBuilder toString = new StringBuilder();
toString.append("public String toString() {return \"").append(intfc.getSimpleName()).append("[\"");
boolean first = true;
for (CtField ctField : fields)
{
String name = ctField.getName();
Matcher matcher = GETTER_PATTERN.matcher(name);
if (matcher.matches()) {
if (!first) {
toString.append("+\",\"");
}
toString.append("+\"").append(name).append("=\"+");
toString.append("java.lang.String.valueOf(this.").append(name).append(")");
first^=first;
}
}
toString.append("+\"]\";}");
CtMethod method = CtNewMethod.make(toString.toString(), c);
c.addMethod(method);
{
// we want to be able to use xpath
CtMethod selectPath = CtNewMethod.make("public XmlObject[] selectPath ( String path ){return delegate.selectPath(path);}", c);
c.addMethod(selectPath);
}
{
// we want to be able to use xmlText() - for comparisons
CtMethod xmlText = CtNewMethod.make("public String xmlText(){return delegate.xmlText();}", c);
c.addMethod(xmlText);
}
return c;
}
private static boolean isSafeToAssignAsIs(CtClass type) throws NotFoundException, ClassNotFoundException
{
if (type.isPrimitive() || type.isEnum() || isPrimitiveWrapperOrJavaLang(type))
{
return true;
}
if (type.isArray())
{
CtClass componentType = type.getComponentType();
return isSafeToAssignAsIs(componentType);
}
return false;
}
private static boolean isPrimitiveWrapperOrJavaLang(CtClass type) throws ClassNotFoundException
{
String name = type.getName();
if (name.startsWith("java.lang.")) {
return true;
}
Class< ? > clazz = Class.forName(name);
return ClassUtils.isPrimitiveOrWrapper(clazz);
}
}
Чего оно делает? Создает реализацию нужного интерфейса на ходу и копирует все свойства объекта куда надо. Получается immutable версия XmlObject. Для того, чтобы работал Xpath, есть еще selectPath, он работает на оригинале.
Вот и вся нелёгкая :)
