Перейти к содержанию

JSP (JavaServer Pages)

JSP - Это технология для динамических веб-страниц: HTML-шаблон со вставками Java-кода, тегов и выражений. - отделяет представление (HTML) от логики приложения (Java-код) - JSP автоматически компилируется в сервлет при первом обращении, поэтому сервлет вручную писать не нужно (см. Java Servlets)


JSP и сервлеты

JSP - это тот же сервлет, только написанный «наизнанку»: в сервлете HTML печатается из Java-кода, в JSP Java-код вставляется в HTML.

Сервлет JSP
Что пишешь Java-класс, HTML через out.println() HTML-страницу с тегами и выражениями
Удобно для Логики: разбор запроса, вызов сервисов, выбор ответа Представления: вёрстка, вывод данных
Изменения Нужна перекомпиляция и передеплой Контейнер сам перекомпилирует изменённую страницу
Первый запрос Быстрый Медленнее: трансляция и компиляция
Ошибки в коде Видны при компиляции Видны только при первом обращении к странице

Область применения - слой представления (View). В связке с сервлетом-контроллером это Model 2 (см. Model 1 и Model 2). Сейчас JSP - legacy: вместо него берут Thymeleaf, JSF (Facelets) или SPA на фронтенде.


Жизненный цикл JSP

Жизненный цикл JSP почти совпадает с жизненным циклом сервлета, потому что JSP в итоге компилируется в сервлет.

  1. Трансляция - контейнер преобразует JSP в Java-файл сервлета.
  2. Компиляция - Java-файл компилируется в .class.
  3. Загрузка - класс сервлета загружается в контейнер, создаётся экземпляр.
  4. Инициализация - вызывается jspInit(), после неё JSP готова обрабатывать запросы.
  5. Обработка запросов - на каждый запрос вызывается _jspService(), он формирует ответ.
  6. Уничтожение - JSP больше не нужна или сервер останавливается: контейнер вызывает jspDestroy().

Шаги 1-2 выполняются при первом запросе и повторяются, когда файл JSP изменился.

page.jsp → page_jsp.java → page_jsp.class → jspInit() → _jspService() × N → jspDestroy()

Структура JSP-страницы

Элемент Синтаксис Что делает
Комментарий <%-- ... --%> Не попадает в HTML-ответ, в отличие от <!-- -->
Директива <%@ page ... %>, <%@ include ... %>, <%@ taglib ... %> Настройка страницы при трансляции
Объявление <%! ... %> Поле или метод класса сервлета
Скриптлет <% ... %> Произвольный Java-код внутри _jspService()
Выражение <%= ... %> Вычислить и вывести в ответ
<%-- Это комментарий JSP, не попадает в HTML-выход --%>
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<%@ include file="header.jsp" %>

<%! private int counter = 0; %>
<% counter++; // изменение переменной %>
<p>Сообщение: <%= message %></p>

Java-код внутри JSP

Правила записи: - код скриптлетов попадает в метод _jspService(): переменные из <% %> - локальные, свои у каждого запроса - объявления <%! %> становятся полями класса. Экземпляр один на все запросы, поэтому counter из примера выше общий для всех потоков и меняется без синхронизации - выражение <%= %> пишется без ; в конце: оно целиком подставляется в out.print(...) - импорт классов - директивой <%@ page import="java.util.List" %>

Стандартные (неявные) переменные - объекты, которые доступны в скриптлетах и выражениях без объявления:

Переменная Тип Что это
request HttpServletRequest Текущий запрос
response HttpServletResponse Ответ
out JspWriter Вывод в ответ: out.println(...)
session HttpSession Сессия пользователя
application ServletContext Контекст приложения
config ServletConfig Настройки этого сервлета
pageContext PageContext Доступ ко всем областям видимости
page Object Сам экземпляр сервлета, this
exception Throwable Только на странице ошибок с isErrorPage="true"

JavaBeans в JSP

Через JavaBean в JSP передают и выводят данные, отделяя бизнес-логику от представления.

Как использовать в JSP: 1) Создать класс бина по соглашениям JavaBean. 2) Импортировать его директивой <%@ page import="..." %>. 3) Создать или найти экземпляр и задать свойства: <jsp:useBean> и <jsp:setProperty>. 4) Вывести свойства: <jsp:getProperty> или EL ${user.name}.

<%@ page import="com.example.User" %>

<jsp:useBean id="user" class="com.example.User" scope="session" />   <%-- найти в сессии или создать --%>
<jsp:setProperty name="user" property="name" value="Алексей" />
<jsp:setProperty name="user" property="age" value="30" />

<p>Имя: <jsp:getProperty name="user" property="name" /></p>
<p>Возраст: <jsp:getProperty name="user" property="age" /></p>

Стандартные теги JSP

Стандартные теги (actions) заменяют типовой Java-код в скриптлетах.

Тег Что делает
<jsp:useBean id="user" class="..." scope="session" /> Найти бин в области видимости или создать новый
<jsp:setProperty name="user" property="name" value="..." /> Задать свойство бина
<jsp:getProperty name="user" property="name" /> Вывести свойство бина
<jsp:include page="header.jsp" /> Вставить ответ другой страницы во время запроса
<jsp:forward page="welcome.jsp" /> Передать запрос другой странице (forward)
<jsp:param name="..." value="..." /> Параметр для include и forward

<jsp:include> и директива <%@ include %> различаются моментом: директива вставляет текст файла при трансляции, тег - результат выполнения страницы на каждом запросе.


Expression Language (EL)

EL - Это упрощённый синтаксис ${...} для доступа к данным: свойствам бинов, коллекциям, атрибутам запроса и сессии. - ${user.name} вызывает user.getName() - имя ищется по областям видимости по порядку: page → request → session → application - вместе с JSTL (<c:if>, <c:forEach>) убирает Java-код из страницы

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<jsp:useBean id="user" class="com.example.User" scope="session" />
<jsp:setProperty name="user" property="name" value="Алексей" />
<jsp:setProperty name="user" property="age" value="30" />

<html>
<head>
    <title>Пример JSP с EL и стандартными тегами</title>
</head>
<body>
    <h2>Информация о пользователе:</h2>
    <p>Имя: ${user.name}</p>
    <p>Возраст: ${user.age}</p>

    <c:if test="${user.age >= 18}">
        <p>Вы совершеннолетний.</p>
    </c:if>

    <h3>Список покупок:</h3>
    <c:forEach var="item" items="${shoppingList}">
        <p>${item.name}</p>
    </c:forEach>
</body>
</html>

Note

URI http://java.sun.com/jsp/jstl/core - из Java EE. В Jakarta Tags 3.0 (Jakarta EE 10) пишут uri="jakarta.tags.core".


Настройка JSP в web.xml

В web.xml для JSP есть элемент <jsp-config> с настройками для всех страниц приложения.

  • <jsp-property-group> - группа настроек для набора страниц, чтобы не дублировать их
  • <url-pattern> - к каким страницам применяется группа
  • <el-ignored> - игнорировать выражения EL
  • <scripting-invalid> - запретить скриптлеты
  • <is-xml> - страницы в XML-синтаксисе
  • <trim-directive-whitespaces> - удалять пустые строки, которые остаются от директив
<jsp-config>
    <jsp-property-group>
        <url-pattern>*.jsp</url-pattern>
        <el-ignored>false</el-ignored>
        <scripting-invalid>true</scripting-invalid>              <!-- только EL и теги, без Java-кода -->
        <trim-directive-whitespaces>true</trim-directive-whitespaces>
    </jsp-property-group>
</jsp-config>

Model 1 и Model 2

Две архитектурные модели веб-приложения на Java EE. Обе вырастают из шаблона MVC, разница в том, кто принимает запрос.

Model 1 - Это модель, где запрос принимает сама JSP-страница. Представление и бизнес-логика объединены в одном компоненте: логика в скриптлетах или в JavaBeans, которые JSP вызывает напрямую.

<%@ page import="com.example.User" %>
<%@ page import="com.example.UserService" %>
<%
    UserService userService = new UserService();   // бизнес-логика прямо в странице
    User user = userService.getUserById(request.getParameter("id"));
%>
<html>
<head>
    <title>User Details</title>
</head>
<body>
    <h1>User Details</h1>
    <p>Name: <%= user.getName() %></p>
    <p>Email: <%= user.getEmail() %></p>
</body>
</html>

Model 2 - Это MVC на сервлетах и JSP: запрос принимает сервлет-контроллер, JSP только отображает. - Controller - сервлет: разбирает запрос, вызывает модель, выбирает представление - Model - JavaBeans и сервисные классы с бизнес-логикой - View - JSP: берёт данные из атрибутов запроса и выводит их

Как проходит запрос: 1) Сервлет получает запрос и его параметры. 2) Вызывает сервис и получает данные. 3) Кладёт данные в атрибут запроса: request.setAttribute("user", user). 4) Делает forward на JSP (см. Диспетчеризация запросов). 5) JSP достаёт атрибут и формирует HTML.

@WebServlet("/userDetails")
public class UserController extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String userId = request.getParameter("id");
        UserService userService = new UserService();
        User user = userService.getUserById(userId);

        request.setAttribute("user", user);   // данные для представления
        RequestDispatcher dispatcher = request.getRequestDispatcher("userDetails.jsp");
        dispatcher.forward(request, response);
    }
}
<%@ page import="com.example.User" %>
<%
    User user = (User) request.getAttribute("user");   // то же через EL: ${user.name}
%>
<html>
<head>
    <title>User Details</title>
</head>
<body>
    <h1>User Details</h1>
    <p>Name: <%= user.getName() %></p>
    <p>Email: <%= user.getEmail() %></p>
</body>
</html>

Реализация на Java EE. Сервлеты и JSP из коробки дают всё для Model 2. Фреймворки строят Model 2 поверх них: в Spring MVC контроллер - один DispatcherServlet (front controller), он раздаёт запросы методам @Controller (см. Spring Web MVC).

Note

В исходной версии Spring MVC был назван фреймворком «для управления зависимостями». Зависимостями управляет Spring Core (IoC-контейнер), а Spring MVC - реализация Model 2 для веба.


Model 1 Model 2
Кто принимает запрос JSP-страница Сервлет-контроллер
Где бизнес-логика В JSP: скриптлеты или бины, вызванные из страницы В модели: сервисы и бины, их вызывает контроллер
Роль JSP Всё сразу Только представление
Плюсы Просто реализовать, быстрая разработка Чистое разделение компонентов, код читаемее и проще в поддержке, логика тестируется отдельно
Минусы Для крупных приложений код становится сложным и трудно поддерживаемым, логику трудно тестировать Сложнее реализовать
Когда Простые приложения Крупные и сложные системы

Итого: разделение бизнес-логики и представления делает код модульнее и проще в сопровождении. Поэтому Model 2 стала стандартом, и на ней построены все MVC-фреймворки.