Следует ли вызывать .close () для HttpServletResponse.getOutputStream () /. GetWriter ()?

96

В сервлетах Java можно получить доступ к телу ответа через response.getOutputStream()или response.getWriter(). Следует ли позвонить .close()по этому OutputStreamповоду после того, как было написано?

С одной стороны, есть блоховский призыв всегда закрывать OutputStreams. С другой стороны, я не думаю, что в этом случае есть базовый ресурс, который нужно закрыть. Открытие / закрытие сокетов управляется на уровне HTTP, чтобы разрешить такие вещи, как постоянные соединения и тому подобное.

Стивен Хьювиг
источник
Вам не предлагается угадывать, нужно ли закрыть базовый ресурс. Если разработчик так считает или, скорее, знает об этом, он предоставит объект close(), который ничего не делает. Что вы должны сделать , это близко каждый закрываемой ресурс.
Маркиз Лорн
1
Даже если ваш код не открыл его? Я так не думаю ...
Стивен Хьювиг

Ответы:

93

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

Например, если вы закроете поток, он будет недоступен, если вы реализовали фильтр .

Сказав все это, если вы закроете его, ничего плохого не произойдет, если вы не попытаетесь использовать его снова.

РЕДАКТИРОВАТЬ: еще одна ссылка на фильтр

EDIT2: adrian.tarau верен в том, что если вы хотите изменить ответ после того, как сервлет сделал свое дело, вы должны создать оболочку, расширяющую HttpServletResponseWrapper, и буферизовать вывод. Это сделано для того, чтобы вывод не поступал напрямую клиенту, но также позволяет защитить, если сервлет закроет поток, как указано в этом отрывке (выделено мной):

Фильтр, изменяющий ответ, обычно должен захватить ответ, прежде чем он будет возвращен клиенту. Для этого нужно передать сервлету, который генерирует ответ, резервный поток. Резервный поток предотвращает закрытие сервлетом исходного потока ответов после его завершения и позволяет фильтру изменять ответ сервлета.

Статья

Из этой официальной статьи Sun можно сделать вывод, что закрытие OutputStreamсервлета из сервлета - это нормальное явление, но не обязательное.

Неми
источник
2
Это верно. Следует отметить, что в некоторых случаях вам может потребоваться очистить поток, и это вполне допустимо.
toluju
1
Есть еще один побочный эффект закрытия писателя. Вы также не сможете установить код состояния через response.setStatus после его закрытия.
che javara
1
Следуйте этому совету. Это избавит вас от боли. Я бы тоже не стал flush (), если вы не знаете, зачем вы это делаете - вы должны позволить контейнеру обрабатывать буферизацию.
Hal50000 02
76

Общее их правило таково: если вы открыли поток, то вы должны его закрыть. Если нет, то и не надо. Убедитесь, что код симметричен.

В случае с HttpServletResponse, это немного менее однозначно, поскольку не очевидно, является ли вызов getOutputStream()операцией, открывающей поток. Javadoc просто говорит, что это " Returns a ServletOutputStream"; аналогично для getWriter(). В любом случае ясно, что HttpServletResponse«владеет» потоком / писателем, и он (или контейнер) несет ответственность за его повторное закрытие.

Итак, отвечая на ваш вопрос - нет, в этом случае вам не следует закрывать поток. Контейнер должен это делать, и если вы попадете туда раньше, вы рискуете внести в свое приложение небольшие ошибки.

Скаффман
источник
Я согласен с этим ответом, вы также можете проверить ServletResponse.flushBuffer () см .: docs.oracle.com/javaee/1.4/api/javax/servlet/…
cyber-monk
14
«если вы открыли поток, вам следует закрыть его. Если вы этого не сделали, вам не следует» --- хорошо сказано
Srikanth Reddy Lingala
2
Похоже на предложения, размещенные на школьной доске: «Если вы откроете ее, закройте ее. Если вы включите ее, выключите. Если вы разблокируете ее, заприте ее. [...]»
Рикардо
Ив заметил, что я использую поток на ранней стадии своего кода и никогда больше, клиент ожидает выполнения всего сервлета, но когда я вызываю, close()когда я закончил с потоком, клиент немедленно возвращается, а остальная часть сервлета продолжает выполнение. Разве это не делает ответ более относительным? в отличие от окончательного да или нет
BiGGZ
«если вы открыли поток, вам следует закрыть его. Если вы этого не сделали, не следует. Убедитесь, что код симметричен». - А что, если вы затем создадите другой поток, который будет обертывать этот поток? В этом случае трудно оставаться симметричным, поскольку вызов закрытия внешнего потока обычно закрывает вложенный.
steinybot
5

Если есть вероятность, что фильтр может быть вызван для «включенного» ресурса, вам определенно не следует закрывать поток. Это приведет к сбою включаемого ресурса с исключением «поток закрыт».

Пропустить голову
источник
Спасибо за добавление этого комментария. Как ни странно, я все еще немного борюсь с проблемой - только сейчас я заметил, что шаблон сервлета NetBeans действительно включает код для закрытия выходного потока ...
Стивен Хьювиг,
4

Вы должны закрыть поток, код чище, поскольку вы вызываете getOutputStream (), и поток не передается вам в качестве параметра, когда обычно вы просто используете его и не пытаетесь закрыть. В API сервлета не указано, что если выходной поток может быть закрыт или не должен быть закрыт, в этом случае вы можете безопасно закрыть поток, любой контейнер позаботится о закрытии потока, если он не был закрыт сервлетом.

Вот метод close () в Jetty, они закрывают поток, если он не закрыт.

public void close() throws IOException
    {
        if (_closed)
            return;

        if (!isIncluding() && !_generator.isCommitted())
            commitResponse(HttpGenerator.LAST);
        else
            flushResponse();

        super.close();
    }

Также как разработчик фильтра вы не должны предполагать, что OutputStream не закрыт, вы всегда должны передавать другой OutputStream, если вы хотите изменить содержимое после того, как сервлет выполнил свою работу.

EDIT: я всегда закрываю поток, и у меня не было проблем с Tomcat / Jetty. Не думаю, что у вас должны возникнуть проблемы с каким-либо контейнером, старым или новым.

adrian.tarau
источник
4
«Вы должны закрыть поток, код чище ...» - Для меня код, приправленный .close (), выглядит менее чистым, чем код без, особенно если .close () не нужен - вот в чем заключается этот вопрос пытаюсь определить.
Стивен Хьювиг,
1
да, но красота приходит позже :) В любом случае, поскольку API не ясен, я бы предпочел закрыть его, код выглядит согласованным, как только вы запрашиваете OutputStream, вы должны закрыть его, если API не говорит «не закрывайте его».
adrian.tarau
Я не думаю, что закрытие потока вывода в моем собственном коде - это хорошая практика. Это работа контейнера.
Джим Хокинс,
get * не создает поток, это не его производитель. Не стоит его закрывать, это должна делать тара.
dmatej
3

Еще один аргумент против закрытия OutputStream. Посмотри на этот сервлет. Выдает исключение. Исключение отображается в файле web.xml на ошибку JSP:

package ser;

import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet(name = "Erroneous", urlPatterns = {"/Erroneous"})
public class Erroneous extends HttpServlet {

  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setContentType("text/html;charset=UTF-8");
    PrintWriter out = resp.getWriter();
    try {
      throw new IOException("An error");
    } finally {
//      out.close();
    }
  }
}

Файл web.xml содержит:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <error-page>
        <exception-type>java.io.IOException</exception-type>
        <location>/error.jsp</location>
    </error-page>
</web-app>

И error.jsp:

<%@page contentType="text/html" pageEncoding="UTF-8" isErrorPage="true"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Error Page</title>
    </head>
    <body>
        <h1><%= exception.getMessage()%></h1>
    </body>
</html>

При загрузке /Erroneousв браузере вы видите страницу с сообщением об ошибке «Ошибка». Но если вы не закомментируете out.close()строку в приведенном выше сервлете, повторно развернете приложение и перезагрузите, /Erroneousвы ничего не увидите в браузере. Я понятия не имею, что происходит на самом деле, но полагаю, чтоout.close() предотвращает обработку ошибок.

Протестировано с Tomcat 7.0.50, Java EE 6 с использованием Netbeans 7.4.

user1872904
источник