среда, 19 ноября 2008 г.

Генерация javabean в runtime

Если требуется создать javabean в runtime по набору полей, то никакими стандартными средствами java эту задачу решить не удастся.

По началу я решил, что с этой задачей может справиться какая-либо из реализаций org.apache.commons.beanutils.DynaBean. Однако работать с ним можно лишь так if (object instanceof DynaBean) ... . То есть, если модуль использующий этот объект работает с ним только через reflection и ничего не знает о DynaBean, то такое решение не подходит.

В итоге, было решено использовать Javassist. Очень удобный инструмент для подобного рода действий, позволяет производить как создание так и модификацию уже загруженных JVM классов. Предоставляет два вида API, на уровне исходного кода и собственно самого байткода. Использовать конечно лучше первый, т.к. мало кого устроит работать напрямую с байткодом.

В альтернативу classloader-у используется класс javassist.ClassPool, он позволяет не только загружать, но и создавать классы javassist.CtClass. При этом класс не будет доступен приложению сразу же, а лишь после преобразования в обычный класс java.lang.Class. Сам javassist.CtClass обладает богатыми возможностями для создания, удаления методов и полей, изменения наследования или даже названия уже загруженного класса. Для доступа ко всем классам загруженным JVM следует использовать ClassPool.getDefault(), хотя можно реализовать и свой, чтобы, например, загружать классы самостоятельно.

Итак, решение с использованием Javassist будет выглядеть так:

public Class<?> createClass(String className, Map<String, Class<?>> props) throws Exception {
ClassPool classPool = ClassPool.getDefault();
CtClass cc = classPool.makeClass(className);

for (Entry<String, Class<?>> entry : props.entrySet()) {
String name = entry.getKey();
Class<?> type = entry.getValue();
CtClass fieldType = classPool.get(type.getName());
CtField field = new CtField(fieldType, name, cc);
cc.addField(field);

String n = camelize(name);
CtMethod setter = CtNewMethod.setter("set" + n, field);
cc.addMethod(setter);
CtMethod getter = CtNewMethod.getter("get" + n, field);
cc.addMethod(getter);
}

return cc.toClass();
}

private String camelize(String s) {
return s.substring(0, 1).toUpperCase() + s.substring(1);
}

6 комментариев:

Andrey Rybin комментирует...

Не совсем ясно как полученным классом дальше пользоваться...
Тоже только через reflection?

Никита Кокшаров комментирует...

Да, только через reflection, хотя можно и заранее существующий интерфейс добавить и тогда работать с ним. Вообще такая генерация мне понадобилась при передаче объекта с динамической структурой flex-клиенту.

Анонимный комментирует...

Спасибо за полезную информацию

agahov комментирует...

очень интересно!

если я не ошибаюсь то
для передачи flex клиенту динамического объекта можно использовать java.util.Map

Владимир Долженко комментирует...

а как на счёт Proxy-объектов и cglib ?

@agahov: для передачи во/из java из/во flex java.util.Map достаточно - про flex ли вообще идёт тема ?

Никита Кокшаров комментирует...

Конечно можно было использовать и net.sf.cglib.beans.BeanGenerator из cglib. Я решил опробовать JavaAssist, т.к. именно эту библиотеку используют как альтернативу cglib, в проектах типа hibernate и tapestry.