Использование JNI в сервлете

Данная статья с примером использования динамической библиотеки .dll в сервлете является продолжением описания взаимодействия Java с C/C++ посредством JNI. Причины использования native методов в WEB-приложении могут быть разными, и их рассматривать не будем; будем считать, что определенную информацию необходимо обработать «нативной» библиотекой.

В предыдущей статье была описана технология создания динамической библиотеки JNICall.dll с native методами. В нашем примере данную бибиотеку будем использовать в сервлете для выполнения функций умножения и конкатенации. Дополнительно в примере используем скриптовую библиотеку jQuery, которая позволит нам выполнить асинхронный ajax-запрос и разместить результат выполнения сервлетом функции на странице без ее перезагрузки. Пример использования библиотеки jQuery на странице JSP уже представлен на сайте, и Вы можете увидеть его здесь.

Описание примера использования JNI в сервлете

Структура примера использования динамической библиотеки JNICall.dll в сервлете посредством JNI представлена на следующем скриншоте. Проект в IDE Eclipse включает :

  • сервлет JNICallServlet.java;
  • модуль загрузки динамической библиотеки JNICall.java;
  • динамически подгружаемую библиотеку dll/JNICall.dll;
  • скриптовую библиотеку js/jquery-3.2.1.min, используемую для ajax-вызовов функций сервлета;
  • дескриптор приложения web.xml;
  • страницу index.jsp.

Примечание : библиотека JNICall.dll создана для работы в Windows x64. Если Вам необходима библиотека для Windows x32, то можете создать новую библиотеку согласно технологии, описанной здесь.

Листинг дескриптора приложения web.xml

В дескрипторе приложения web.xml определен полный путь к сервлету (тег <servlet-class>) и алиас вызова сервлета из браузера (тег <url-pattern>). По умолчанию для сайта открывается страница index.jsp.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <display-name>Web Application</display-name>

    <servlet>
        <servlet-name>JNIServlet</servlet-name>
        <servlet-class>
              com.example.JNICallServlet
        </servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>JNIServlet</servlet-name>
        <url-pattern>/server</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

Листинг сервлета JNICallServlet

Сервлет, при поступлении вызова, извлекает параметр "action", получает путь к контексту сервлета и загружает динамическую библиотеку при первичном обращении. После этого вызывает соответствующую параметру функцию в библиотеке, формирует ответ и возвращает результат в браузер.

package com.example;

import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JNICallServlet extends HttpServlet
{
    private static final long serialVersionUID = 1L;

    private JNICall jniCall;
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    protected void doGet(HttpServletRequest request, 
                         HttpServletResponse response) 
                                throws ServletException, IOException 
    {
        String action = request.getParameter("action").trim();
        if(action == null || "".equals(action))
            action = "undefined";

        if (jniCall == null) {
            ServletContext context;
            context = request.getSession().getServletContext();
            String path = context.getRealPath("/");
            loadJNICall(path);
        }
        String answer = "";
        if (jniCall != null) {
            try {
                if (action.equalsIgnoreCase("Multiply"))
                    answer = "" + jniCall.doMultiply(12, 15);
                else if (action.equalsIgnoreCase("Concat"))
                    answer = jniCall.doCombine("Hello, ", "world!");
                else if (action.equalsIgnoreCase("Message"))
                    answer = jniCall.getMessage("java message");
            } catch (Exception e) {
                System.err.println (e.getMessage());
            }
        }
        response.setContentType("text/plain");

        OutputStream outStream = response.getOutputStream();
        outStream.write(answer.getBytes("UTF-8"));
        outStream.flush();
        outStream.close();
    }
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    private void loadJNICall(String path)
    {
        if (jniCall != null)
            return;
        jniCall = new JNICall(path);
    }
}

Листинг класса загрузки DLL библиотеки

Модуль загрузки динамической библиотеки JNICall в качестве параметра получает путь контекста и проверяет наличие библиотеки в поддиректории dll. Если библиотека найдена, то в соответствующее свойство загрузчика класса добавляется путь к директории JNICall.dll в методе addLibraryPath(String), и после этого загружается библиотека. Дополнительную информацию о настройке "java.library.path" можете получить здесь.

package com.example;

import java.io.File;
import java.lang.reflect.Field;
import java.util.Arrays;

public class JNICall
{
    public native int    doMultiply(int    val1, int    val2); 
    public native String doCombine (String str1, String str2); 
    public native String getMessage(String message); 
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    public JNICall(String path)
    {
        File file = new File(path + "/dll/JNICall.dll");
        if (file.exists()) {
            path += "/dll";
            try {
                addLibraryPath(path);
                System.loadLibrary("JNICall");
                System.out.println("JNICall.dll is loaded");
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.err.println("JNICall.dll not found");
        }
    }
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    public void addLibraryPath(String pathToAdd) throws Exception
    {
        Field usrPathsField;
        usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
        usrPathsField.setAccessible(true);

        // get array of paths
        final String[] paths = (String[])usrPathsField.get(null);

        // check if the path to add is already present
        for (String path : paths) {
            if(path.equals(pathToAdd)) {
                return;
            }
        }
        // add the new path
        String[] newPaths = Arrays.copyOf(paths,paths.length+1);
        newPaths[newPaths.length-1] = pathToAdd;
        usrPathsField.set(null, newPaths);
    }	
}

Листинг страницы index.jsp

В секции head наряду с кодировкой и заголовком страницы определена скриптовая библиотека "jquery-3.2.1.min.js", загружаемая из директории "js". Можно, конечно, снять комментарий и загружать библиотеку из Интернета. Но не всегда это возможно, особенно если проект работает в закрытом Интранете. Поэтому скриптовая библиотека в минимальной комплектации (достаточно для решения задачи) подгружается из проекта. Кроме этого, в секции head определены скриптовые функции doAction, getChecked, callServlet, используемые для формирования ajax-запроса с параметром к сервлету и отображения результата выполнения функции сервлетом на странице.

<%@ page language="java" contentType="text/html; charset=UTF-8"
                         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
                      "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" 
                 content="text/html; charset=UTF-8">
        <title>JNI test</title>
<!-- 
        <script src="http://code.jquery.com/jquery-2.2.4.js" 
                type="text/javascript"></script>
-->
        <script src="js/jquery-3.2.1.min.js" 
                type="text/javascript"></script>
        <script>
            function doAction(){
               // Можно использовать jQuery
               // act = $('input[name=action]:checked').val();
               act = getChecked();
               callServlet(act);
            }
            function getChecked() {
               var inp = document.getElementsByName('action');
               for (var i = 0; i < inp.length; i++) {
                   if (inp[i].type == "radio" && inp[i].checked) {
                       return inp[i].value;
                   }
               }
            }
            function callServlet(el) {
                $.ajax({
                    url : 'server',
                    data : {
                        action : el },
                    success : function(response) {
                        $('#answerId').text(response);
                    }
                });
            }
        </script>
    </head>
    <body>
        <p>Выберите функцию</p>
       
        <input type="radio" name="action" value="Multiply" CHECKED >
                Умножение 12 на 15 <p />
        <input type="radio" name="action" value="Concat">
                Конкатенация строк 'Hello, ' и 'world!'<p />

        <input type="submit" value="Выполнить" 
                                onClick="doAction()"><p/>
        Ответ сервера :
        <span id="answerId" style="color:#46f; font-size:120%; 
                                   font-weight:bold;">&nbsp;
        </span>
    </body>
</html>

Интерфейс страницы включает две радиокнопки выбора функции и кнопку обращения к сервлету. При нажатии на кнопку «Выполнить» вызывается функция doAction(), которая определяет, какая из радиокнопок выбрана. Для этого можно использовать либо jQuery (закомментированные строки в методе doAction), либо скриптовую функцию getChecked().

После того, как выбранная функция определена, вызывается функция callServlet, которая формирует асинхронный ajax-запрос к сервлету с передачей ему параметра action (наименование функции). Результат выполнения сервлетом функции response поступит в функции success, которая разместит значение в поле answerId без перезагрузки страницы.

Интерфейс страницы представлен на следующем скриншоте.

Особенности использования JNI

При перезагрузке приложения в WEB-контейнере (JBoss, Tomcat и т.д.) динамическая библиотека остается загруженной; повторная загрузка вызовет исключение. Т.е. требуется полная перезагрузка WEB-контейнера. Это следует учитывать, если у Вас в WEB-контейнере «крутится» несколько приложений.

Скачать пример

Рассмотренный на странице пример, включающий проект Eclipse в технологии maven можно скачать здесь (633 Кб).

  Рейтинг@Mail.ru