barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)
Показаны сообщения с ярлыком design patterns. Показать все сообщения
Показаны сообщения с ярлыком design patterns. Показать все сообщения

четверг, 19 июля 2012 г.

Повторный вызов HttpServletResponse.sendRedirect() в веб-приложении на Tomcat 6

Задача:

Попытка повторного вызова HttpServletReponse.sendRedirect() в веб-приложении, развернутом на Tomcat 6, приводит к исключению:
java.lang.IllegalStateException: org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:435)
Необходимо реализовать возможность вызывать  sendRedirect() несколько раз, так, чтобы реальный редирект осуществлялся по URL`у, указанному в последнем вызове.

Решение:

Catalina не позволяет вызывать sendRedirect() повторно. Всё же, порой это бывает необходимо сделать. Например, пусть есть некоторый фильтр, который, после того, как запрос пройдет обработку во всех нижлежащих фильтрах, по какому-либо условию выполняет response.sendRedirect():
chain.doFilter(request, response);
if(...)
     ((HttpServletResponse)response).sendRedirect(...)
Однако, если в каком-либо из нижлежащих фильтров или в конечном сервлете, обрабатывающем запрос, также вызывается response.sendRedirect(), повторный его вызов в нашем фильтре вызовет вышеуказанное исключение, в то врем как редирект в фильтре всё же  делать надо, причем так, чтобы он переопределял URL, указанный в предыдущих вызовах sendRedirect().
Вариант, который пришел мне на ум и был реализован - перед передачей дальше по цепочке завернуть передаваемый фильтру response в некоторый объект-делегатор, который будет делегировать все вызовы оригинальному объекту ответа, кроме вызовов sendRedirect. Последний будет просто запоминать переданный ему URL, а реальный редирект будет выполняться только по вызову нового метода applyRedirect:
public class DeferredRedirectHttpServletResponse implements HttpServletResponse
{
  protected HttpServletResponse responseDelegate;

  protected String deferredRedirectURL = null;

  public DeferredRedirectHttpServletResponse(HttpServletResponse responseDelegate)
  {
    this.responseDelegate = responseDelegate;
  }
  @Override
  public void sendRedirect(String string) throws IOException {
    deferredRedirectURL = string;
  }
  public void applyRedirect() throws IOException
  {
    if(this.deferredRedirectURL != null)
      responseDelegate.sendRedirect(this.deferredRedirectURL);  
  }
...
/* здесь идут все методы из интерфейса HttpServletResponse, вызов которых делегируется объекту responseDelegate*/
...
}
Теперь код фильтра примет вид:
DeferredRedirectHttpServletResponse responseDelegator = new DeferredRedirectHttpServletResponse((HttpServletResponse)response);
chain.doFilter(request, responseDelegator);
if(...)
     responseDelegator.sendRedirect(...);
responseDelegator.applyRedirect();
Всё, теперь приложение работает корректно при всех условиях: и когда фильтр делает редирект (даже если он уже был сделан в сервлете / нижлежащем фильтре), и когда он его не делает.

UPDATE. Пожалуй, удобнее было бы наследовать HttpServletResponseWrapper, чем реализовывать HttpServletResponse, кода было бы меньше. Но на момент реализации моего DeferredRedirectHttpServletResponse я про HttpServletResponseWrapper не знал. Наткнулся на него, лазя по исходникам Spring Security, в частности, глядя на класс org.springframework.security.web.firewall.FirewalledResponse.
UPDATE 2. Кстати, если конечный обработчик запроса - jsp-страница, то тут есть одна особенность. Если без использования DeferredRedirectHttpServletResponse вызвать на jsp-хе response.sendRedirect() без следующего за ним return`а прокатывает (ответ просто игнорирует последующую запись в него jsp-страницей), то теперь валится тот же IllegalStateException, на этот раз уже из-за того, что отправка ответа пользователю на момент вызова applyRedirect() уже началась (с jsp-хи вовремя не return`ались, поэтому она уже успела записать в response свой вывод). В общем-то, вышесказанное относится не только к jsp-хам, а к любому коду, где в response идет запись после вызова sendRedirect() (просто на jsp эта самая запись не очевидна).

четверг, 18 августа 2011 г.

Шаблон проектирования Приспособленец (Flyweight)

Вместо:
- создание нескольких копий идентичных неизменяемых объектов
предлагается:
- использование модифицированной версии шаблона Фабрика (Factory), в которой фабрика при запросе объекта с внутренней структурой, идентичной ранее созданному объекту, возвращает ссылку на этот объект вместо создания нового объекта. В противном случае создается новый экзмепляр объекта.
Шаблон предполагает использование 2 классов - самого приспособленца (объекта, свойства которого задаются единожды при создании и не могут быть изменены в дальнейшем), и фабрики приспособленцев.

пятница, 24 июня 2011 г.

Шаблон проектирования ObjectMother для написания (модульных) тестов

Вместо:
- тестируемые, а также все остальные необходимые для теста объекты, создаются и инициализируются в коде самого теста (если нет возможности объединить общую фикстуру, инициализируемую в setUp-методе)
- тестируемые, а также все остальные необходимые для теста объекты, создаются и инициализируются в отдельных методах тест-кейса, обращение к которым производится из различных тестов, которым необхоима аналогичная инициализация

Предлагается:
- использование одного или нескольких ObjectMother - классов, которые содержат методы для создания и(или) модификации любых часто используемых в тестовых методах объектов в различных комбинациях. Методы этого класса должны содержать максимально говорящие об их назначении имена, чтобы понятность тестов не снижалась. Объекты, создаваемые ObjectMother, должны иметь возможность легко менять свое состояние, так чтобы после их инициализации в ObjectMother, тесты могли легко поменять его (состояние) на требуемое. Чаще всего ObjectMother создает обычные доменные объекты вместе с требуемыми атрибутами, намного реже - заглушки и мок-объекты, так как последние более специфичны для конкретных тестов и менее - для всего набора модульных тестов приложения.

"+": повышается читабельность кода тестов, уменьшается дублирование инициализирующего кода, упрощается создание новых тестов

Более подробно и с примерами есть тут: http://wiki.agiledev.ru/doku.php?id=tdd:object_mother

четверг, 23 июня 2011 г.

Шаблон проектирования Inversion of Control (IOC)

Вместо:
- класс сам создает экземпляры сторонних классов (ресурсов), от которых он зависит
предлагается:
- класс получает объекты этих классов (ресурсов) одним из следующих способов: как параметр конструктора / setter-метод / параметр того метода, где эти объекты непосредственно нужны

"+": большая гибкость при использовании класса (особенно когда тип передаваемых объектов задается не конкретным классом, а интерфейсом)
"+": упрощается unit-тестирование. Можно контролировать состояние передаваемых классу объектов непосредственно в коде самого текста, а также заменять их мок-объектами