Статьи

Grand Tour: ускоренный курс по JSP без Java

Пришло время убрать Java из страниц JavaServer.

В последней части моего «Большого тура» по технологии веб-приложений на Java мы разделили наше веб-приложение со списком дел на три части : модель , классы, которые управляют данными в основе нашего приложения; контроллер , сервлеты, которые обрабатывают входящие запросы и затем отправляют отображаемые значения в представление; и представление , JSP-файлы, которые генерируют веб-страницы из этих отображаемых значений.

Хотя это разделение, как правило, хорошо отделяло логику приложения Java от кода пользовательского интерфейса HTML, оно по-прежнему оставляло нам некоторый код Java в наших файлах JSP. Этот код был необходим для захвата значений, предоставленных контроллером, и втирания их в наш HTML-интерфейс. Мы назовем эту логику отображения .

На этот раз я покажу вам, как избавиться от кода Java в ваших JSP-файлах и реализовать эту логику отображения с помощью более простой и более мощной альтернативы.

Давайте начнем с рассмотрения того, что делает логика отображения Java в файле JSP нашего примера. Сначала он получает список элементов, предоставленных контроллером:

List toDoItems = (List)request.getAttribute("toDoItems"); Iterator it = toDoItems.iterator(); 

Затем он проверяет, есть ли какие-либо элементы в списке:

  if (it.hasNext()) { 

Чтобы HTML <select> отображался в виде стандартного списка правильного размера, а не раскрывающегося списка, необходима логика отображения для установки атрибута размера:

 <select name="deleteid" size="<%= Math.max(2, toDoItems.size()) %>"> 

Затем используется логика отображения для обработки каждого элемента списка дел по очереди:

  while (it.hasNext()) { ToDoItem toDoItem = (ToDoItem) it.next(); 

Наконец, логика отображения используется для извлечения идентификатора и текстового содержимого каждого элемента списка дел , а также для экранирования любых специальных символов, которые могут появиться в последнем:

 <option value="<%= toDoItem.getId() %>"><%= HTMLEscaper.escape(toDoItem.toString()) %></option> 

Учитывая, что это относительно простое приложение, это довольно много кода Java, все еще задерживающегося в файле, который якобы будет создаваться и редактироваться веб-дизайнерами , которые, как правило, плохо разбираются в тонкостях языка Java и API.

Так что делать? Очевидно, что эта логика отображения требуется, но нам нужен лучший способ ее реализации, чем включение кода Java на нашу страницу.

На самом деле существует довольно много альтернатив, написанных различными группами, плавающими в Интернете. От простых библиотек тегов JSP (наборов пользовательских тегов, которые вы можете смешать с вашим HTML для реализации логики отображения) до полной замены JSP — галактика опций может быть унизительной. Однако решение, предлагаемое стандартом JSP 2.0, представляет собой комбинацию библиотеки тегов, называемой стандартной библиотекой тегов JSP (JSTL), и синтаксиса для доступа к значениям Java без кода Java, называемого языком выражений JSP (EL) .

Хотя EL встроен в JSP 2.0 и поэтому автоматически доступен в файлах JSP, JSTL — это отдельная библиотека, которая должна быть специально включена в ваше веб-приложение, прежде чем вы сможете ее использовать. В Tomcat 5.x вы можете получить файлы библиотеки JSTL 1.1 из веб-приложения jsp-examples, поставляемого с сервером. Просто перейдите в подкаталог webapps / jsp-examples / WEB-INF / lib установки сервера, возьмите jstl.jar и standard.jar и скопируйте их в подкаталог WEB-INF / lib веб-приложения списка дел.

Теперь вы можете использовать теги JSTL в ваших файлах JSP. Давайте изменим файл todo.jsp, чтобы создать тот же пользовательский интерфейс списка дел без каких-либо сценариев Java.

Во-первых, нам нужно объявить о нашем намерении использовать теги JSTL в файле. JSTL фактически разделен на несколько под-библиотек, поэтому вам не нужно загружать всю библиотеку, если вы просто хотите использовать пару ее тегов. Наиболее часто используемые теги находятся в подбиблиотеке Core, которую мы можем загрузить с помощью этой директивы @taglib, следующей сразу за директивой @page в верхней части файла:

 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 

Вот первый фрагмент кода Java в нашем файле todo.jsp. Мы используем его, чтобы получить контекстный путь (корень) нашего веб-приложения, чтобы мы могли найти файл CSS, который будет использоваться страницей:

  <link rel="stylesheet" type="text/css" href="<%= request.getContextPath() %>/styles.css" /> 

Например, если наше веб-приложение установлено в http://www.example.com/todo, тогда контекстным путем для приложения является / todo, и поэтому наша таблица стилей будет правильно расположена в /todo/styles.css.

Тег <c:url> библиотеки JSTL Core позволяет автоматически создавать URL-адреса относительно пути к контексту. Вот как выглядит скорректированный код:

  <link rel="stylesheet" type="text/css" href="<c:url value="/styles.css"/>" /> 

Да, вы правильно прочитали. Это тег JSTL в значении атрибута тега HTML. Приверженцы XML в аудитории могут стиснуть зубы, но JSP никогда не должен был быть действительным XML. Для соответствия стандартам XHTML вам просто нужно убедиться, что код генерирует действительный XML.

Следующий фрагмент кода Java в нашем файле todo.jsp немного сложнее. Он захватывает объект List, который контроллер хранит в атрибуте объекта запроса с именем toDoItems, а затем проверяет, содержит ли он какие-либо элементы списка дел:

  <% List toDoItems = (List)request.getAttribute("toDoItems"); Iterator it = toDoItems.iterator(); if (it.hasNext()) { %> 

Тег <c:if> позволяет нам проверять условие и выводить содержимое тега только тогда, когда это условие истинно. Тег выглядит так:

  <c:if test="<i>condition</i>">...</c:if> 

Но как мы определяем условие? Вот тут и вступает язык выражений JSP (EL). Атрибут test тега <c:if> принимает выражение EL в качестве условия для тестирования.

EL был разработан специально для того, чтобы действительно хорошо извлекать значения из атрибутов запроса, сеанса и приложения. Фактически, выражение EL, которое будет извлекать атрибут запроса toDoItems, просто так:

 ${toDoItems} 

Для наших целей мы действительно хотим пойти еще дальше и проверить, сколько элементов в списке toDoItems. Для этого нам нужно использовать функцию EL, которая требует добавления другой библиотеки JSTL с директивой @taglib в верхней части страницы:

 <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> 

После этого мы можем получить количество элементов в списке с помощью этого выражения EL:

 ${fn:length(toDoItems)} 

И мы можем определить, содержит ли список элементы с этим выражением:

 ${fn:length(toDoItems) > 0} 

Теперь мы можем написать <c:if> для проверки этого условия:

  <c:if test="${fn:length(toDoItems) > 0}">...</c:if> 

Далее мы будем использовать JSTL не для того, чтобы заменить логику проектирования Java, а для улучшения чистого HTML в нашем файле todo.jsp. Форма, которая отображает элементы списка дел и позволяет пользователю удалять их, начинается с этого <form> :

  <form action="DeleteItem.do" method="post"></form> 

Это нормально, если наш JSP находится в корневом каталоге нашего веб-приложения, но если бы мы переместили его в подкаталог, атрибут действия этого тега больше не указывал бы на DeleteItem.do в корне приложения. , Чтобы этот атрибут всегда указывал на правильный URL, мы можем использовать <c:url> же, как мы использовали для URL таблицы стилей выше:

  <form action="<c:url value="/DeleteItem.do"/>" method="post"> 

Кстати, мы можем внести то же изменение во второй <form> на странице, который указывает на AddItem.do.

Следующий фрагмент логики отображения Java в todo.jsp немного сложнее воспроизвести с помощью JSTL / EL:

  <select name="deleteid" size="<%= Math.max(2, toDoItems.size()) %>"> 

Не существует JSTL или EL, эквивалентного Java-методу Math.max, поэтому для получения списка <select> с размером, равным количеству элементов в toDoItems, но не менее 2, используя только стандартные функции JSTL / EL, нам нужно реализовать это логика более многословно

  <c:set var="selectSize" value="${fn:length(toDoItems)}"/> <c:if test="${selectSize < 2}"> <c:set var="selectSize" value="2"/> </c:if> 

Тег JSTL <c:set> позволяет нам установить переменную для использования в выражениях EL на остальной части страницы. В этом случае мы устанавливаем для него количество элементов в toDotems (которое мы снова получаем с помощью функции fn: length EL). Затем мы используем <c:if> для проверки, если значение меньше 2, и в этом случае мы используем другой <c:set> чтобы установить переменную на этот минимум.

Затем мы можем вывести значение с выражением в атрибуте размера нашего <select> :

  <select name="deleteid" size="${selectSize}"> 

Да — вы также можете использовать выражения EL в атрибутах HTML-тегов.

Это решение довольно грязное, хотя. Разве не было бы лучше, если бы у нас была функция EL, которая выполняла бы то же самое, что и Math.max? Оказывается, расширить EL своими собственными функциями довольно легко.

Во-первых, вам нужно создать класс Java с открытым статическим методом, который делает то, что вы хотите, чтобы ваша EL-функция делала. Для этого проекта давайте создадим класс с именем com.sitepoint.jsp.Functions (в com / sitepoint / jsp / Functions.java). Код для этого класса будет очень простым:

 package com.sitepoint.jsp; public class Functions { public static int max(int i1, int i2) { return Math.max(i1, i2); } } 

Затем вам нужно определить библиотеку пользовательских тегов, чтобы установить метод max этого класса как пользовательскую функцию EL. Для этого создайте файл XML с именем functions.tld и поместите его в каталог WEB-INF проекта. Помимо первого тега (который всегда один и тот же), большая часть кода довольно понятна:

 <?xml version="1.0" encoding="iso-8859-1"?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0"> <tlib-version>1.0</tlib-version> <short-name>Functions</short-name> <uri>http://www.sitepoint.com/jsp/taglibs/functions</uri> <function> <name>max</name> <function-class>com.sitepoint.jsp.Functions</function-class> <function-signature>int max(int, int)</function-signature> </function> </taglib> 

Этот файл связывает уникальный идентификатор в форме URL (http://www.sitepoint.com/jsp/taglibs/functions) с библиотекой функций EL. Одна функция в этой библиотеке будет называться max и будет вызывать метод max класса com.sitepoint.jsp.Functions.

Чтобы использовать эту библиотеку в нашем JSP, нам нужно добавить директиву @taglib для нее в верхней части страницы:

 <%@ taglib prefix="spfn" uri="http://www.sitepoint.com/jsp/taglibs/functions" %> 

Теперь мы можем вычислить атрибут размера нашего <select> используя одно выражение EL:

  <select name="deleteid" size="${spfn:max(2,fn:length(toDoItems))}"> 

Далее в нашей логике отображения Java у нас есть цикл while, который перебирает элементы списка дел, вытягивая каждый из них для обработки:

  <% while (it.hasNext()) { ToDoItem toDoItem = (ToDoItem) it.next(); %> 

Это почти позорно просто сделать с помощью <c:forEach> JSTL:

  <c:forEach var="toDoItem" items="${toDoItems}"> 

Опять же, мы используем простое выражение EL $ {toDoItems} для доступа к атрибуту запроса, предоставленному контроллером. Этот тег будет выводить его содержимое один раз для каждого элемента в списке, назначая этот элемент имени переменной EL, указанному атрибутом var (в данном случае toDoItem).

Наши последние кусочки логики отображения Java используются для вывода идентификатора и текста объектов ToDoItem, содержащихся в списке:

  <option value="<%= toDoItem.getId() %>"><%= HTMLEscaper.escape(toDoItem.toString()) %></option> 

Вот где EL действительно вступает в свои права. В дополнение к кардинальному упрощению процесса извлечения значений из атрибутов запроса, это также действительно хорошо для извлечения значений из этих значений! Позвольте мне показать вам, что я имею в виду …

Хотя я не упоминал об этом в то время, класс ToDoItem был разработан в соответствии со стандартом JavaBeans . Хотя в нем есть несколько более сложных разделов, спецификация JavaBeans (не путать с Enterprise JavaBeans — совершенно другая спецификация) в основном представляет собой соглашение об именах для классов и методов.

Согласно спецификации JavaBeans, если у объекта есть свойство (значение, которое может быть прочитано и записано), то для доступа к нему у него должны быть методы getPropertyName и setPropertyName . Если это свойство только для чтения, то оно должно иметь только метод getPropertyName. Если свойство содержит логическое значение, метод get должен называться isPropertyName.

Спекуляция намного шире, чем это, но это самое необходимое. Так как класс ToDoItem имеет метод getId, он имеет свойство только для чтения с именем id.

Теперь смысл всего этого в том, что EL хорошо разбирается в JavaBeans. Это позволяет легко получить значения свойств JavaBean. Таким образом, чтобы вывести значение свойства id ToDoItem, мы можем просто использовать это выражение:

 ${toDoItem.id} 

Что касается текстового значения ToDoItem, EL автоматически преобразует любой объект в строку, используя его метод toString, поэтому мы могли бы просто использовать $ {toDoItem}. Наш Java-код, однако, также использовал класс HTMLEscaper для экранирования любых символов, которые могут быть интерпретированы как HTML-код. Но подождите: JSTL / EL тоже может это сделать!

На самом деле есть два способа избежать специальных символов HTML с помощью JSTL / EL. Во-первых, используйте <c:out> JSTL <c:out> с атрибутом escapeXml:

 <c:out value="${toDoItem}" escapeXml="true"/> 

Еще более удобной является EL-функция fn: escapeXml:

 ${fn:escapeXml(toDoItem)} 

Итак, вот как вывести наш список дел в наш новый файл todo.jsp:

  <option value="${toDoItem.id}">${fn:escapeXml(toDoItem)}</option> 

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

Со всеми этими изменениями наш готовый файл todo.jsp выглядит следующим образом:

 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="spfn" uri="http://www.sitepoint.com/jsp/taglibs/functions" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>To-Do List</title> <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" /> <link rel="stylesheet" type="text/css" href="<c:url value="/styles.css"/>" /> </head> <body> <c:if test="${fn:length(toDoItems) > 0}"> <form action="<c:url value="/DeleteItem.do"/>" method="post"> <select name="deleteid" size="${spfn:max(2,fn:length(toDoItems))}"> <c:forEach var="toDoItem" items="${toDoItems}"> <option value="${toDoItem.id}">${fn:escapeXml(toDoItem)}</option> </c:forEach> </select> <input type="submit" value="Delete Selected Item"/> </form> </c:if> <form action="<c:url value="/AddItem.do"/>" method="post"> <input type="text" name="newtodo"/> <input type="submit" value="Add New Item"/> </form> </body> </html> 

Хотя синтаксис JSTL / EL в этом файле, безусловно, новый, вашему среднестатистическому веб-дизайнеру будет намного легче понять его, чем Java-код, который был там ранее!

Вот структура файлов и каталогов нашего обновленного приложения:

  /todo.jsp
 /styles.css
 /WEB-INF/functions.tld
 /WEB-INF/web.xml
 /WEB-INF/classes/com/sitepoint/AddToDoServlet.class
 /WEB-INF/classes/com/sitepoint/AddToDoServlet.java
 /WEB-INF/classes/com/sitepoint/DeleteToDoServlet.class
 /WEB-INF/classes/com/sitepoint/DeleteToDoServlet.java
 /WEB-INF/classes/com/sitepoint/ToDoItem.class
 /WEB-INF/classes/com/sitepoint/ToDoItem.java
 /WEB-INF/classes/com/sitepoint/ToDoList.class
 /WEB-INF/classes/com/sitepoint/ToDoList.java
 /WEB-INF/classes/com/sitepoint/ToDoServlet.class
 /WEB-INF/classes/com/sitepoint/ToDoServlet.java
 /WEB-INF/classes/com/sitepoint/jsp/Functions.class
 /WEB-INF/classes/com/sitepoint/jsp/Functions.java
 /WEB-INF/lib/jstl.jar
 / WEB-INF / lib / mysql-connector-java- версия -bin.jar
 /WEB-INF/lib/standard.jar 

Загрузить код (759 КБ)

Что касается передового опыта JSP, то этот обновленный пример списка дел примерно так же хорош, как и следуя официальным спецификациям. И по общему признанию, у нас здесь есть довольно мощная платформа веб-приложений. Конечно, вы могли бы создавать масштабные приложения, используя только классы Java для модели, сервлеты Java для контроллера и JSP, используя для представления JSTL / EL, но есть более мощные варианты.

Вместо ручного кодирования доступа к базе данных в ваших классах модели вы можете использовать постоянную среду для автоматической синхронизации вашей базы данных с набором автоматически сгенерированных объектов Java. Hibernate является дорогой день в этой области.

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

Вместо того, чтобы полагаться на теги JSTL и выражения EL, улучшенные вашими собственными пользовательскими функциями, вы можете использовать другие библиотеки тегов, такие как JavaServer Faces или одну из многих тегов с открытым исходным кодом , или вы можете отойти от JSP и использовать систему шаблонов, такую ​​как Velocity или XMLC. ,

Вы могли бы почти сказать, что есть больше способов для создания веб-приложений с Java, чем без него! В ближайшие месяцы я рассмотрю многие из этих вариантов критически и постараюсь посоветовать вам, какой использовать для какого рода проекта.