
Недавно мне было лень делать основную работу, и я подумал, что было бы неплохо сделать что-нибудь другое из списка задач. Я вспомнил, что в приложении ошибки до сих пор показываются в самом стандартном виде – это пугает пользователей и выглядит не очень красиво, ведь всё остальное приложение уже оформлено в соответствии с брэндбуком (корпоративные цвета, один шрифт и всё такое), а страница с исключением очень уж отличается от выбранного дизайна.

Примерно так выглядят исключения. Здесь есть все самые важные данные: и статус, и название исключения, и стэк-трейс. Но я решил, что пришло время эту информацию ещё и самостоятельно оформить и избавиться от печального белого экрана. И в этой статье расскажу, как это сделать в приложении с сервлетами и jsp-страницами.
Сервлет для обработки исключений и ошибок
Для начала нужно сделать новый сервлет. Выглядеть он будет абсолютно так же, как и все остальные: наследуется от HttpServlet, есть методы doGet и doPost. Я предлагаю из них обоих просто вызывать ещё один метод для обработки исключений и ошибок process.
В итоге обработчик будет находиться по адресу /exceptionHandler, называется класс ExceptionHandler и выглядит следующим образом:
@WebServlet("/exceptionHandler")
public class ExceptionHandler extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processError(request, response);
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processError(request, response);
}
private void processError(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
// Здесь будет код с обработкой исключения или ошибки
}
}
Ну и не надо забывать о дескрипторе – нужно добавить информацию о новом сервлете в web.xml. Сделать это можно двумя способами. Например, можно отталкиваться от типа исключения, тогда нужен тэг <exception-type>.
error-page>
<exception-type>javax.servlet.ServletException</exception-type>
<location>/exceptionHandler</location>
</error-page>
А можно отталкиваться от кода ошибки, тогда указывается <error-code>.
</error-page>
<error-code>404</error-code>
<location>/exceptionHandler</location>
</error-page>
Соответственно, для разных типов исключений и разных ошибок можно создавать разные сервлеты и по-разному их обрабатывать. Моя обработка будет не очень объёмной, я просто вывожу информацию об ошибке на экран, поэтому всё будет вести к одному сервлету:
<error-page>
<error-code>404</error-code>
<location>/exceptionHandler</location>
</error-page>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/exceptionHandler</location>
</error-page>
Я также не стал вдаваться в подробности и указал лишь java.lang.Throwable, как самый общий класс для всех исключений.
Обработка исключений и ошибок
Теперь, когда сервлет на месте, можно приступить к обработке информации в методе processError:
Способ 1. Вывод информации с помощью PrintWriter.
Самый примитивный способ – просто напихать всю нужную информацию в Writer. Например, так:
private void processError(HttpServletRequest request,
HttpServletResponse response) throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.write("<html><head><title>Ошибочка вышла!</title></head><body>");
out.write("<h3>Ошибка</h3>");
out.write("<p>Что-то пошло не так, и произошла ошибка. Программист обязательно всё когда-нибудь исправит.</p>");
out.write("</body></html>");
}
Можно добавить немного больше информации: например, адрес сервлета, код и т. д.
private void processError(HttpServletRequest request,
HttpServletResponse response) throws IOException {
Throwable throwable = (Throwable) request
.getAttribute("javax.servlet.error.exception");
Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
String servletName = (String) request
.getAttribute("javax.servlet.error.servlet_name");
String requestUri = (String) request
.getAttribute("javax.servlet.error.request_uri");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.write("<html><head><title>Ошибочка вышла!</title></head><body>");
out.write("<h3>Ошибка</h3>");
out.write("<ul><li>Сервлет:"+servletName+"</li>");
out.write("<li>Исключение:"+throwable.getClass().getName()+"</li>");
out.write("<li>Запрошенный URI:"+requestUri+"</li>");
out.write("<li>Сообщение:"+throwable.getMessage()+"</li>");
out.write("</ul>");
out.write("</body></html>");
}
Но никакой красоты и элегантности от этого способа ожидать не стоит. По сути, мы создаём такую же уродливую страничку, которая никак не сочетается с оформлением приложения, как и стандартная страница об ошибке. Только тут мы ещё строчка за строчкой прописываем весь HTML код, только из сервлета. Ужасно.
Способ 2. Jsp-страница
Так как мне нужно было сохранить оформление, как на остальных страницах, то построчный вывод кода из прошлого способа вообще не подходил.Вместо этого я собираю нужную информацию (всё ту же информацию об ошибке) и отправляю её в jsp-страницу, не заморачиваясь с тем, как страница будет выглядеть. Мне кажется, так логичнее, ведь в обязанности сервлета не должен входить внешний вид страниц.
private void processError(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
Throwable throwable = (Throwable) request
.getAttribute("javax.servlet.error.exception");
Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
String servletName = (String) request
.getAttribute("javax.servlet.error.servlet_name");
String requestUri = (String) request
.getAttribute("javax.servlet.error.request_uri");
SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy HH:mm");
request.setAttribute("date", format.format(new Date()));
request.setAttribute("statusCode", statusCode);
request.setAttribute("servletName", servletName);
request.setAttribute("requestUri", requestUri);
if (statusCode == 500) {
request.setAttribute("stackTrace", throwable.getStackTrace());
request.setAttribute("exception", throwable.getClass().getName());
} else {
request.setAttribute("stackTrace", null);
request.setAttribute("exception", "Error");
}
request.getRequestDispatcher( "errorPage.jsp").forward(request, response);
response.flushBuffer();
}
Так как я вообще все нештатные ситуации решаю с помощью этого сервлета, то нужно было разграничить ситуации с кодом 500 и остальные. В первом случае у нас есть исключение, из которого можно достать информацию вроде названия класса, или сообщения с помощью getMessage (у меня в примере этого метода нет, но ничего не мешает вам его добавить к себе). Во втором случае throwable будет null, поэтому любая попытка достать оттуда любую информацию поломает всю систему и мы снова получим некрасивую стандартную страничку.
Ниже страница errorPage.jsp. Стили описаны в файле error.css, а меню приложения и футер добавляются подключением файлов nav.jsp и footer.jsp, соответственно, как и на всех других страницах приложения.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<%@include file="head.jsp"%>
<%@include file="nav.jsp"%>
<style><%@include file="/WEB-INF/css/error.css"%></style>
</head>
<body>
<main>
<div class="container">
<div class="main-wrapper">
<div class="main-error-block">
<div class="error-area">
<h4>Ошибка</h4>
<p>Покажите эту страницу с информацией об ошибке администратору системы, чтобы он мог найти причину</p>
</div>
<div class="error-info-area">
<table>
<tr>
<td class="error-label">Статус:</td>
<td class="error-info">${statusCode}</td>
</tr>
<tr>
<td class="error-label">Сервлет:</td>
<td class="error-info">${servletName}</td>
</tr>
<tr>
<td class="error-label">Адрес: </td>
<td class="error-info">${requestUri}</td>
</tr>
<tr>
<td class="error-label">Exception: </td>
<td class="error-info">${exception}</td>
</tr>
<tr>
<td class="error-label"><b>Дата: </b></td>
<td>${date}</td>
</tr>
</table>
</div>
<div class="stack-trace-area">
<p><b>Stack trace:</b></p>
<c:forEach items="${stackTrace}" var="list">
<p> ${list} </p>
</c:forEach>
</div>
</div>
</div>
</div>
</main>
<%@include file="footer.jsp"%>
</body>
</html>


И в итоге всё стало выглядеть намного лучше, чем изначально. Конечно, можно улучшать /обработку ещё и ещё, потому что условный оператор внутри processError не совсем то, что рекомендуют делать в чистом коде, но я пока остановился на этом варианте.
Классно, что вы делаете такие публикации. Не знаю — сколько просмотров у вас, но, действительно, на английском языке было бы более востребовано.
НравитсяНравится 1 человек
как раз с программированием на английском языке очень много материалов, а на русском маловато, поэтому на русском выгоднее писать) а вот с музыкой наоборот — в мире больше фанатов, до переводов всё никак не доберусь))
НравитсяНравится 1 человек
Действительно, я на русском почти не находила материалов, когда нужно было. Но я уже и не искала на русском — сам поиск уже делала на английском.
НравитсяНравится