JavaScript Native Interface, JSNI

Java может взаимодействовать с приложением, выполненным с использованием другого языка программирования, например С/С+. В этом случае необходимо объявить java native метод и обеспечить реализацию этого метода в другом не java-приложении. Выполняться код будет уже вне JVM (Java Virual Machine). Этот механизм называется Java Native Interface (JNI).

Google, разработчик GWT, создал свой механизм вызова не java-методов, выполненных в виде механизма JavaScript Native Interface (JSNI). JSNI позволяет получить доступ к низкоуровневой функциональности браузера, не представленным API-классами GWT.

При использовании JSNI-методов необходимо предусмотреть возможности исполнения кода в различных браузерах, контролировать утечки памяти и безопасность. Для GWT-приложения (java-кода), тело JavaScript-метода абсолютно непрозрачно, как и любой объект JavaScript. А это значит, что для отладки JavaScript-кода, придется использовать JavaScript отладчик, так как встроеный в Eclipse debuger его не видит. В остальном JSNI работает очень прозрачно.

Компилятор фреймворка GWT транслирует java-код в JavaScript. Для того, чтобы вставить в java-код приложения определенные методы, исполненные в виде JavaScript-кода и которые компилятор не должет конвертировать, необходимо эти методы соответствующим образом оформить. Тело JSNI-метода «оборачивается» специальной комбинацией символов :

  • начало кода JSNI-метода начинается с символов «/*-{»;
  • завершается код JSNI-метода символами «}-*/;».

Ниже представлен пример JSNI-метода alert(String) для вызова окна с сообщением в браузере.

public static native void alert(String msg) /*-{
  $wnd.alert(msg);
}-*/;

При обращении к окну браузера window или документу document из JSNI-метода, необходимо использовать зарезервированные аббревиатуры $wnd и $doc соответственно. Cкомпилированный скрипт JSNI-метода будет работать во вложенном фрейме, а $wnd и $doc автоматически инициализируются в окно и документ.

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

Получение доступа к полям и методам java-класса из JavaScript

Поскольку JavaScript использует динамическую типизацию, а Java - статическую, то для обращения из JSNI-метода к полям и методам java-класса необходимо использовать специальный синтаксис :


[instance-expr.]@class-name::object-name(param-signature)(arguments)
 

где

  • instance-expression - экземпляр класса; при вызове статических методов или обращении к статическим полям класса этот атрибут отсутствует;
  • class-name - полное наименование класса, к полям и методам которого выполняется обращение (package.class);
  • object-name - наименование поля или метода класса;
  • param-signature - внутренняя сигнатура параметров java-метода определенная в JNI Type Signatures; при обращении к полю класса отсутствует;
  • arguments - список аргументов для передачи в вызываемый метод; при обращении к полю класса отсутствует.

Как говорится лучше один раз увидеть, чем 100 раз услышать; поэтому рассмотрим использование JSNI на примере.

Пример использования JSNI в GWT-проекте

Создадим небольшой GWT-проект с использованием библиотеки GXT в Eclipse. В интерфейсе приложения разместим кнопку и метку. В java-коде будем использовать статическое поле счетчика и нестатическое текстовое поле для сообщений. По нажатию на копку необходимо изменить значение счетчика и создать текстовое сообщение, которое отображается в метке. При обработке событий по нажатию на кнопку будем использовать JSNI-методы. Интерфейс примера представлен на следующем скриншоте.

В качестве основы проекта используется пример, приведенный на странице описания GXT-контейнеров. Поскольку основной темой этой страницы является использование JSNI-методов в GWT-проекте, то полное описание примера не приводится. Желающие могут скачать пример в конце страницы.

Класс Page.java

Формирование интерфейса и обработка всех событий осуществляется в классе Page.java, который наследует свойства шаблона TemplatePage. В интерфейсе страницы размещается кнопка button и метка label. Интерфейс страницы формируется в методе createBody.

В качестве счетчика используется статическое целочисленное поле counter. Сообщение будет формироваться в нестатическом поле TEXT. Изменение значения счетчика осуществляется в статическом методе onButtonClick. Для отображения значения в метке используется нестатический метод showCounter(String). Эти методы вызываются непосредственно из JSNI-методов countClick и showCounter, которые вызываются из обработчика событий по нажатию на кнопку.

public class Page extends TemplatePage
{
    TextButton      button  = null;
    FieldLabel      label   = null;

    static  int     counter = 0;
    String          TEXT    = null;
    //--------------------------------------------------------
    public Page() 
    {
        super();
        createBody();
    }
    //--------------------------------------------------------
    private void createBody()
    {
        HorizontalLayoutContainer hlcHead;
        HorizontalLayoutContainer hlcFoot;

        HorizontalLayoutData      hld;

        hlcHead = new HorizontalLayoutContainer();
        hlcHeader.add(hlcHead);

        button = new TextButton("Кнопка");
        button.setPixelSize(80, 24);
        Margins margins = new Margins(4, 0, 0, 10);
        hld = new HorizontalLayoutData (-1, -1, margins);
        hlcHead.add(button, hld);

        button.addSelectHandler(new SelectHandler() {
            @Override
            public void onSelect(SelectEvent event) {
                countClick ();
                showCounter(Page.this, counter);
            }
        });

        hlcFoot = new HorizontalLayoutContainer();
        hlcFooter.add(hlcFoot);

        label = new FieldLabel(null, "");
        label.setLabelSeparator("");
        label.setId("FieldLabel");
        label.setPixelSize(240, 24);
        label.setStyleName("font-10");

        margins = new Margins(5, 0, 0, 10); 
        hld = new HorizontalLayoutData (-1, -1, margins);
        hlcFoot.add(label, hld);
    }
    //--------------------------------------------------------
    static void onButtonClick()
    {
        counter++;
    }
    //--------------------------------------------------------
    void showCounter(final String text)
    {
        label.setText(text);
    }
    //--------------------------------------------------------
    public native void countClick() /*-{
        // тело метода
    }-*/;
    //--------------------------------------------------------
    public native void showCounter(Page jsni, int count) /*-{
        // тело метода
    }-*/;
}

Обращение к статическому полю и методу

JSNI-метод countClick при работе со статическими объектами класса (полем и методом) вызывает сначала статический метод onButtonClick, увеличивая значения счетчика на 1, после чего обрабатывает значение напрямую - получает новое значение счетчика и увеличивает его на 2.

public native void countClick() /*-{

    // Вызов статического метода
    @com.common.client.Page::onButtonClick()();

    // Получение значения статического поля класса
    var cnt = @com.common.client.Page::counter;
        
    // Изменение значение статического поля класса
    @com.common.client.Page::counter = cnt + 2;
}-*/;

Следует обратить внимание, что при взаимодействии со статическими полем и методом instance-expression не используется.

Обращение к нестатическому полю и методу

JSNI-метод showCounter (jsni, count) для работы с нестатическими объектами класса сначала формирует сообщение text, сохраняет значение в нестатическом поле класса TEXT, после этого читает данное сообщение из поля и вызывает нестатический метод showCounter с передачей в качестве параметра прочитанного сообщения.

public native void showCounter(Page jsni, int count) /*-{

    // Формирование сообщения 
    var text = 'Кнопка была нажата '+ count + ' раз';

    // Сохранение текста в нестатическом поле класса
    jsni.@com.common.client.Page::TEXT = text;

    // Получение значения из нестатического поля класса
    text = jsni.@com.common.client.Page::TEXT;

    // Вызов нестатического метода
    jsni.@com.common.client.Page::showCounter(Ljava/lang/String;)(text);
}-*/;

При взаимодействии с нестатическими полем и методом в качестве instance-expression используется экземпляр класса jsni.

В качестве маленького «презента» предлагаем Вам класс Console.java, построенный полностью на JSNI-методах, который можно использовать в своих разработках.

Класс Console c JSNI-методами

Для отладки GWT-приложения можно использовать представленный в начале страницы метод alert. Кроме этого фреймворк GWT предлагает свои окна вывода сообщений: Info.display (title, message) для кратковременного отображения информации и Window.alert (message). Можно использовать представленный ниже класс Console.java c JSNI-методами для вывода сообщения в консоль браузера.

public class Console
{
   /**
     * Вывод сообщения об ошибке в консоль браузера
     * @param msg сообщение
     */
    public static native void error(String msg) /*-{
        if (typeof(console) != "undefined") {
            console.error(msg);
        }
    }-*/;

    /**
     * Вывод информационного сообщения в консоль браузера
     * @param msg сообщение
     */
    public static native void info(String msg) /*-{
        if (typeof(console) != "undefined") {
            console.info(msg);
        }
    }-*/;

    /**
     * Вывод сообщения протоколирования в консоль браузера
     * @param msg сообщение
     */
    public static native void log(String msg) /*-{
        if (typeof(console) != "undefined") {
            console.log(msg);
        }
    }-*/;

    /**
     * Вывод предупреждающего сообщения в консоль браузера 
     * @param msg сообщение
     */
    public static native void warn(String msg) /*-{
        if (typeof(console) != "undefined") {
            console.warn(msg);
        }
    }-*/;
}

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


// вывод сообщения о версии
Console.info("version : 3.2.1");
 

В окне консоли браузера Google Chrome, открываемое по нажатию кнопки F12, можно увидеть представленное на следующем скриншоте сообщение.

Скачать исходный код примера

Исходный код рассмотренного GWT-примера с использованием JSNI-методов в виде проекта Eclipse можно скачать здесь (43.2 Кб).

В архив примера не включены библиотечные файлы gwt-servlet.jar из GWT SDK и GXT библиотека gxt-3.1.1.jar. Их необходимо подключить самостоятельно, как это описано в GWT-GXT примере с табличным компонентом Grid.

  Рейтинг@Mail.ru