Одной из самых приятных особенностей Java является её многогранная природа. Конечно, создание традиционных десктопных и даже мобильных приложений — это здорово. Но что, если вы хотите уйти с проторенных дорожек и зайти на территорию разработки web приложений на Java? Для вас есть хорошая новость: в комплекте с языком идёт полноценный Servlet API, который позволяет вам создавать надёжные веб-приложения без особых хлопот.
Это статья для продолжающих. Если вы только начинаете знакомство с Java, советуем пройти быстрый старт, посмотреть видеокурс и выбрать одну из книг для новичков.
Создание приложений на Java с помощью Servlets
Встречайте сервлеты, особый тип Java-программ, выполняемый в пределах веб-контейнера (также называемый контейнером сервлетов, как, например, Tomcat и Jelly), которые позволяют обрабатывать запросы клиентов и ответы сервера просто и эффективно. Сейчас не время и не место дотошно объяснять вам, что из себя представляет сервлет. Достаточно сказать, что сервлеты создаются и уничтожаются их контейнерами, а не разработчиком, и действуют как промежуточный уровень между клиентами (как правило, веб-браузерами) и другими приложениями, запущенными на сервере (например, базами данных).
Сервлет — классная штука, которая помимо всего прочего может принимать данные от клиента, как правило через GET и POST-запросы, работать с cookie и параметрами сеанса. А ещё она обрабатывает данные через дополнительные уровни приложений и отправляет выходные данные клиенту как в текстовом, так и в бинарном форматах (HTML, XML, PDF, JPG, GIF и т.д.), во многих случаях используя Java Server Pages (JSP) файлы.
Лучше всего начинать работу с сервлетами через конкретный пример. Мы создадим простое веб-приложение, которое позволит клиентам регистрироваться с помощью простой HTML-формы. Данные, предоставленные клиентами, будут собираться сервлетом и проверяться валидаторами.
Разбираемся с конфигурационными файлами
Чтобы вы понимали структуру нашего будущего приложения, вот как она будет выглядеть:
Первым шагом к созданию приложения является определение так называемого дескриптора развёртывания. Он указывает, как приложение должно быть развёрнуто в определенной среде. Когда дело касается веб-приложений, дескриптор развёртывания представляет собой простой XML-файл, называемый web.xml
и является частью стандартной спецификации Java EE. В нашем случае он будет выглядеть так:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
metadata-complete="false">
</web-app>
Как вы видите, web.xml
только определяет версию Java Servlet Specification (3.1), которую мы будем использовать в приложении. Разумеется, в нём может быть гораздо больше содержимого, включая директивы сопоставления сервлетов, параметры инициализации, список приветственных файлов и несколько дополнительных настроек. Но чтобы не усложнять процесс разработки, давайте оставим его таким как есть.
Так как наше приложение будет использовать Servlet API и Java Servlet Pages (JSP) для отображения данных клиента, нам нужно загрузить все необходимые JAR. Maven сделает за нас трудную работу, вот как будет выглядеть файл POM:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.electricalweb.customerapplication</groupId>
<artifactId>customerapplication</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>Customer Application</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>jstl<groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
<build>
<finalName>Customer Application</finalName>
</build>
</project>
Если говорить просто, pom.xml
определяет все зависимости, необходимые для работы с сервлетами: JSP, Java Standard Tag Library (JSTL) и Java Expression Language (JEL).
Итак, мы уже создали конфигурационные файлы приложения. Однако в текущем состоянии оно буквально ничего не делает. Мы хотим, чтобы клиенты могли регистрироваться с помощью HTML-формы, поэтому следующее, что нам нужно сделать, — это создать JSP-файлы, которые будут отображать вышеупомянутую форму и данные клиента после успешного завершения регистрации. Этим мы сейчас и займёмся.
Работаем над внешним видом
Внешний вид приложения будет определяться двумя JSP-файлами — в контексте MVC они называются представлениями. Первый будет отвечать за отображение формы регистрации и возможных ошибок, вызванных после проверки введённых данных. Второй будет обычной страницей приветствия, в которой будут показаны данные, введённые клиентом, после успешного завершения процесса регистрации.
Вот первый JSP-файл:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Регистрация</title>
</head>
<body>
<h1>Регистрация</h1>
<c:if test="${violations != null}">
<c:forEach items="${violations}" var="violation">
<p>${violation}.</p>
</c:forEach>
</c:if>
<form action="${pageContext.request.contextPath}/processcustomer" method="post">
<label for="firstname">Имя : </label>
<input type="text" name="firstname" id="firstname" value="${firstname}">
<label for="lastname">Фамилия:
<input type="text" name="lastname" id="lastname" value="${lastname}">
<label for="email">Email: </label>
<input type="text" name="email" id="email" value="${email}">
<input type="submit" name="signup" value="Sign Up">
</form>
</body>
</html>
Файл содержит простой HTML с парочкой дополнений. Вот она, прелесть JSP в сочетании с JSTL и JEL. Обратите внимание на то, как легко проверить наличие ошибок валидации, используя такие стандартные теги, как <с:if>
и <c:forEach>
.
Атрибут формы регистрации action
указывает на следующий URL: ${pageContext.request.contextPath}/processcustomer
. Это значит, что каждый раз, когда клиент пытается зарегистрироваться, данные будут отправляться в processcustomer
независимо от URL, по которому доступна форма. Это достигается за счёт функциональности объектов, доступных из JSP-файла, таких как request
.
Скоро мы увидим, как сервлет связывается с URL processcustomer
и как он взаимодействует с введёнными данными. А пока давайте посмотрим на JSP-файл, который отвечает за страницу приветствия:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Данные клиента</title>
</head>
<body>
<h1>Спасибо за регистрацию!</h1>
<h2>Ваши введённые данные:</h2>
<p><strong>Имя:</strong> ${firstname}</p>
<p><strong>Фамилия:</strong> ${lastname}</p>
<p><strong>Email: </strong>${email}</p>
</body>
</html>
Теперь, когда мы разобрались с отображением страниц, следующим шагом будет создание сервлета, ответственного за сбор данных клиента из POST-запросов и подтверждение данных простым способом.
Пишем контроллер
Написать сервлет, способный получить данные из формы регистрации, проще простого. Всё, что нам нужно сделать, — это написать подкласс для класса HttpServlet
и реализовать его методы doGet()
или doPost()
(или оба, если надо). В данном случае сервлет будет взаимодействовать с данными, поступающими из POST-запросов.
Вот как он выглядит:
@WebServlet(name = "CustomerController", urlPatterns = "/processcustomer")
public class CustomerController extends HttpServlet {
@Override
protected void doPost(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
RequestCustomer customer = RequestCustomer.fromRequestParameters(request);
customer.setAsRequestAttributes(request);
List violations = customer.validate();
if (!violations.isEmpty()) {
request.setAttribute("violations", violations);
}
String url = determineUrl(violations);
request.getRequestDispatcher(url).forward(request, response);
}
private String determineUrl(List violations) {
if (!violations.isEmpty()) {
return "/";
} else {
return "/WEB-INF/views/customerinfo.jsp";
}
}
private static class RequestCustomer {
private final String firstName;
private final String lastName;
private final String email;
private RequestCustomer(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public static RequestCustomer fromRequestParameters(
HttpServletRequest request) {
return new RequestCustomer(
request.getParameter("firstname"),
request.getParameter("lastname"),
request.getParameter("email"));
}
public void setAsRequestAttributes(HttpServletRequest request) {
request.setAttribute("firstname", firstName);
request.setAttribute("lastname", lastName);
request.setAttribute("email", email);
}
public List validate() {
List violations = new ArrayList<>();
if (!StringValidator.validate(firstName)) {
violations.add("Имя является обязательным полем");
}
if (!StringValidator.validate(lastName)) {
violations.add("Фамилия является обязательным полем");
}
if (!EmailValidator.validate(email)) {
violations.add("Email должен быть правильно сформирован");
}
return violations;
}
}
}
Первое, на что здесь стоит обратить внимание, — использование аннотации @WebServlet(name ="CustomerController", urlPatterns = "/processcustomer")
. Она говорит контейнеру сервлета использовать класс CustomerController
для обработки HTTP-запросов по адресу /processcustomer
. Того же эффекта можно достичь путём добавления директив сопоставления сервлетов в web.xml
, как здесь, но так как мы используем Servlet Specification 3.1 нам нет необходимости прибегать к такому способу.
Здесь мы назвали сервлет CustomerController
, так как считается хорошей практикой использовать имя класса сервлета в качестве значения атрибута name
аннотации @WebServlet
. В противном случае некоторые контейнеры не смогут выполнить сопоставление, что приведёт к ошибке 404.
Сам класс CustomerController
выполняет несколько простых задач. Во-первых, он собирает введённые в форму данные, используя реализацию интерфейса HttpServletRequest
, который содержит значения, соответствующие полям firstname
, lastname
и email
формы. Затем он устанавливает эти значения в качестве атрибутов запроса, поэтому их можно повторно отобразить либо в форме, либо на странице с результатами. Наконец, валидаторы проверяют правильность введённых данных.
Валидаторы — это простые классы, которые проверяют определённые свойства, например, является ли строка пустой и выглядит ли email как email. На GitLab автора можно посмотреть на их реализацию.
Результат валидации влияет на дальнейший ход событий: если данные не валидны, клиент перенаправляется через объект RequestDispatcher на страницу регистрации, где отображаются соответствующие ошибки. Если всё в порядке, то отображается страница приветствия.
Итак, мы создали целое веб-приложение на Java, которое позволяет зарегистрировать клиентов с помощью HTML-формы, базового сервлета и нескольких JSP-файлов. Пора его запустить.
Запускаем приложение
Для запуска приложения нужно проделать следующие шаги:
- Для начала нам понадобится Git (убедитесь, что скачиваете подходящую версию), Maven и контейнер сервлета (например, Apache Tomcat, Jetty, или JBoss Wildfly). Если что-то из этого уже включено в вашу IDE, то вы можете использовать встроенный вариант.
- Используйте Git, чтобы клонировать репозиторий приложения, и импортируйте его в вашу IDE, желательно как проект Maven.
- Разверните проект в контейнере сервлета и запустите его. Развёртывание подразумевает создание WAR-файла или exploded WAR и его помещение в папку развёртывания контейнера по умолчанию. Зачастую IDE способна сделать развёртывание за вас, поэтому не перегружайте себя лишней работой и посмотрите документацию вашей IDE (документация для IntelliJ IDEA). Когда вы развернёте проект и запустите его, должен запуститься бразуер по умолчанию с окном регистрации.
- Попробуйте заполнить не все поля в форме или вовсе не заполнить их и вы увидите, как поверх соответствующих полей отобразятся ошибки. Введите всё как положено, и вас перенаправит на страницу приветствия.
Заключение
Итак, вы приобрели все навыки, необходимые для создания собственного веб-приложения на Java без необходимости прибегать к сложным фреймворкам. Всё, что вам нужно, — Servlet API, технология вроде JSP для отображения и встроенные средства Java. Здорово, правда?
Стоит отметить, что реализация класса CustomerController
подчеркивает достоинства и недостатки сервлетов: с одной стороны, он вкратце показывает, насколько легко обрабатывать параметры запроса и отправлять ответы клиенту в разных форматах. Но эта функциональность имеет свою цену: обе реализации интерфейсов HttpServletResponse
и HttpServletResponse
являются обычными локаторами служб. Нельзя сказать, что это плохо, поскольку локаторы просто содержат данные. Однако нужно помнить, что эти реализации будут всегда привязаны к сервлету.
Перевод статьи «Building a Web App with Java Servlets»