HandlerInterceptor в Spring

Иногда нужно чтобы на каждый запрос происходила одна и также определенная операция, как например выдернуть количество записей в базе для размещения на странице, или проверка прав доступа, или еще куча вариантов.
Я видел приложение написанное на Spring, в котором для всех контроллеров был один базовый класс, который и выполнял такую функциональность. Но на самом деле в Spring для этого есть такая вещь как HandlerInterceptor.

Bean, имплементирующий этот интерфейс можно указать в маппинге урлов и он будет вызыватся для каждого запроса. Лучше даже не имплементировать интерфейс HandlerInterceptor, а наследовать класс HandlerInterceptorAdapter, т.к. он имеет заглушки для всех методов, и достаточно переопределить лишь нужный метод. А методы такие:

boolean preHandle (request, response, handler)
Выполняется перед выполнением контроллера. Объект handler и есть контроллер который запустится следующим. Возвращает true в случае если можно продолжать дальше, или false, если вся работа уже сделана(например выдали ошибку о недостатке прав).
postHandle (request, response, handler, modelAndView)
Если нужно чтото сделать после выполнения контроллера, но до выдачи View (т.е. страница для выдачи еще не сгенерировалась). Имеет смысл если контроллер у нас выдал например ошибку или редирект, в таком случае, например, уже и не надо выдергивать количество объектов в базе. Или можно чтото переопределить после контроллера.
afterCompletion (request, response, handler, exception)
Вызывается уже после всего выполнения и выдачи страницы пользователю. Вызовется только в случае если сам контроллер отработал, если наш метод preHandle до этого выдал false, а значит и не вызывался контроллер, то и данный метод не вызовется. Если произошла ошибка, то она будет в атрибуте exception. Метод полезен если после выполнения нужно освободить какието ресурсы.

Для примера расширю пример с калькулятором, добавлю счетчик открытий страниц:

public class Counter extends HandlerInterceptorAdapter {

    private int start = 0;
    private int step = 1;

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        start += step;
        request.setAttribute("count", start);
        return true;
    }

    public void setStart(int start) {
        this.start = start;
    }
    public void setStep(int step) {
        this.step = step;
    }
}

в наш spring.xml добавим следующее:

<bean id="Counter" class="example.simplespring.ctrl.Counter" >
    </bean>

и расширим urlMapping:

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="Counter"/>
            </list>
        </property>
        <property name="mappings">
            <props>
                <prop key="/calc.html">Calc</prop>
            </props>
        </property>
    </bean>

Все. Теперь если мы гдето на странице добавим

Counter: ${count}

то в этом месте будет выводится количество открытий страницы.

В общем суть заключается в том что когда нам постоянно нужно чтото подготавливать для выдачи пользователю, то мы делаем свои HandlerInterceptor'ы для каждого случая и добавляем их в список interceptors маппинга адресов.

  • http:// nroff

    Можешь помочь с проблемой: есть форма

    Цель визита: "/>

    Так вот в объект command записываются вместо русских букв — каракули, как и где надо прописать локализацию?

    Зараннее спасибо!

  • http:// igor

    Не особо понятно что же было в оригинале 🙂

    В общем я так понимаю что вам нужно настроить AS, если это Tomcat то прописать URIEncoding

  • http:// nroff

    Обрезался xml с формой и конфигом. Используется oc4j. В контроллере я могу задать кодировку для request'а и считать параметры в нужной кодировке. Но как быть с command object, который заполняется из формы автоматически? Можно как-нибудь задать кодировку для приложения в web.xml?

  • http:// igor

    C OC4J не знаю, пользовал его когда он был еще Orion AS, но это было давно и не помню.

    Но если у него нет подобной настройки, то может поможет использование CharacterEncodingFilter из Spring.

  • http:// nroff

    А как этот CharacterEncodingFilter прикрутить?

  • http:// igor

    Его как обычный фильтр, в web.xml. И указать кодировку в проперти encoding

  • http:// lucker

    Было бы не плохо еще добавить синхронизацию при изменении переменной start и сохранении ее в атрибут запроса.

  • http:// igor

    Лень синхронизацию писать для такого простого примера 🙂

    А значение сохраняется в атрибут.

  • http:// lucker

    ну только что лень и в демонстративных целях, а вообще, конечно, в реальных приложениях лучше не лениться.

  • http:// igor

    Оно на самом деле все эти i++ я и так всегда забываю синхронизировать, ну очень уж оно атомарным со стороны кажется 🙁

    Хотя чтото и не помню чтобы за последне время где то такое попадалось.

  • http:// lucker

    Кстати, насчет атомарно, есть такой пакет concurrent-atomic

    Очень удобно вот в таких вот ситуациях.

    AtomicInteger start = new AtomicInteger(0); .... request.setAttribute("count", start.incrementAndGet());

  • http:// Denis

    Имхо, достаточно сделать переменную start как volatile, и тогда синхронизация не нужна.

    Денис

  • http:// HitOdesit

    Не совсем понятно, зачем в примере класса Counter нужны сеттеры для start & step...?

    Просто для красоты?

  • http:// igor

    И вправду непонятно 🙂 Видимо хотел что то на их примере показать, но пропустил. Ну в общем в любом случае для красоты.

  • http:// Владислав

    Как быть если есть два и более HandlerInterceptor'a

    и некоторые вызываются для всех страниц,

    некоторые для, например, двух?

  • http://artamonov.ru splix

    Ну собственно так и быть. А в чем именно тут возникла проблема?

    Спринг позволяет повешать сколько угодно хэндлеров, а уж он пусть сам думает что ему делать для конкретного адреса.