ENGLISH | | | БЛОГ

Занятие 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».

Исходный код примера вы найдете в архиве (см. ниже). Здесь реализованы не только авторизация пользователя, но и регистрация. Вы можете подсмотреть код при выполнении упражнения, если возникнут какие-либо проблемы или трудности.

Исходный код к уроку (и упражнению)

VN:F [1.0.9_379]
Рейтинг: 5.0/5 (голосов: 5)

Метки: ,

Оставить комментарий

CAPTCHA Image Audio Version
Reload Image

 

E-mail :: Телефон: (8452) 22-89-40
Copyright © 1999–2010 LAR