Struts2 - перехватчики

Действия в Struts2 являются основным объектом фреймворка, определяющими логику приложения. Но часто требуется решать отдельные задачи, которые должны располагаться вне действий. К таким задачам можно отнести вспомогательный функционал, связанный с авторизацией пользователя, проверкой корректности данных и т.д. Для решения данных задач в Struts2 используются перехватчикии (Interceptors). Феймворк включает большой набор перехватчиков, но при необходимости можно создать и собственные.

На рисунке из книги "Struts2 in Action" представлена схема обработки запроса, поступившего от пользователя. FilterDispatcher уже выполнил свою миссию и передал управление фреймворку для выполнения соответствующего действия. Согласно данной структуры Interceptors включаются в процесс обработки перед выполнением действия и перед выдачей результата пользователю.

При использовании перехватчиков очень важно понимать их предназначение. Interceptors в Struts2 предназначены для перехвата процесса обработки запроса. Не стоит делать перехватчики, чтобы использовать их в 1-2 действиях. В таких случаях проще ограничиться вызовом из методов дополнительных действий. С перехватчиками надо обращаться достаточно осторожно, это очень эффективный инструмент, но поддержка огромного количества перехватчиков через некоторое время может сделать работу "кошмаром".

Interceptors в Struts2 позволяют значительно упростить приложение. Но вместе с плюсами у каждого решения бывают и подводные камни. Многослойная архитектура, получаемая при использовании перехватчиков, способна при неумелом использовании привнести "неявные" зависимости.
Рассмотрим использование перехватчиков ( Interceptors ) на простом примере. Допустим в нашем WEB-приложении имеются две области : открытая и закрытая. В открытой области ( Public ) пользователь должен авторизоваться, т.е. ввести логин и пароль. Если информация введена правильно и пользователь с данным аккаунтом присутствует в нашей Базе Данных, то ему открывается закрытая область ( Private ), включающая несколько страниц.

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

Создание перехватчика AuthenticationInterceptor

Перехватчик создается на основе класса Interceptors и включает три метода : destroy, init и intercept. В методе intercept мы будем проверять пользователя.

Методу intercept в качестве параметра передается класс ActionInvocation - это основной класс в процессе выполнения действия перехватчика. ActionInvocation управляет всей задачей обработки запроса и содержит в себе всю информацию о процессе обработки запроса действием.

Объект ActionInvocation создается фреймворком, когда поступает от пользователя запрос. После этого определяется действие и оно устанавливается как свойство объекта ActionInvocation. Далее фреймворк определяет какой набор перехватчиков должен быть вызван и в каком порядке, после этого они добавляются в ActionInvocation. Так же, ActionInvocation содержит параметры запроса и варианты результатов действия.

После того как все необходимые атрибуты ( действие, перехватчики, параметры запроса) созданы вызывается метод invoke. Ошибочно утверждать что вызовется первый перехватчик из стека. Это обязанность реализации ActionInvocation определить, какой из перехватчиков должен быть вызван и на какой стадии обработки запроса находится сейчас приложение.
Листинг класса-действия AuthenticationInterceptor.java
package examples.interceptors; import java.util.Map; import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.Interceptor; public class AuthenticationInterceptor implements Interceptor { public void destroy ( ) { } public void init ( ) { } public String intercept ( ActionInvocation actionInvocation ) throws Exception { Map session = actionInvocation.getInvocationContext().getSession(); User user = (User) session.get( "user" ); if (user == null) return Action.LOGIN; else { Action action = ( Action ) actionInvocation.getAction ( ); return actionInvocation.invoke ( ); } } }

Представленный перехватчик получает объект сессии ( session ) из параметра actionInvocation и извлекает информацию о пользователе. Предполагается, что при авторизации пользователя мы объект user типа User заносим в сессию. Если в сессии пользователя нет, т.е. ранее не было авторизации данного оператора, то перехватчик возвращает в качестве результата строковое значение "login", в противном случае управление передается фреймворку для дальнейшей обработки.

Таким образом, мы получили элегантный способ для передачи информации о пользователе. Если в процессе разработки модуля авторизации что-то изменится, то необходимо будет внести изменения только в перехватчик.

Настройка перехватчика

Подключение перехватчика AuthenticationInterceptor осуществляется в файле конфигурации struts.xml.
Листинг файла конфигурации struts.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="public" namespace="/Public" extends="struts-default"> <action name="Login" class="examples.interceptors.Common;"> <result name="success">/login.jsp</result> </action> <action name="Authentication" class="examples.interceptors.Authentication"> <result name="success" type="redirect">/Private/MainPage.action</result> <result name="input">/login.jsp</result> </action> </package> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <package name="private" namespace="/Private" extends="struts-default"> <interceptors> <interceptor name="authentication" class="examples.interceptors.AuthenticationInterceptor" /> <interceptor-stack name="privateStack"> <interceptor-ref name="authenticationInterceptor" /> <interceptor-ref name="defaultStack" /> </interceptor-stack> </interceptors> <default-interceptor-ref name="privateStack" /> <global-results> <result name="login" type="redirect">/Public/Login.action</result> </global-results> <action name="MainPage" class="examples.interceptors.Common"> <result name="success">/Wellcome.jsp</result> </action> </package> </struts>

В представленном файле конфигурации два пакета : Public и Private. При авторизации пользователь находится в области "Public" на странице login.jsp. При обращение к действию Login.action выполняется Java-класс examples.interceptors.Common. Данный класс может выполнять функцию локализации интерфейса или вообще отсутствовать. Поскольку оно не играет никакой роли в структуре функционирования перехватчиков, то исходный код не приводится, и мы его не будем рассматривать. Для авторизации пользователя вызывается Action-класс Authentication.action.

Перехватчик располагается в области Private для защиты всех действий ( закрытых страниц WEB-приложения ) от "незванных гостей". В области Private определен раздел <interceptors>, в котором описывается перехватчик, указывается глобальный результат <global-results> под наименованием "login". Именно сюда будут попадать инопришельцы, которых Struts2 перенаправит на страницу авторизации. При успешной авторизации пользователь должен со страницы login.jsp перейти на страницу Wellcome.jsp.

Бизнес-логика описания процесса перехода с одной страницы на другую приведена на сайте Действия, поэтому здесь не приводятся исходные коды JSP-страниц. Желающие могут посетить указанную страницу и самостоятельно создать завершенный модуль авторизации пользователя. Описание действия авторизации пользователя и добавления объекта User в сессию приведен на сайте Использование сессии.

Подводя итоги

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

Interceptors в Struts2 могут применяться в случаях:
  • взаимодействия с действиями, чтобы предоставить им дополнительную информацию;
  • извлечения параметров из запроса, обработки загруженных файлов, настройки Spring компонентов;
  • выполнения различных функций перед выполнением действия, например логирование URL запросов;
  • выполнения различных функций после выполнения действия, например логирование результатов действия;
  • отслеживания и обработки исключений.