- PVSM.RU - https://www.pvsm.ru -
Сидел я вчера на очередном интервью, грустил, что javax [1] в меня какашками кидается [2], и слушал печальную историю соискателя о том, как он мучался пытаясь прикрутить сериализацию в JSON к модели на Java не имея ее исходников. От вида его попыток настроение мое не улучшилось.
Мы попробуем лучше, потому что, в отличие от него, мы знаем про Groovy.
Этот пост является более-менее продолжением вчерашнего [3]. Им хотелось бы убить мешок зайцев:
Имеем мы модель из JavaBean-ов написаную на Java, и для примера допустим, что сорцов у нас нет. Также для интересу предположим что у этой модели нет общего интерфейса и/или суперкласса, и все что эту модель объеденяет это package, так что забудьте про всякий полиморфизм. Для примера допустим, что в модели аж 2 класса — model.Clock и model.Radio, оба extend Object, и у них есть парочка полей — стринги и примитивы.
Вот тест, который будет проходить к концу этой статьи:
import model.Radio
import service.Service
Radio radio = new Radio()
radio.frequency = 91.2
radio.volume = 20
def json = radio.toJson()
assert json == '{"frequency":91.2,"volume":20}'
Radio radioClone = Radio.parseJson(json)
assert radioClone == radio
try {
new Service().toJson()
assert false
} catch (MissingMethodException e){}
Естественно, ни метода toJson(), ни метода parseJson() в классе Radio не существует, и поэтому наш тест падает с «groovy.lang.MissingMethodException: No signature of method: model.Radio.toJson() is applicable for argument types: () values: []».
Проверка с try в конце — у класса Service, не относящегося к package-у model не должены отрости методы toJson и parseJson в результате нашего шаманства.
Как вы, конечно, уже знаете (как минимум со вчерашнего дня) что с помощью MetaClass-а любого класса можно добавлять и заменять методы. Значит, нам нужно добавить методы toJson() и parseJson() в MetaClass-ы всех классов из package model. А как? В Java с этим все плохо — нельзя получить список классов у package. Ну, то есть можно, но не совсем, не всегда, только из jar-ов, и еще куча ограничений и простыня кода. Это все не наш метод. Мы — Groovy.
Groovy совершенно точно вклинивается в classloading. Например, для создания MetaClass-ов. И раз уж мы все равно собрались с ними пошаманить, мы можем вклиниться в процесс их создания!
В классе groovy.lang.MetaClassRegistry смотрим на код создания Handler-a, который занимается созданием MetaClass-ов (я слегка код упростил, но смысл вот):
try {
Class customHandle = Class.forName("groovy.runtime.metaclass.CustomMetaClassCreationHandle");
this.handle = customHandle.newInstance();
} catch (ClassNotFoundException e) { //нету custom handler, будем использовать стандартный
this.metaClassCreationHandle = new MetaClassCreationHandle();
}
— Шерлок, но что это?
— Это элементарно, Ватсон. Это — один из extension points, позволяющий нам написать наш собственный MetaClassCreationHandle, в котором мы будем создавать наши собственные MetaClassы, с новыми методами и джейсоном.
Тут все ясно, Groovy ищет класс под названием «groovy.runtime.metaclass.CustomMetaClassCreationHandle», и мы ему его дадим:
public class CustomMetaClassCreationHandle extends MetaClassRegistry.MetaClassCreationHandle {
protected MetaClass createNormalMetaClass(Class theClass, MetaClassRegistry registry) {
Package thePackage = theClass.getPackage();
if (thePackage != null && thePackage.getName().equals("model")) {
return new Jsonizer(theClass);
} else {
return super.createNormalMetaClass(theClass, registry);
}
}
}
Если package наш — даем свой MetaClass, если нет — родной. Я считаю — зачет.
Обратите внимание на дурацкие точки-с-запятой. Все верно, CustomMetaClassCreationHandle обязан быть классом Java, не Groovy, потому как тут курица и яйцо — для создания Groovy класса нужен уже созданный MetaClassCreationHandle.
Все что нам осталось, это написать наш собственый MetaClass, в котором мы добавим один обычный (instance) и один static метод.
Тут, наверное надо набросать небольшой план работ:
А вот вам список вопросов и ответов по плану работ (походу, я люблю списки):
Смотрим:
(нововеденная поддержка «собачки» для маркировки юзеров ломает код объявления аннотаций, так что там «а» вместо сами знаете чего. Ну, вы поняли.)
аgroovy.lang.Grapes([
аGrabResolver(name = 'rjo', root = 'http://repo.jfrog.org/artifactory/libs-releases'),
аGrab(group = 'net.sf.json-lib', module = 'json-lib', version = '2.4', classifier = 'jdk15')])
class Jsonizer extends ExpandoMetaClass {
Jsonizer(Class theClass) {
super(theClass)
toJson << {->
fromObject(delegate).toString()
}
static.parseJson << {String json ->
toBean(fromObject(json), theClass);
}
}
}
Ну, в общем-то profit. Тест проходит, у Clock и Radio отросли методы, у Service — нет, а вы, я надеюсь, получили удовольствие от процесса.
Автор: jbaruch
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/8117
Ссылки в тексте:
[1] javax: http://habrahabr.ru/users/javax/
[2] в меня какашками кидается: http://habrahabr.ru/post/144297/#comment_4842901
[3] вчерашнего: http://habrahabr.ru/post/144297
[4] json-lib: http://json-lib.sourceforge.net/groovy.html
[5] dependency management для бедных: http://groovy.codehaus.org/Grapes+and+grab()
Нажмите здесь для печати.