HotLog

понедельник, 21 ноября 2011 г.

Git. Восстановление удаленных в предыдущих ревизиях файлов или обход Already up-to-date при мерже..

Иногда надо восстановить потертые несколько ревизий назад файлы, сохранив их историю. Попытка мержить с тем, что находится ниже по дереву ревизий приводит к ошибке Already up-to-date. Например. Выполним следующие команды: Вывод последней команды будет такой (у вас будет отличаться хешами ревизий): Итак. Допустим, мы вспомнили, что в ревизии 91185e8 зря удалили файл 1.txt и надо его восстановить. Причем, сохранив историю изменений. Если мы попробуем мерж с ревизией 4629fbed(где файл еще существовал), нам скажут, что уже все смержено: Выход в использовании более низкоуровневых команд гита. Алгоритм следующий:
  1. привести текущие файлы в интересующее нас состояние (в данном случае - восстановить 1.txt из ревизии 4629fbed)
    $ git checkout 4629fbedf7d57 1.txt
  2. записать текущее дерево в базу (выведет хеш записанного дерева)
    $ git write-tree
    82a6654d94d0425c747f1926279a9487bdc95c6a
  3. записать коммит с деревом, записанным в предыдущей ревизии, и парентами: текущая ревизия и ревизия, где файл еще существовал
    $ echo "merge" | git commit-tree 82a6654d -p 0301aba7 -p 4629fbed
    a75e4109b929b7b0f53b2bc0a8284fcf8569a5e
  4. перейти на новый коммит
    $ git reset a75e41
После чего можно просмотреть историю файла 1.txt c помощью git blame 1.txt, а увидеть дерево с помощью git log --graph.

пятница, 2 сентября 2011 г.

AspectJ и @NotNull

Контракт надо проверять. И наиболее часто встречающийся контракт метода - ненулевые значения параметров. Обычно проверка выглядит так: Таким образом на каждый параметр приходится три строчки дополнительного кода. Мало того, что один и тот же код дублируется из метода в метод, так и читаемость ухудшается. Решение: использовать аспекты и аннотировать нужные параметры @NotNull. Но что бы не потерять в производительности, надо отказаться от рефлекшина и накладывать условия во время исполнения:

суббота, 6 августа 2011 г.

Решение проблемы testable and encapsulation: Мочим создание объектов..

Исходный код лежит здесь: https://github.com/Fuud/new-instance-transformer (на момент поста ревизия 486d973)

Допустим у нас есть два класса:





Мы хотим протестировать метод CreateInConstructor.getText(). Для этого надо подменить classToMock на затычку-заглушку.

Мне известно два фреймворка, решающих эту проблему: PowerMock и JMockit. Первый не дружит с JMock, а второй представляет отдельную тестовую систему с кучей черной и белой магии. Резюмируя: надо писать что-то свое.

Что нужно:
1) уметь подменять создание нового объекта на получение откуда-то нового. javassist.CodeConvertor.replaceNew()
2) загружать агент при первой необходимости. com.sun.tools.attach.VirtualMachine.loadAgent (это часть attach api)
3) перетрансформировать классы после зарузки агента: Instrumentation.retransformClasses().

После реализации можно делать так:

пятница, 15 июля 2011 г.

Listeners Support (PropertyChangeSupport с произвольным типом листнеров)

Пусть у нас есть событие:


И слушатель:


Обычно, класс в который можно зарегистрировать слушатель выглядит как-то так:


Два метода fireEvent и fireChange мало того, что выглядят почти одинаково, так еще и имеют кучу таких же клонов в других классах. Повторяющийся код - это плохо. Хочется чего-то вроде этого:

Итог - минус полтора десятка строчек. Добиться этого можно с помощью java.lang.reflect.Proxy (предполагая, что Listener - это интерфейс).
Вот код решения:

И тест к нему:

пятница, 11 марта 2011 г.

Операция с ограниченной частотой вызова.

Иногда надо выполнять некоторую операцию не чаще, чем раз в столько-то времени. Например, если нам надо отображать банковскую котировку. Иногда в секунду прилетает сотня новых значений, иногда - котировка не меняется минутами.

Идея заключается в том, что при вызове операции, помещаем в очередь объект. А в другом потоке ждем, когда объект придет и, если с прошлого вызова до момента получения нового объекта прошло мало времени, спим остаток.

суббота, 19 февраля 2011 г.

Spring: lazy-init.

Рассмотрим вот этот проект.

ChildLazy объявлен как @Lazy, значит он будет загружен по первому требованию (но не раньше).
ChildNormal как @Lazy не объявлен, значит он будет загружен при старте приложения.
Composite так же не @Lazy.

Запустим SpringStart, вывод будет такой:
created LazyChild
created NormalChild
Composite is ready
Composite.printNormalChild
NormalChild.print()
Composite.printLazyChild
LazyChild.print()

Видно, что LazyChild загрузился одновременно с NormalChild. Случилось это потому, что Composite, являясь бином-синглетоном, требует LazyChild в качестве своего свойства.

Задачу решает создание ScopedProxy для LazyChild: таким образом в соответствующее свойство Composite будет проставлена только обертка, а сам LazyChild создастся только в том случае, если кто-нибудь вызовет какой-нибудь метод на прокси.
Выглядеть это будет так:


Запустив, получаем:
created NormalChild
Composite is ready
Composite.printNormalChild
NormalChild.print()
Composite.printLazyChild
created LazyChild
LazyChild.print()

Из последних трех строчек видно, что задача решена.

вторник, 8 февраля 2011 г.

Компилируем rt.jar c debug информацией.

Задача: добавить debug-информацию в rt.jar.
Шаги решения:
  1. Распаковываем src.zip в c:\_temp\jdk_src\
  2. Запускаем консоль в папке с распакованными файлами
  3. Выполняем в консоли "dir /S /B *.java > build.cmd" (без кавычек)
  4. Открываем build.cmd любимым текстовым редактором
  5. Заменяем "C:\_temp\jdk_src\" на "javac -g C:\_temp\jdk_src\"
  6. Запускаем build.cmd
  7. Долго ждем
  8. Добавляем с заменой скомпилированные файлы в rt.jar
  9. Пользуемся

Готовый rt.jar для jdk1.6.0_23 можно скачать тут.

суббота, 5 февраля 2011 г.

Spring-mvc 3.0: пишем простейшее web-приложение.

В этой заметке я предлагаю попробовать "на вкус" популярный веб-фреймворк swing-mvc.

Исходный код проекта можно скачать по этой ссылке

Напишем простейший калькулятор, позволяющий складывать два числа. Выглядеть он будет так:


Spring-mvc работает так:
  1. ищется метод-контроллер, который может обработать данный запрос
  2. в метод передаются параметры из запроса
  3. метод выполняется, возвращая модель (ModelMap)
  4. в соответствии с ViewResolver'ом ищется jsp, которая будет отображать ответ
  5. найденная jsp рендерится с учетом параметров из ModelMap

Будем использовать maven и структура проекта будет следующая:
C:\WORK\SIMPLE-SPRING-PROJECT
├───src
│   └───main
│       ├───java
│       │   └───com
│       │       └───blogspot
│       │           └───fuud
│       │               └───java
│       │                   └───simple
│       │                       └───spring
│       │                           └─────SampleController.java
│       └───webapp
│           └───WEB-INF
│               ├───pages
│               │   └────calc.jsp
│               ├────dispatcher-servlet.xml
│               └────web.xml
└───pom.xml

pom.xml выглядит так:


Я добавил в плагины jetty, что бы можно было запускать наше приложение прямо из консоли командой:


Следующий на очереди web.xml:


В web.xml я определяю, где находится главный spring-context, определяю DispatcherServlet как сервлет по умолчанию для всего приложения. Обратите внимание, что паттерн для сервлета задан как /, а не как /* - потому как все содержимое папки WEB-INF должно обрабатываться обычным образом.

На очереди calc.jsp:


И контроллер:


Аннотацией @Controller я говорю spring'у, что методы этого класса будут обрабатывать входящие запросы (какие именно, указанно в аннотации @RequestMapping). Параметры помечены как необязательные в виду того, что при первом входе на страничку пользователь еще ничего не ввел - и эту ситуацию надо корректно обрабатывать.

И, наконец, мы добрались до ядра нашего приложения dispatcher-servlet.xml:


Сначала я настраиваю спринг использовать аннотации:


Потом говорю в каком пакете он должен искать аннотированные файлы:


Потом определяю, как из реквеста получать файлы для отображения (в данном случае в начало имени добавится "/WEB-INF/pages/", а в конце добавится ".jsp"):


Вот и все: запускаем проект, выполнив в корне mvn jetty:run и заходим броузером на страничку
http://localhost:8080/simple-swing-project/calc
И радуемся спрингу!

суббота, 29 января 2011 г.

PropertyChangeSupport и AspectJ

Когда надо создать несколько java-beans с извещением об изменении свойств, обычно заводят общий класс-предок:


И тогда java-bean выглядит примерно так:


Налицо дублирование кода. Повторящийся шаблон ("XXX" - заменяет переменную часть):


Основная цель аспектов - убирать дублирующийся код, встречающийся во многих методах. Это как раз тот случай. Заодно можно будет убрать и надоедливый "extends PropertyChange".

Теперь надо подумать, как aspectJ будет отличать сеттеры, в которые надо добавить общий код от остальных. Проще всего проставить аннотацию над классом и отобрать все сеттеры с одним аргументом.
Аннотация выглядит так:


Тогда bean можно будет писать в более простом виде:


И сам аспект:

Или в annotation-style:


Теперь по шагам:
  1. Все классы, аннотированные @Bean будут наследовать PropertyChange (в случае с аннотациями они будут наследовать только интерфейс IPropertyChange, а реализация методов будет браться из PropertyChange).

    Или в annotation-style:

  2. Все нужные нам сеттеры - это методы типа void, принимающие один параметр, находящиеся в классе, помеченные аннотацией @Bean.

    Или в annotation-style:

  3. Перед вызовом сеттера запоминаем текущее значение свойства в переменную oldValue(//1). Выполняем сеттер (//2) и публикуем сообщение (//3).

    Или в annotation-style:

вторник, 25 января 2011 г.

JavaWS и javassist

При использовании javassist из-под java-webstart возможны проблемы с поиском классов и/или с класслоадерами. В первом случае мы получим ошибку вроде javassist.NotFoundException, во втором что-нибудь вроде VerifyError.

От первого спасет ручное добавление классов в пул:


От второго: создание класса с указанием класс-лоадера:

воскресенье, 23 января 2011 г.

Debug и JavaWS

Запускаем из консоли так:
set JAVAWS_VM_ARGS="-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8200"
javaws http://xxx.xx/launch.jnlp
И аттачимся дебаггером.

суббота, 15 января 2011 г.

Type-safe Hibernate Criteria.

Пусть у нас есть класс User:


Обычно Criteria-api используют в таком стиле:


Мне кажется немного неправильным писать имена свойств в виде строк: могут возникнуть проблемы при рефакторинге.

На решение меня натолкнула библиотека jmock. В результате для getUser(*) получаем такой код:


Осталось реализовать (использую javassist и google-collections):

вторник, 4 января 2011 г.

Hibernate: org.hibernate.MappingException: Unknown entity ...

Если пользуетесь аннотациями для маппинга, и получаете ошибку Unknown entity, проверяйте в первую очередь, что импортируете @javax.persistence.Entity, а не @org.hibernate.annotations.Entity.