Занятие 4. Перехватчики
Interceptor
В этой части мы завершаем рассмотрение примера с авторизацией пользователя. Нам нужно учесть, что когда пользователь авторизовался, то система должна запомнить этого пользователя. Дальнейшие действия (экшны) будут обрабатываться с учетом того, что пользователь уже авторизован.
Если же данные пользователя не найдены, только тогда предложить ему ввести их (перенаправить действие на login.action).
Замечание: Для простоты мы будем хранить пользователя (объект) в http-сессии.
Одним из решений такой проблемы является проверка внутри каждого экшна (хранится ли в сессии такой-то пользователь), но это неэкономно. Гораздо удобнее осуществлять подобную проверку до вызова экшна. Подобный подход в Struts 2 реализуется с помощью механизма перехватчиков, или интерцепторов (interceptors).
Как видно из простой схемы (см. иллюстрацию выше), перехватчики могут выполняться до и/или после выполнения экшнов. В результате своей работы перехватчик может вызвать действие, или предотвратить его выполнение, или перенаправить дальнейшую обработку третьему действию. Все перехватчики, так же как и экшны, складываются в стек и выполняются в указанном порядке, поэтому подряд могут выполняться сразу несколько интерцепторов.
Хранение пользователя в сессии
Это отступление необходимо, т.к. позволит сократить дальнейшие объяснения по реализации собственного интрецептора, но останавливаться на них подробно не будем. Все изменения несложные и очевидные.
Сначала создаем класс User:
public class User implements Serializable {
private String nick;
private String password;
public User(String nick, String password){
this.nick = nick;
this.password = password;
}
public String getNick() {
return nick;
}
public void setNick(String nick) {
this.nick = nick;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Создаем класс <code>AuthController</code>, отвечающий за помещение объекта User в сессию с некоторым ключом и извлечение из сессии по тому же ключу:
public class AuthController {
private static final String SESSION_KEY = "authUser";
public static User getUser() {
return (User)ActionContext.getContext().
getSession().get(SESSION_KEY);
}
public static void setUser(User user) {
ActionContext.getContext().getSession().
put(SESSION_KEY, user);
}
}
Исправляем LoginAction:
//добавляем поле user типа User
private User user;
....
//исправляем валидацию с учетом появления нового объекта
public void validate() {
if (!this.getFieldErrors().isEmpty()) return;
this.user = new User(this.getNick(),this.getPassword());
if (!"user".equalsIgnoreCase(this.user.getNick()) ||
!"user".equalsIgnoreCase(this.user.getPassword()))
this.addFieldError("invalidLoginPassw",
"Invalid login/password");
}
...
//кладем user в сессию, если валидация пройдена
public String loginSubmit() throws Exception {
AuthController.setUser(this.user);
return SUCCESS;
}
ActionInvocation
Когда фреймворк получает запрос от клиента, он в первую очередь решает, какой экшн нужно выполнить. Но вместо того, чтобы выполнять действие напрямую, создается объект ActionInvocation, который инкапсулирует экшн и интерцепторы, выполняемые до и/или после выполнения действия. Вообще ActionInvocation инкапсулирует все детали обработки текущего действия.
Итак, в начале обработки создается экземпляр класса соответствующего экшна. Этот экземпляр помещается в новый экземпляр ActionInvocation. Информация об экшне, как мы помним, берется из struts.xml. Из того же конфигурационного файла получаем информацию об интерцепторах, которые связаны с этим действием, и об их порядке (что именно добавляется в struts.xml, покажем ниже). Ссылки на перехватчики в нужном порядке так же добавляются к ActionInvocation. ActionInvocation отвечает за последовательный вызов и выполнение перехватчиков из стека, вызывая у каждого из них метод intercept(). Заметим, что этот метод содержит ActionInvocation в качестве параметра. После выполнения всех перехватчиков из стека вызывается выполнение экшна (фреймворк вызывает метод invoke()). В общем случае ActionInvocation проходит весь стек перехватчиков и передает дальнейшую обработку экшну, пришедшему от клиента. Выполнение действия происходит так же, как мы рассматривали в первом уроке.
Создание собственного перехватчика
Вначале создаем абстрактный класс ActionBase, который содержит всего одно поле checkAuth:
public abstract class ActionBase extends ActionSupport {
private boolean checkAuth;
public boolean isCheckAuth() {
return checkAuth;
}
public void setCheckAuth(boolean checkAuth) {
this.checkAuth = checkAuth;
}
}
Это поле checkAuth отвечает за то, нужно ли для данного экшна проверять наличие пользователя в сессии.
Все классы, обрабатывающие экшны в примере, должны наследовать этот абстрактный класс.
Теперь переходим к самому перехватчику.
Наш класс должен наследовать интерфейс Interceptor, от которого нужно реализовать метод intercept():
public class AuthenticationInterceptor implements Interceptor {
public String intercept(ActionInvocation actionInvocation)
throws Exception {
Object action = actionInvocation.getAction();
if (action != null && (action instanceof ActionBase) &&
(ActionBase)action).isCheckAuth()) {
if (AuthController.getUser() == null)
return "redirectToLogin";
}
return actionInvocation.invoke();
}
...
}
Метод intercept() принимает ActionInvocation в качестве параметра, из которого получаем Action. Перехватчик проверяет, свойство экшна checkAuth и наличие пользователя в сессии. Если для полученного экшна не нужно выполнять проверку или пользователь найден в сессии, то интерцептор передает управление первоначальному экшну. Иначе интерцептор возвращает строку «redirectToLogin».
На первом занятии мы выяснили, что для каждого экшна в struts.xml определен результат, например:
<result type="dispatcher" name="success">/WEB-INF/jsp/login.jsp</result>
Какой именно результат (а их может быть несколько) будет выполняться, указывается параметром name. Интерцептор в любом случае тоже должен вернуть какой-либо результат. В случае, если пользователь не найден в сессии, возвращается строка. По этому значению фреймворк будет искать соответствующий результат в struts.xml. Однако результат, который мы пытаемся получить, не относится к какому-либо экшну, а определен независимо от них и называется global result. Определяется global result так:
<global-results>
<result type="redirect" name="redirectToLogin">login.action</result>
</global-results>
Как видим, в результате произойдет редирект на login.action.
Поскольку обработка экшнов начинается с обращения к struts.xml, то наш интерцептор также должен быть указан в этом файле:
<interceptors<
interceptor name="checkAuthInterceptor"
class="mymovieteach.interceptor.AuthenticationInterceptor"/>
<interceptor-stack name="defStack" >
<interceptor-ref name="defaultStack" />
<interceptor-ref name="checkAuthInterceptor" />
</interceptor-stack>
</interceptors>
Все, что касается перехватчиков, определяется внутри тега <interceptors>. Тегом <interceptor> мы определяем свой собственный интерцептор. Атрибут name, очевидно, отвечает за имя интерцептора, а атрибут class — за полное имя класса, его обрабатывающего.
С помощью тега <inteceptor-stack> мы задаем стек перехватчиков, перечисляя их по имени. Первым в стеке указан defaultStack, он предоставляется самим фреймворком и отвечает, например, за доставку параметров в экшн и загрузку файлов. Следом указан определенный выше перехватчик checkAuth.
Остался последний шаг. Укажем в struts.xml значение параметра checkAuth=true для home.action:
<action name="home" class="mymovieteach.action.HomeAction"
method="show">
<param name="checkAuth">true</param>
<result type="tiles">home</result>
</action>
Таким образом, интерцептор будет выполнять свою проверку только для этого экшна. Для login.action проверка не имеет смысла, поэтому интерцептор сразу передаст управление самому экшну.
Приложение 1:
полный листтинг конфигурационнго файла struts.xml
<struts>
<package name="common" extends="tiles-default">
<interceptors>
<interceptor name="checkAuthInterceptor"
class="mymovieteach.interceptor.AuthenticationInterceptor"/>
<interceptor-stack name="defStack">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="checkAuthInterceptor" />
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="defStack"/>
<global-results>
<result type="redirect" name="redirectToLogin">login.action</result>
</global-results>
<action name="login" class="mymovieteach.action.LoginAction" method="input">
<result type="tiles">login</result>
</action>
<action name="loginSubmit" class="mymovieteach.action.LoginAction"
method="loginSubmit">
<result type="tiles" name="input">login</result>
<result type="redirect">home.action</result>
</action>
<action name="home" class="mymovieteach.action.HomeAction" method="show">
<param name="checkAuth">true</param>
<result type="tiles">home</result>
</action>
</package>
</struts>
Приложение 2:
полный листинг класса AuthenticationInterceptor
package mymovieteach.interceptor;
import mymovieteach.action.ActionBase;
import mymovieteach.auth.AuthController;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
public class AuthenticationInterceptor implements Interceptor {
public String intercept(ActionInvocation actionInvocation) throws Exception {
Object action = actionInvocation.getAction();
if (action != null && (action instanceof ActionBase) &&
((ActionBase)action).isCheckAuth()) {
if (AuthController.getUser() == null)
return "redirectToLogin";
}
return actionInvocation.invoke();
}
public void init() {
}
public void destroy() {
}
}
Упражнение 1
Предлагаем вам на основе всех четырех уроков самостоятельно добавить в приложение регистрацию пользователей. Для определенности считаем, что на форме регистрации должны быть следующие поля: ник, пароль, подтверждение пароля, email и чекбокс «I agree with terms and conditions».
Исходный код примера вы найдете в архиве (см. ниже). Здесь реализованы не только авторизация пользователя, но и регистрация. Вы можете подсмотреть код при выполнении упражнения, если возникнут какие-либо проблемы или трудности.


Copyright © 19992010 LAR