Содержание


JavaScript EE

Часть 1. Исполнение файлов JavaScript на стороне сервера

Учимся применять API javax.script в приложениях Ajax и Java ЕЕ

Comments

Серия контента:

Этот контент является частью # из серии # статей: JavaScript EE

Следите за выходом новых статей этой серии.

Этот контент является частью серии:JavaScript EE

Следите за выходом новых статей этой серии.

На стороне клиента типичные приложения Ajax используют JavaScript, а на стороне сервера — другие языки, такие как Java. В результате разработчикам приходится создавать некоторые программы дважды, используя для Web-браузера JavaScript, а для сервера другой язык. Этой двойной работы можно избежать, воспользовавшись на стороне сервера языком JavaScript в сочетании с Java, чтобы получить полную поддержку языков сценариев через API javax.script. К тому же Java SE Development Kit (JDK) 6 уже содержит механизм JavaScript Rhino от Mozilla, поэтому дополнительно устанавливать ничего не нужно.

В этой первой статье данной серии мы воспользуемся простым сценарием, который позволяет исполнять файлы JavaScript внутри приложений Java ЕЕ. Сценарии будут иметь доступ к так называемым "неявным объектам" (implicit objects), которые используются в страницах JSP, таким как application, session, request и response. Большинство примеров состоит из многократно используемого кода, так что вам будет легко перейти к применению JavaScript на сервере в своих собственных приложениях.

Применение API javax.script

В этом разделе содержится обзор API javax.script. Вы научитесь исполнять сценарии, которые обращаются к объектам Java, вызывают функции JavaScript из кода Java и реализуют механизм кэширования для скомпилированных сценариев.

Исполнение сценариев

API javax.script чрезвычайно прост. Начнем с создания экземпляра ScriptEngineManager, который позволяет получить объект ScriptEngine (см. листинг 1) посредством одного из следующих методов:

  • getEngineByName()
  • getEngineByExtension()
  • getEngineByMimeType()
Листинг 1. Получение экземпляра ScriptEngine
import javax.script.*;
...
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
...
engine.eval(...);

Список доступных механизмов сценариев можно получить также с помощью getEngineFactories() В настоящее время в JDK 6 входит только механизм JavaScript, но ScriptEngineManager реализует функцию поиска сторонних механизмов, поддерживающих JSR-223 Scripting for the Java Platform (см. Ресурсы ). Нужно просто включить файлы JAR соответствующих механизмов сценариев в свой CLASSPATH.

Создав экземпляр javax.script.ScriptEngine, можно вызвать eval() для исполнения сценариев. Можно также экспортировать объекты Java в качестве переменных сценария, передав объект Bindings методу eval(). В листинге 2 приведен пример кода ScriptDemo.java, который экспортирует две переменные с именами demoVar и strBuf, исполняет сценарий с именем DemoScript.js, а затем получает переменные для вывода их измененных значений.

Листинг 2. Пример ScriptDemo.java.
package jsee.demo;

import javax.script.*;
import java.io.*;

public class ScriptDemo {

    public static void main(String args[]) throws Exception {
        // Get the JavaScript engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // Set JavaScript variables
        Bindings vars = new SimpleBindings();
        vars.put("demoVar", "value set in ScriptDemo.java");
        vars.put("strBuf", new StringBuffer("string buffer"));

        // Run DemoScript.js
        Reader scriptReader = new InputStreamReader(
            ScriptDemo.class.getResourceAsStream("DemoScript.js"));
        try {
            engine.eval(scriptReader, vars);
        } finally {
            scriptReader.close();
        }

        // Get JavaScript variables
        Object demoVar = vars.get("demoVar");
        System.out.println("[Java] demoVar: " + demoVar);
        System.out.println("    Java object: " + demoVar.getClass().getName());
        System.out.println();
        Object strBuf = vars.get("strBuf");
        System.out.println("[Java] strBuf: " + strBuf);
        System.out.println("    Java object: " + strBuf.getClass().getName());
        System.out.println();
        Object newVar = vars.get("newVar");
        System.out.println("[Java] newVar: " + newVar);
        System.out.println("    Java object: " + newVar.getClass().getName());
        System.out.println();
    }

}

Файл DemoScript.js (приведен в листинге 3) содержит функцию printType(), которая используется для вывода типа каждой переменной сценария. Пример вызывает метод append() объекта strBuf, изменяет значение demoVar и создает новую переменную сценария с именем newVar.

Если объект, переданный printType(), содержит метод getClass(), он должен быть объектом Java, имя класса которого получается с помощью obj.getClass().name. Это выражение JavaScript вызывает метод getName() экземпляра объекта java.lang.Class. Если объект не содержит члена getClass, то printType() вызывает метод toSource(), который должен содержать все объекты JavaScript.

Листинг 3. Пример DemoScript.js
println("Start script \r\n");

// Output the type of an object
function printType(obj) {
    if (obj.getClass)
        println("    Java object: " + obj.getClass().name);
    else
        println("    JS object: " + obj.toSource());
    println("");
}

// Print variable
println("[JS] demoVar: " + demoVar);
printType(demoVar);

// Call method of Java object
strBuf.append(" used in DemoScript.js");
println("[JS] strBuf: " + strBuf);
printType(strBuf);

// Modify variable
demoVar = "value set in DemoScript.js";
println("[JS] demoVar: " + demoVar);
printType(demoVar);

// Set a new variable
var newVar = { x: 1, y: { u: 2, v: 3 } }
println("[JS] newVar: " + newVar);
printType(newVar);

println("End script \r\n");

В листинге 4 приведен результат исполнения примера ScriptDemo.java. Первое, что бросается в глаза, это то, что переменная demoVar экспортирована как строка JavaScript, тогда как тип strBuf остался прежним: java.lang.StringBuffer. Простые переменные и строки Java экспортируются как стандартные объекты JavaScript. Любые другие объекты Java (включая массивы) экспортируются неизменными.

Листинг 4. Результат ScriptDemo.java
Start script

[JS] demoVar: value set in ScriptDemo.java
    JS object: (new String("value set in ScriptDemo.java"))

[JS] strBuf: string buffer used in DemoScript.js
    Java object: java.lang.StringBuffer

[JS] demoVar: value set in DemoScript.js
    JS object: (new String("value set in DemoScript.js"))

[JS] newVar: [object Object]
    JS object: ({x:1, y:{u:2, v:3}})

End script

[Java] demoVar: value set in DemoScript.js
    Java object: java.lang.String

[Java] strBuf: string buffer used in DemoScript.js
    Java object: java.lang.StringBuffer

[Java] newVar: [object Object]
    Java object: sun.org.mozilla.javascript.internal.NativeObject

После исполнения сценария механизм берет все переменные (в том числе новые) и выполняет обратное преобразование, превращая примитивы и строки JavaScript в объекты Java. Другие объекты JavaScript внедряются в объекты Java с использованием специфического для данного механизма внутреннего API, такого как sun.org.mozilla.javascript.internal.NativeObject.

Так как желательно использовать только стандартные API, весь обмен данными между кодом Java и исполняемым сценарием должен осуществляться посредством простых переменных, строк и объектов Java (например, компонентов), к свойствам и методам которых можно легко обращаться в коде JavaScript. Попросту говоря, не пытайтесь обращаться к стандартным объектам JavaScript в своем коде Java. Вместо этого используйте объекты Java в коде JavaScript.

Обращение к функциям

Из предыдущего примера видно, что методы Java можно вызывать из JavaScript. Теперь посмотрим, как обращаться к функциям JavaScript из кода Java. Прежде всего нужно выполнить сценарий, содержащий нужную функцию. Затем экземпляр ScriptEngine преобразуется в javax.script.Invocable, что дает invokeFunction() и invokeMethod(). Если ваш сценарий реализует все методы интерфейса Java, можно также воспользоваться getInterface() для получения объекта Java, методы которого запрограммированы на языке сценариев.

Пример InvDemo.java (приведен в листинге 5) выполняет сценарий с именем InvScript.js, содержащий подпрограмму demoFunction(). После преобразования экземпляра ScriptEngine в Invocable пример Java передает имя и параметры функции методу invokeFunction() механизма, который возвращает значение, полученное от demoFunction().

Листинг 5. Пример InvDemo.java
package jsee.demo;

import javax.script.*;
import java.io.*;

public class InvDemo {

    public static void main(String args[]) throws Exception {
        // Get the JavaScript engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // Run InvScript.js
        Reader scriptReader = new InputStreamReader(
            InvDemo.class.getResourceAsStream("InvScript.js"));
        try {
            engine.eval(scriptReader);
        } finally {
            scriptReader.close();
        }

        // Invoke a JavaScript function
        if (engine instanceof Invocable) {
            Invocable invEngine = (Invocable) engine;
            Object result = invEngine.invokeFunction("demoFunction", 1, 2.3);
            System.out.println("[Java] result: " + result);
            System.out.println("    Java object: "
                    + result.getClass().getName());
            System.out.println();
        } else
            System.out.println("NOT Invocable");
    }

}

Файл InvScript.js (см. листинг 6) содержит подпрограмму demoFunction() и ту же функцию printType(), которая использовалась в предыдущем примере сценария.

Листинг 6. Пример InvScript.js
println("Start script \r\n");

function printType(obj) {
    if (obj.getClass)
        println("    Java object: " + obj.getClass().name);
    else
        println("    JS object: " + obj.toSource());
    println("");
}

function demoFunction(a, b) {
    println("[JS] a: " + a);
    printType(a);
    println("[JS] b: " + b);
    printType(b);
    var c = a + b;
    println("[JS] c: " + c);
    printType(c);
    return c;
}

println("End script \r\n");

Если посмотреть на результат InvDemo.java (показан в листинге 7), видно, что числовые параметры преобразованы в объекты JavaScript, а значения, возвращаемые функцией demoFunction(), получены как объекты Java. Эти преобразования выполняются только для примитивов и строк. Любые другие объекты передаются между JVM и механизмом JavaScript неизменными.

Листинг 7. Результат InvDemo.java
Start script

End script

[JS] a: 1
    JS object: (new Number(1))

[JS] b: 2.3
    JS object: (new Number(2.3))

[JS] c: 3.3
    JS object: (new Number(3.3))

[Java] result: 3.3
    Java object: java.lang.Double

Обратите внимание на то, что javax.script.Invocable - это необязательный интерфейс, который может быть реализован не во всех механизмов сценариев. Механизм JavaScript, поставляемый с JDK 6, не поддерживает этот интерфейс.

Компиляция сценариев

Интерпретация сценариев при каждом их исполнении приводит к расходу ресурсов процессора. Если одни и те же сценарии выполняются много раз, время их исполнения можно существенно сократить, скомпилировав эти сценарии с использованием методов, предоставляемых другим необязательным интерфейсом с именем javax.script.Compilable, который поддерживается механизмом JavaScript из JDK 6.

Класс CachedScript (см. листинг 8) берет файл сценария и перекомпилирует его заново только в случае изменения исходного кода. Метод getCompiledScript() вызывает метод compile() из механизма сценариев, который возвращает объект javax.script.CompiledScript, методы eval() которого исполняют сценарий.

Листинг 8. Класс CachedScript
package jsee.cache;

import javax.script.*;
import java.io.*;
import java.util.*;

public class CachedScript {
    private Compilable scriptEngine;
    private File scriptFile;
    private CompiledScript compiledScript;
    private Date compiledDate;

    public CachedScript(Compilable scriptEngine, File scriptFile) {
        this.scriptEngine = scriptEngine;
        this.scriptFile = scriptFile;
    }

    public CompiledScript getCompiledScript()
            throws ScriptException, IOException {
        Date scriptDate = new Date(scriptFile.lastModified());
        if (compiledDate == null || scriptDate.after(compiledDate)) {
            Reader reader = new FileReader(scriptFile);
            try {
                compiledScript = scriptEngine.compile(reader);
                compiledDate = scriptDate;
            } finally {
                reader.close();
            }
        }
        return compiledScript;
    }

}

Класс ScriptCache (показан в листинге 9) организует репозиторий для скомпилированных сценариев, используя объект java.util.LinkedHashMap. Первоначальная емкость map-таблицы устанавливается равной максимальному числу сохраненных в кэше сценариев, а коэффициент загрузки равен 1. Эти два параметра гарантируют, что cacheMap никогда не придется хешировать заново.

По умолчанию класс LinkedHashMap использует для своих записей порядок вставки (insert-order). Третий параметр конструктора LinkedHashMap() должен иметь значение true, чтобы вместо порядка по умолчанию для записей map-таблицы использовать порядок доступа (access-order).

После достижения максимальной емкости кэша метод removeEldestEntry() начинает выдавать значение true, так что всякий раз, когда в кэш добавляется новый сценарий, из cacheMap автоматически удаляется запись.

Применяя механизм автоматического удаления LinkedHashMap в сочетании с порядком доступа, ScriptCache гарантирует, что при добавлении нового сценария из кэша будет удаляться сценарий с наиболее длительным отсутствием обращений (Least Recently Used - LRU).

Листинг 9. Класс ScriptCache
package jsee.cache;

import javax.script.*;

import java.io.*;
import java.util.*;

public abstract class ScriptCache {
    public static final String ENGINE_NAME = "JavaScript";
    private Compilable scriptEngine;
    private LinkedHashMap<String, CachedScript> cacheMap;

    public ScriptCache(final int maxCachedScripts) {
        ScriptEngineManager manager = new ScriptEngineManager();
        scriptEngine = (Compilable) manager.getEngineByName(ENGINE_NAME);
        cacheMap = new LinkedHashMap<String, CachedScript>(
                maxCachedScripts, 1, true) {
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > maxCachedScripts;
            }
        };
    }

    public abstract File getScriptFile(String key);

    public synchronized CompiledScript getScript(String key)
            throws ScriptException, IOException {
        CachedScript script = cacheMap.get(key);
        if (script == null) {
            script = new CachedScript(scriptEngine, getScriptFile(key));
            cacheMap.put(key, script);
        }
        return script.getCompiledScript();
    }

    public ScriptEngine getEngine() {
        return (ScriptEngine) scriptEngine;
    }

}

В следующем разделе используется класс ScriptCache, который реализует абстрактный метод getScriptFile() и использует getScript() для извлечения из кэша скомпилированных сценариев.

Построение исполнителя сценариев

В этом разделе мы покажем, как создать простой сервлет Java, реализующий отображение URL-сценарий, что позволяет запускать сценарии на стороне сервера из Web-браузера. Кроме этого, сервлет будет представлять несколько объектов Java ЕЕ как переменные, которые можно использовать в коде JavaScript. Мы покажем также, как использовать контексты сценария, которые позволяют одному механизму JavaScript исполнять несколько сценариев одновременно.

Инициализация сервлета

Класс сервлетов называется JSServlet. Его метод init() (см. листинг 10) получает несколько параметров конфигурации и создает объект ScriptCache. Кэш сценариев сервлета посредством getRealPath() получает путь к файлу сценария, который отображается на данный URI.

Листинг 10. Метод init() JSServlet
package jsee.servlet;

import javax.script.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import jsee.cache.*;

public class JSServlet extends HttpServlet {
    private String cacheControlHeader;
    private String contentTypeHeader;
    private ScriptCache scriptCache;

    public void init() throws ServletException {
        ServletConfig config = getServletConfig();
        cacheControlHeader = config.getInitParameter("Cache-Control");
        contentTypeHeader = config.getInitParameter("Content-Type");
        int maxCachedScripts = Integer.parseInt(
                config.getInitParameter("Max-Cached-Scripts"));
        scriptCache = new ScriptCache(maxCachedScripts) {
            public File getScriptFile(String uri) {
                return new File(getServletContext().getRealPath(uri));
            }
        };
    }
    ...
}

В листинге 11 приведены параметры сервлета, который описан в файле web.xml. Заголовок Cache-Control не имеет никакого отношения к кэшу сценариев. Оба заголовка войдут в состав ответов HTTP, возвращаемых сервлетом. Значение no-cache указывает браузеру, что ответ сервлета, который следует рассматривать как text/plain, не надо помещать в кэш.

Листинг 11. Файл web.xml
<web-app ...>

    <servlet>
        <servlet-name>JSServlet</servlet-name>
        <servlet-class>jsee.servlet.JSServlet</servlet-class>
        <init-param>
            <param-name>Cache-Control</param-name>
            <param-value>no-cache</param-value>
        </init-param>
        <init-param>
            <param-name>Content-Type</param-name>
            <param-value>text/plain</param-value>
        </init-param>
        <init-param>
            <param-name>Max-Cached-Scripts</param-name>
            <param-value>1000</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>JSServlet</servlet-name>
        <url-pattern>*.jss</url-pattern>
    </servlet-mapping>

</web-app>

Как видно из листинга 11, в сервлет передается шаблон *.jss. Это означает, что JSServlet будет обрабатывать все запросы, URL которых оканчивается расширением .jss. Когда пользователь вводит в Web-браузер такой URL или нажимает на ссылку .jss, браузер направляет запрос HTTP Web-серверу (например, Apache), который должен быть настроен на его передачу в контейнер сервлетов (например, Tomcat). Если контейнер сервлетов тоже действует как Web-сервер, никакого дополнительного конфигурирования не требуется.

Когда контейнер сервлетов получает запрос с URL, оканчивающимся на .jss, он обращается к методу service(), который наследует JSServlet у javax.servlet.http.HttpServlet. В зависимости от метода запроса HTTP этот метод вызывает либо doGet(), либо doPost(). Как будет показано далее в этой главе, оба метода отменяются методом JSServlet.

Использование контекстов сценария

Каждый экземпляр механизма сценариев имеет контекст по умолчанию, куда можно класть переменные методом put(), а выход любого выполненного сценария по умолчанию направляется на System.out. В среде сервера хотелось бы исполнять сценарии одновременно, чтобы у каждого из них при этом был собственный контекст. API javax.script выполняет это требование, предоставляя интерфейс ScriptContext и реализацию SimpleScriptContext.

Механизм JavaScript Rhino от Mozilla представляет собой многопоточный механизм (см. врезку), позволяя одновременно исполнять сценарии, разделяющие один общий контекст. Однако в нашем случае нужно изолировать друг от друга области действия механизма и выход сценариев, работающих в разных потоках, а это значит, что для каждого запроса HTTP нужно создавать новый экземпляр ScriptContext.

В листинге 12 показан метод createScriptContext() класса JSServlet. Этот метод устанавливает свойство контекста writer, чтобы при выполнении сценария направлять его выход на вход объекта response. Это означает, что все, что вы передаете в своем сценарии методам print() или println(), будет включено в ответ сервлета.

Кроме того, createScriptContext() определяет следующие переменные сценария с помощью метода контекста сценария setAttribute():

Таблица 1. Переменные сценариев, выполняемых сервлетом JSServlet
Переменная сценарияОписание
configэкземпляр сервлета javax.servlet.ServletConfig
applicationэкземпляр Web-приложения javax.servlet.ServletContext
sessionобъект javax.servlet.http.HttpSession
requestобъект javax.servlet.http.HttpServletRequest
responseобъект javax.servlet.http.HttpServletResponse
outобъект java.io.PrintWriter, который используется для вывода результатов
factoryjavax.script.ScriptEngineFactory механизма сценария

Переменная factory может использоваться для получения сведений о механизме JavaScript, таких как версия языка или версия самого механизма. Остальные переменные выполняют те же функции, что и в страницах JSP.

Листинг 12. Метод createScriptContext() JSServlet
public class JSServlet extends HttpServlet {
    ...
    protected ScriptContext createScriptContext(
            HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        ScriptContext scriptContext = new SimpleScriptContext();
        scriptContext.setWriter(response.getWriter());
        int scope = ScriptContext.ENGINE_SCOPE;
        scriptContext.setAttribute("config", getServletConfig(), scope);
        scriptContext.setAttribute("application", getServletContext(), scope);
        scriptContext.setAttribute("session", request.getSession(), scope);
        scriptContext.setAttribute("request", request, scope);
        scriptContext.setAttribute("response", response, scope);
        scriptContext.setAttribute("out", response.getWriter(), scope);
        scriptContext.setAttribute("factory",
                scriptCache.getEngine().getFactory(), scope);
        return scriptContext;
    }
    ...
}

Метод runScript() (см. листинг 13) получает из кэша скомпилированный сценарий и вызывает метод eval(), передавая в качестве параметра контекст данного сценария.

Листинг 13. Метод runScript() JSServlet
public class JSServlet extends HttpServlet {
    ...
    protected void runScript(String uri, ScriptContext scriptContext)
            throws ScriptException, IOException {
        scriptCache.getScript(uri).eval(scriptContext);
    }
    ...
}

Управление запросами

Чтобы исполнить сценарий, соответствующий URL из запроса HTTP, можно просто вызывать приведенный выше метод runScript(). Однако в реальном приложении перед выполнением сценария, обычно нужно выполнить некоторую инициализацию, а после его выполнения — некоторые завершающие действия.

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

Метод сервлета handleRequest() (показан в листинге 14) устанавливает заголовки HTTP, исполняет сценарий init.jss, удаляет путь контекста из URI запроса, исполняет сценарий с полученным URI, а затем исполняет другой сценарий с именем finalize.jss.

Листинг 14. Метод handleRequest() сервлета JSServlet
public class JSServlet extends HttpServlet {
    ...
    protected void handleRequest(
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (cacheControlHeader != null)
            response.setHeader("Cache-Control", cacheControlHeader);
        if (contentTypeHeader != null)
            response.setContentType(contentTypeHeader);
        ScriptContext scriptContext = createScriptContext(request, response);
        try {
            runScript("/init.jss", scriptContext);
            String uri = request.getRequestURI();
            uri = uri.substring(request.getContextPath().length());
            try {
                runScript(uri, scriptContext);
            } catch (FileNotFoundException x) {
                response.sendError(404, request.getRequestURI());
            }
            runScript("/finalize.jss", scriptContext);
        } catch (ScriptException x) {
            x.printStackTrace(response.getWriter());
            throw new ServletException(x);
        }
    }
    ...
}

Методы doGet() и doPost() (см. листинг 15) сервлета JSServlet вызывают handleRequest().

Листинг 15. Методы doGet() и doPost() сервлета JSServlet
public class JSServlet extends HttpServlet {
    ...
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        handleRequest(request, response);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        handleRequest(request, response);
    }

}

Разработка сценариев на стороне сервера

В этом разделе содержатся несколько примеров сценариев на стороне сервера, которые иллюстрируют, как можно получать параметры запроса, обращаться к свойствам компонентов JavaBean и генерировать ответы JSON.

Предварительная обработка и пост-обработка

В предыдущем разделе мы видели, что, прежде чем выполнить требуемый сценарий, JSServlet вызывает init.jss (показан в листинге 16). Если нужно измерить, как много времени требуется на выполнение сценариев, можно записать время запуска в переменную, как показано ниже.

Листинг 16. Сценарий init.jss
var debug = true;
var debugStartTime = java.lang.System.nanoTime();

Затем время исполнения вычисляется в finalize.jss (см. листинг 17). Время распечатывается как комментарий JavaScript, так что JSServlet может генерировать правильные ответы JSON.

Листинг 17. Сценарий finalize.jss
var debugEndTime = java.lang.System.nanoTime();
if (debug)
    println("// Time: " + (debugEndTime - debugStartTime) + " ns");

По ходу этих статей мы будем добавлять в сценарии init.jss и finalize.jss новый код.

Получение параметров запроса

Сценарии, запускаемые с помощью JSServlet, могут обращаться к параметрам запроса с помощью методов request.getParameter() и request.getParameterValues(), которые возвращают объекты Java. Вы можете предпочесть более короткий синтаксис и работу с объектами JavaScript вместо строк и массивов Java. Это делается очень легко путем добавления в сценарий init.jss следующих строк (см. листинг 18).

Листинг 18. Получение параметров запроса в init.jss
var param = new Object();
var paramValues = new Object();

function initParams() {
    var paramNames = request.getParameterNames();
    while (paramNames.hasMoreElements()) {
        var name = paramNames.nextElement();
        param[name] = String(request.getParameter(name));
        paramValues[name] = new Array();
        var values = request.getParameterValues(name);
        for (var i = 0; i < values.length; i++)
            paramValues[name][i] = String(values[i]);
    }
}

initParams();

Допустим, вы обращаетесь к сценарию с именем ParamDemo.jss по URL, приведенному в листинге 19.

Листинг 19. Пример URL для обращения к сценарию
http://localhost:8080/jsee/ParamDemo.jss?firstName=John&lastName=Smith

В сценарии ParamDemo.jss (см. листинг 20) можно получить два параметра запроса с помощью param.firstName и param.lastName.

Листинг 20. Пример ParamDemo.jss
println("Hello " + param.firstName + " " + param.lastName);

Доступ к компонентам JavaBeans

Компоненты Web-приложения application, session и request содержат экземпляры, которые можно извлечь и заменить методами getAttribute() и setAttribute() соответственно ServletContext, HttpSession и HttpServletRequest. Можно воспользоваться также функциями getBean() и setBean() (см. листинг 21), которые нужно поместить в файл init.jss, чтобы их мог вызывать любой сценарий. Параметр scope должен представлять собой одну из следующих строк:

  • "application"
  • "session"
  • "request"
Листинг 21. Функции init.jss getBean() и setBean()
function getBean(scope, id) {
    return eval(scope).getAttribute(id);
}

function setBean(scope, id, bean) {
    if (!bean)
        bean = eval(id);
    return eval(scope).setAttribute(id, bean);
}

Теперь допустим, что вам нужно хранить экземпляр DemoBean (см. листинг 22) в поле действия сеанса session.

Листинг 22. Пример DemoBean.java
package jsee.demo;

public class DemoBean {
    private int counter;

    public int getCounter() {
        return counter;
    }

    public void setCounter(int counter) {
        this.counter = counter;
    }

}

Сценарий BeanDemo.jss (показан в листинге 23) импортирует содержимое пакета JavaBean с помощью importPackage(Packages.jsee.demo). Затем он пытается получить экземпляр компонента из области session с помощью getBean(). Если компонент не найден, BeanDemo.jss создает объект и помещает его в область session с помощью setBean(). Наконец, сценарий наращивает и распечатывает свойство счетчика counter компонента.

Листинг 23. Пример BeanDemo.jss
importPackage(Packages.jsee.demo);
var bean = getBean("session", "demo");
if (!bean) {
    bean = new DemoBean();
    setBean("session", "demo", bean);
}
bean.counter++;
println(bean.counter);

При импорте пакета, который начинается с java, javax, org, edu, com или net, в importPackage() не нужно использовать префикс Packages. Можно также импортировать отдельные классы с помощью importClass().

Возвращение ответа JSON

В листинге 24 приведен пример, который генерирует ответ JSON, содержащий некоторые сведения о механизме JavaScript и URI сценария. В примере используется синтаксис JavaScript для создания объекта json, исходный код которого получен в форме String методом toSource().

Листинг 24. Пример JsonDemo.jss
var json = {
    engine: { 
        name: String(factory.engineName),
        version: String(factory.engineVersion),
        threading: String(factory.getParameter("THREADING"))
    }, 
    language: { 
        name: String(factory.languageName),
        version: String(factory.languageVersion)
    },
    script: {
        uri: String(request.requestURI)
    }
};

println(json.toSource());

Чтобы обеспечить правильную работу toSource(), все объекты Java, полученные из свойств factory и request в этом примере, нужно преобразовать в объекты JavaScript. Результат работы сценария приведен в листинге 25:

Листинг 25. Выход JsonDemo.jss
({language:{name:"ECMAScript", version:"1.6"}, 
engine:{name:"Mozilla Rhino", threading:"MULTITHREADED", 
version:"1.6 release 2"}, script:{uri:"/jsee/JsonDemo.jss"}})

Заключение

Из этой статьи вы узнали, как компилировать и исполнять файлы JavaScript с помощью API javax.script. Вы также научились создавать кэш LRU на базе java.util.LinkedHashMap, экспортировать объекты Java как переменные сценария, обеспечивать отображение URL на сценарий и создавать файлы JavaScript, выполняемые на стороне сервера. В следующей части этой серии мы научимся обращаться к удаленным функциям JavaScript с помощью Ajax.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, Технология Java
ArticleID=498259
ArticleTitle=JavaScript EE: Часть 1. Исполнение файлов JavaScript на стороне сервера
publish-date=06282010