Category Archives: Java
Наша система, написанная на Java, должна взаимодействовать с коллцентром. Коллцентр – это такая десктопная приложуха, которая запускается на компьютере отдельно и принимает звонки клиентов. Приложение это предоставляет COM-интерфейсы для взаимодействия с ней, можно получить текущий статус, подключиться к серверу и подписаться на события поступления звонков. Так как взаимодействовать с COM удобнее всего на дотнете, то решили действовать именно так. Но основное-то наше приложение – это десктопный Swing-клиент! Нужно как-то транслировать всю информацию из дотнетовой аппликухи в свинговую. Два года назад такая же проблема была решена путём взаимодействия через сеть. Дотнетовое приложение тогда запускалось отдельно и при некоторых событиях дёргало по HTTP некий урл, который уже обрабатывался Java-программой. Но сейчас мы обнаружили такую замечательную штуку как jni4net. Этот opensource инструмент, написанный энтузиастом, позволяет внутри Java-процесса загружать дотнетовую CRL и работать напрямую с дотнетовыми классами через JNI. Для дотнетовых классов будут сгенерированы удобные Java-прокси классы, которые вы сможете вызывать так, как будто это были бы обычные Java классы. Но при этом все вызовы будут затранслированы в дотнетовский рантайм. То же самое можно проделать и в другом направлении – то есть внутри CLR подгрузить Java Runtime и обращаться к JVM аналогичным образом. Поковырявшись немного, хочу тезисно расписать основные моменты, на которых бы хотелось остановиться:
- Это реально работает!
- Чтобы создать ant-скрипт для сборки такой конструкции, пришлось повозиться, но я подготовил шаблон проекта, с которого можно начать быстро и безболезненно.
- В случае вызова .NET из контекста работающего Java-процесса все происходит примерно следующим образом: jni4net загружает DLL-переходник для CLR соответствующей разрядности, затем происходит загрузка CLR, после этого грузится ваша DLL.
- Дотнетовые поля не маршаллятся (в прокси-классах их просто не будет), используйте свойства.
- Свойства маршаллятся на ура, превращаясь по ходу дела в геттеры и сеттеры (правда, булевые свойства тоже будет доступны через get, а не через is, как многие привыкли видеть).
- Делегаты нужно придумывать свои, поскольку прокси-классы к стандартным скорее всего не будут созданы (для этого наверное нужно более подробно конфигурировать proxygen). То есть просто EventHandler лучше не использовать, можно завести свой public делегат и он прекрасно будет преобразован в интерфейс с методом Invoke(). Соответственно, в Java коде вы создаете свою реализацию этого интерфейса (например, анонимную) – всё как с обычными джавовыми слушателями.
- Насчет исключений я не разбирался, вроде бы тоже должны маршаллиться, но я предпочел использовать паттерн “GetLastError”, чтобы не зависеть от этого.
- Если ваша DLL требует того, чтобы быть сконфигурированной в App.config- у вас не получится этого сделать. В списках рассылки автор предлагает обходить это двумя путями: либо инициализировать всё программно, либо в вашей DLL уже создавать отдельный AppDomain и в него загружать сборки.
- Если вам необходимо работать с 32-разрядными библиотеками (в моём случае это было именно так), то вам придётся позаботиться о том, как настроить запуск вашего java-приложения с 32-битной JVM.
- Если вы хотите засунуть все DLL в отдельную папку, вы должны туда же засунуть и jni4net.j-0.8.6.0.jar. Судя по экспериментам, jni4net ищет свой рантайм рядом с собой, определяя местоположение своего джарника. Но при указании библиотеки, которую вы хотите загрузить, вы должны указать путь к ней. Например, так:
Bridge.LoadAndRegisterAssemblyFrom(new java.io.File("lib\\net-app.j4n.dll"));
Если всё, что связано с jni4net лежит в lib. В этом случае jni4net найдет свой рантайм, а также найдет net-app.j4n.dll в месте, которое вы ему указали. Можно ли держать рантайм jni4net в одной папке, а дотнетовые DLL в другой, пока не ясно.
- Запуск из папки, расположенной на сетевом диске, не работает – Windows не разрешает загружать дотнетовые сборки из shared-папок по умолчанию. На Windows 7 я получил следующий стектрейс:
Can't init BridgeExport: DLLs are marked as unsafe. Open file properties in windows explorer and click unblock. Can't init BridgeExport:An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not enable CAS policy by default, s o this load may be dangerous. If this load is not intended to sandbox the assembly, please enable the loadFromRemoteSources switch . See http://go.microsoft.com/fwlink/?LinkId=155569 for more information. Can't init BridgeExport:System.NotSupportedException: An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not e nable CAS policy by default, so this load may be dangerous. If this load is not intended to sandbox the assembly, please enable th e loadFromRemoteSources switch. See http://go.microsoft.com/fwlink/?LinkId=155569 for more information. at System.Reflection.RuntimeAssembly.nLoadFile(String path, Evidence evidence) at System.Reflection.Assembly.LoadFile(String path) at net.sf.jni4net.BridgeExport.initDotNet(IntPtr envi, IntPtr clazz) Can't initialize jni4net Bridge from S:\temp\elwood\Taxi\lib\jni4net.n.w32.v40-0.8.6.0.dll Can't initialize jni4net BridgeCan't initialize jni4net Bridge. Code:-101
На Windows XP у меня в такой же ситуации просто вылетела JVM. Возможно, есть способ это исправить, но я не исследовал.
Ещё раз ссылка на шаблон проекта, который можно взять и запустить:
Для взаимодействия с 32-разрядной COM библиотекой через jni4net понадобилось, чтобы приложение запускалось с помощью 32-разрядной JDK. Скачал, поставил, и напоролся на то, что установленная 32-битная версия 1.7_25 полностью заменила все имеющиеся JDK, закинув exe-файлы java.exe, javaw.exe прямо в Windows\System32. А мне хотелось, чтобы по умолчанию работала 64-разрядная версия. И только в определённых приложениях запуск производился с помощью 32-битной JVM. Нашел следующее решение:
- Удаляем из System32 три exe файла, появившихся после инсталляции 32-битной JDK
- Устанавливаем JAVA_HOME на 64-разрядную JDK. В Path прописываем путь к директории bin этой же 64-разрядной JDK. Это будет JDK по умолчанию.
- В приложениях, которые требуют запуска с использованием недефолтной JDK, прописываем следующие строки в скрипт запуска:
set JAVA_HOME=путь к JDK
set PATH=%JAVA_HOME%\bin;%PATH%
Вот и всё! Теперь у нас в приложениях, где этих строк нет, будет запускаться дефолтная версия JVM, а в приложениях, которые мы сконфигурировали явно, будет стартовать именно та версия, которую мы прописали.
Озаботился поиском библиотеки для связывания данных в java, чтобы использовать в программировании UI компонентов, наподобие того, как это можно делать в WPF. Нашел библиотеку http://databinding.tornado.no/, попробовал и нашел множество проблем, хотя на первый взгляд она показалась мне вполне подходящей. Проблем оказалось настолько много, что я специально решил их все выписать, чтобы потом при разработке библиотек сверяться с этим чеклистом и проверять свои проекты на соответствие желаемому уровню качества. Итак, поехали.
- Нет комментариев.
С этим всё понятно. Если код библиотеки, пусть даже очень небольшой, не содержит комментариев хотя бы к классам и паблик методам, то сразу возникает вопрос о возможности применять эту библиотеку. - Нет репозитория с кодом на одном из хостингов кода
Нет единой точки входа для того, чтобы скачать последнюю версию исходников, форкнуть или предложить патч. - Нет лицензии
Не понятно, можно ли использовать код в своих приложениях. - Нет unit тестов
Нет гарантии того, что код в основных сценариях работает корректно. - Ошибка при вызове StatusMonitor’a в валидации – clearStatus никогда не вызывается. Вместо него вызывается setStatus(null);
Это уже первая реальная ошибка. Были бы юнит тесты – этой ошибки бы не было. - Поддерживается только связывание с UI компонентами, нельзя связать 2 произвольных объекта.
Это чисто моя хотелка насчет байндинга. - UI bridges поддерживают только 1 проперти, что делать, если хочется связаться с UI компонентом, но по другому свойству?
Тоже хотелка. - Проглатывание исключений в вызове валидатора
Подозрительный код, здесь могут возникнуть трудноотлаживаемые проблемы. - Не поддерживаются свойства с примитивными типами
Печаль. - Неочевидные комбинации режимов ONCHANGE, ONBLUR итд (можно установить отдельно режимы для байндинга и для валидации/конвертации, например)
Проблема плохо продуманного АПИ. - Невозможно получить результат валидации и конвертации отдельно, приходит просто строка
То же самое. - Неудобный интерфейс StatusMonitor
Аналогично. Необходимо реализовывать 2 метода, хотя вполне достаточно и одного. - При связывании происходит связь только из source в UI. То есть состояние валидации не инициализируется. Нужно явно вызывать flushUiToModel().
Ещё одно следствие отсутствия юнит тестов. - Установка model.prop = null не всегда приводит к очистке UI. Бага связана с наличием валидатора и с тем, проходит ли текущее значение валидацию. Хотя по идее от этого не должно зависеть вообще никак.
И еще одно. - Нет возможности отладить какой-то байндинг (включить логирование например).
- Непонятные зависимости от commons-beans и транзитивно еще от commons-logging и commons-collections.
Зачем мне дополнительные 3 джарника для маленькой либы в 40 килобайт кода ? - Байндинг в сторону модели ВООБЩЕ не работает если задан валидатор ! Просто блокер бага.
Без комментариев. - Нет поддержки локализации сообщений об ошибках в конверторах и валидаторах.
Это можно было бы пофиксить патчем, но форкнуть код невозможно. А создавать свой отдельный репозиторий не хочется. - Непонятно, как связывать списки.
Ещё одна очевидная хотелка.
2