Если требуется создать 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 комментариев:
Не совсем ясно как полученным классом дальше пользоваться...
Тоже только через reflection?
Да, только через reflection, хотя можно и заранее существующий интерфейс добавить и тогда работать с ним. Вообще такая генерация мне понадобилась при передаче объекта с динамической структурой flex-клиенту.
Спасибо за полезную информацию
очень интересно!
если я не ошибаюсь то
для передачи flex клиенту динамического объекта можно использовать java.util.Map
а как на счёт Proxy-объектов и cglib ?
@agahov: для передачи во/из java из/во flex java.util.Map достаточно - про flex ли вообще идёт тема ?
Конечно можно было использовать и net.sf.cglib.beans.BeanGenerator из cglib. Я решил опробовать JavaAssist, т.к. именно эту библиотеку используют как альтернативу cglib, в проектах типа hibernate и tapestry.
Отправить комментарий