Struts2拦截器的原理与实现(二)

拦截器与过滤器的区别

    拦截器是对调用的Action起作用,它提供了一种机制可以使开发者定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式,很多业务逻辑都是靠拦截实现的,比如校验,验证登录权限(比如下载时跳转到登陆页面)等等。
     过滤器是对整个的请求过程起作用!换句话说就是拦截器没有过滤器的范围广。过滤器是在java web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的 action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者 struts的action前统一设置字符集,或者去除掉一些非法字符(聊天室经常用到的,一些骂人的话,比如判断用户提交的数据是否存在非法字符等等。

      Struts2拦截器是Struts2中的一个很重要的功能,本质是代理模式。本文将从概念开始,为大家讲解Struts2拦截器的实现原理以及如何定义等等内容。

一、理解Struts2拦截器

1. Struts2拦截器是在访问某个Action或Action的某个方法,字段之前或之后实施拦截,并且Struts2拦截器是可插拔的,拦截器是AOP的一种实现

2. 拦截器栈(Interceptor Stack)。Struts2拦截器栈就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,Struts2拦截器链中的拦截器就会按其之前定义的顺序被调用。

二、执行责任

这个执行职责有3种选择:

1.中止整个执行,直接返回一个字符串作为resultCode(则不会调用该Action)

2. 调用堆栈中下一个Interceptor的执行 

3. 如果在堆栈内已经不存在任何的Interceptor,调用Action

三、实现Struts2拦截器原理

 Struts2拦截器的实现原理相对简单,当请求struts2的action时,Struts2会查找配置文件,并根据其配置实例化相对应的拦截器对象,然后串成一个列表,最后一个一个地调用列表中的拦截器。

四、定义Struts2拦截器

 Struts2规定用户自定义拦截器必须实现com.opensymphony.xwork2.interceptor.Interceptor接口。

例如:(该代码与本案例无关)

package cn.tedu.web;

import org.springframework.stereotype.Component;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
//拦截器必须实现接口Interceptor
@Component
public class DemoInterceptor implements Interceptor{

	@Override
	public void destroy() {}

	@Override
	public void init() {}

	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		System.out.println("开始");
		String val = "success";
		val = invocation.invoke();//调用后续控制器
		System.out.println("结束");
		return val;
	}

}

该接口声明了3个方法

public interface Interceptor extends Serializable {

    /**
     * Called to let an interceptor clean up any resources it has allocated.
     */
    void destroy();

    /**
     * Called after an interceptor is created, but before any requests are processed using
     * {@link #intercept(com.opensymphony.xwork2.ActionInvocation) intercept} , giving
     * the Interceptor a chance to initialize any needed resources.
     */
    void init();

    /**
     * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the
     * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code.
     *
     * @param invocation the action invocation
     * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself.
     * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}.
     */
    String intercept(ActionInvocation invocation) throws Exception;

}

不过,struts中又提供了几个抽象类来简化这一步骤。其中,init()和destroy()方法会在程序开始和结束时各执行一遍,不管使用了该拦截器与否,只要在struts.xml中声明了该Struts2拦截器就会被执行。intercept方法就是拦截的主体了,每次拦截器生效时都会执行其中的逻辑。

Struts2拦截器需要在struts.xml中声明,如下struts.xml配置文件,配置Struts2拦截器

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
    <!-- Struts2的action由Spring来负责进行实例化 -->
    <constant name="struts.objectFactory" value="spring" />
    <package name="default" extends="struts-default">
        <interceptors>
            <interceptor name="MyInterceptor" class="interceptor.MyInterceptor">            
        </interceptor>
            <interceptor-stack name="myInterceptorStack">
                <interceptor-ref name="MyInterceptor" />
                <interceptor-ref name="defaultStack" />
            </interceptor-stack>
        </interceptors>
        <action name="loginAction" class="loginAction">
            <result name="fail">/index.jsp </result>
            <result name="success">/success.jsp</result>
            <interceptor-ref name="myInterceptorStack"></interceptor-ref>
        </action>
    </package>
</struts>

-----------------------------------------------------------------------------------------------------------------------

以下为拦截器使用时候的具体案例:

项目结构如下:

导入jar依赖包:

spring配置文件:spring-web.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context" 
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
	xmlns:jee="http://www.springframework.org/schema/jee" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
		http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
	
	<!-- spring的组件扫描 -->
	<context:component-scan base-package="cn.tedu"/>
	
</beans>

web.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <display-name>struts_day04</display-name>
  
  <filter>
    <display-name>StrutsPrepareAndExecuteFilter</display-name>
    <filter-name>StrutsPrepareAndExecuteFilter</filter-name>
    <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>StrutsPrepareAndExecuteFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  
  
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <context-param>
  	<param-name>contextConfigLocation</param-name>
  	<param-value>classpath:conf/spring-*.xml</param-value>
  </context-param>
</web-app>

struts.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
    "http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
	<!-- 拦截器案例 -->
	<package namespace="/" name="wq" extends="json-default">
		<!-- 拦截器 -->
		<interceptors>
			<!-- 配置自己的拦截器 -->
			<interceptor name="myTime" class="timeConsumingInterceptor"></interceptor><!-- class为bean的ID -->
			<interceptor name="myLogin" class="checkLoginInterceptor"></interceptor><!-- class为bean的ID -->
			<interceptor name="myOther" class="otherInterceptor"></interceptor><!-- class为bean的ID -->
			
			<!-- 配置拦截器栈 -->
			<interceptor-stack name="myStack">
				<!-- 默认自带的拦截器,当配置自己的拦截器时不再走默认的拦截器,所以需要调用自带的拦截器,并写在第一行 -->
                <interceptor-ref name="defaultStack"/>
                <!-- 加入自己的拦截器 -->
                <interceptor-ref name="myTime"/>
                <interceptor-ref name="myLogin"/>
			</interceptor-stack>
		</interceptors>
	
		<!-- 定义默认的拦截器 每个Action都会自动引用,如果Action中引用了其它的拦截器 默认的拦截器将无效 -->
        <default-interceptor-ref name="myStack"/>
		
		<!-- 利用通配符访问action *对应action类的一个方法名-->
		<action name="*Action" class="usersAction" method="{1}">
			<!-- 
				定义局部的拦截器:
	                              当定义局部的拦截器,外面全局(默认)的拦截器则不会走,只会走局部的拦截器,
	                              所以,我们在定义局部拦截器的同时,也要引用Struts2自带的默认拦截器defaultStack。 
	                              不引用defaultStack至少会遭成取不到form表单提交的值。
                <interceptor-ref name="defaultStack"/>
                <interceptor-ref name="myOther"/>
            -->
		
		 	<!-- name属性不写默认success -->
		 	<result>/home.jsp</result>
            <result name="login">/file.jsp</result>
            <result name="input">/login.jsp</result>
            
            <!-- 允许的方法 -->
            <allowed-methods>login</allowed-methods>
		</action>
	</package>
	
</struts>

login.jsp登录页面:

<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>登录页面</title>
</head>
<body>
	<form action="loginAction" method="post">
		<div>
			<label>用户名:</label>
			<input type="text" name="user.name">
		</div>
		<div>
			<label>密码:</label>
			<input type="password" name="user.password">
		</div>
		<div>
			<input type="submit" value="登录按钮">
		</div>
	</form>
</body>
</html>

home.jsp登录成功页面

<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>登录页面</title>
</head>
<body>
        登录成功进入首页。。
</body>
</html>

file.jsp登录失败页面

<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>登录页面</title>
</head>
<body>
        登陆失败页面。。
</body>
</html>

用户实体类Users:

package cn.tedu.entity;

import java.io.Serializable;

public class Users implements Serializable{
	/** 属性 */
	private String name;
	private String password;
	
	/** 构造方法 */
	public Users() {
		super();
	}

	public Users(String name, String password) {
		super();
		this.name = name;
		this.password = password;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		return "Users [name=" + name + ", password=" + password + "]";
	}
	
}

拦截器三个如下:

CheckLoginInterceptor.java
package cn.tedu.interceptor;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.ServletActionContext;
import org.springframework.stereotype.Component;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

import cn.tedu.entity.Users;
/**
 * 拦截器类
 * 作用:检查用户是否登陆,没有登录则不能向Action发送请求。
 * 
 * 测试请url访问:loginUsers.action
 * @author wq
 *
 */
@Component
public class CheckLoginInterceptor implements Interceptor{

	@Override
	public void destroy() {
		
	}

	@Override
	public void init() {
		
	}

	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		System.out.println("执行顺序:3进入CheckLoginInterceptor");
        //得到request对象
        HttpServletRequest request = ServletActionContext.getRequest();
        //取得登陆页面用户输入的账号密码若不为空的话让其通过
        String name = request.getParameter("user.name");
        String password = request.getParameter("user.password");
        //取session中保存的用户登录信息
        Users users = (Users) invocation.getInvocationContext().getSession().get("users");
        if(name != null && password != null){
            //若是登陆页面请求Action,则通过
            return invocation.invoke();
        }else if(users == null){
            return Action.INPUT;//input常量  登录页面
        }
        //若已经登陆,则让其通过访问下一个拦截器,或Action。
        return invocation.invoke();
	}

}

TimeConsumingInterceptor.java

package cn.tedu.interceptor;

import org.springframework.stereotype.Component;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
/**
 * 拦截器类
 * 作用:计算用户从开始登录到结束登录消耗的毫秒数
 * @author wq
 */
@Component
public class TimeConsumingInterceptor implements Interceptor{

	@Override
	public void destroy() {
		
	}

	@Override
	public void init() {
		
	}

	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
        System.out.println("执行顺序:1进入TimeConsumingInterceptor");
        
        //开始时间
        long startTime = System.currentTimeMillis();
        System.out.println("执行顺序:2输出开始时间startTime:"+startTime);
        //调用下一个拦截器,如果拦截器不存在,则执行Action
        String result = invocation.invoke();
        System.out.println("执行顺序:5输出Action返回的结果:"+result);
        //结束时间 
        long endTime = System.currentTimeMillis();
        System.out.println("执行顺序:6输出结束时间endTimen:"+endTime);
        //登陆使用时间
        System.out.println("登陆使用时间:"+(endTime-startTime)+"毫秒。。。");
		return result;
	}

}

OtherInterceptor.java

package cn.tedu.interceptor;

import org.springframework.stereotype.Component;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
/**
 * 拦截器
 * 作用:测试定义局部拦截器执行顺序。
 * @author wq
 *
 */
@Component
public class OtherInterceptor implements Interceptor{

	@Override
	public void destroy() {
		
	}

	@Override
	public void init() {
		
	}

	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		System.out.println("局部拦截器");
	    return invocation.invoke();
	}

}

控制器UsersAction.java

package cn.tedu.web;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

import cn.tedu.entity.Users;

/**
 * 控制器类
 * 作用:处理用户的请求
 * @author wq
 *
 */
@Controller
@Scope("prototype")
public class UsersAction extends BaseAction{
	/** 属性 */
	private Users user;
	
	public Users getUser() {
		return user;
	}

	public void setUser(Users user) {
		this.user = user;
	}
	
	/** 登陆验证的方法 */
	public String login(){
		System.out.println("执行顺序:4进入login()");
		if(user != null){
            if(user.getName().equals("admin") && user.getPassword().equals("admin")){
                return SUCCESS;
            }
        }
		return LOGIN;
	}
	
}

全局拦截器:控制台输出登录成功的拦截器执行顺序。

用户名和密码均为admin

点击‘’登录按钮‘’后页面显示:

登录成功进入首页。。

控制台输出如下:

执行顺序:1进入TimeConsumingInterceptor
执行顺序:2输出开始时间startTime:1567242369265
执行顺序:3进入CheckLoginInterceptor
执行顺序:4进入login()
执行顺序:5输出Action返回的结果:success
执行顺序:6输出结束时间endTimen:1567242369287
登陆使用时间:22毫秒。。。

当用户名和密码错误的时候,页面显示:

登陆失败页面。。

控制台输出如下:

执行顺序:1进入TimeConsumingInterceptor
执行顺序:2输出开始时间startTime:1567242431836
执行顺序:3进入CheckLoginInterceptor
执行顺序:4进入login()
执行顺序:5输出Action返回的结果:login
执行顺序:6输出结束时间endTimen:1567242432658
登陆使用时间:822毫秒。。。

注意:struts.xml文件中采用了通配符来匹配请求路径,关于通配符匹配见博客

思路:

1.通过访问url:http://localhost:8000/struts_day04/login.jsp进入到登录页面,即login.jsp

2.输入用户名和密码后,进入action,由于struts.xml配置文件中配置了拦截器,按照配置顺序依次执行相应拦截器,myTime、myLogin

3.首先进入拦截器TimeConsumingInterceptor,控制台输出1、2步骤,在调用代码String result = invocation.invoke()之前,会调用下一个拦截器CheckLoginInterceptor,此时输出3步骤,在该拦截器中得到前台传来的两个参数,此时用户名和密码都不为null,进入Action处理,控制台输出步骤4,判断密码是否符合,若符合,则跳转成功页面,若不符合,则跳转失败页面,Action调用完成后再次回到TimeConsumingInterceptor,输出步骤5、6,计算Action的耗时

注意:若同时配置了全局拦截器和局部拦截器时,则不会再走全局拦截器。直接进入局部拦截器

<action name="*Action" class="usersAction" method="{1}">
			<!-- 
				定义局部的拦截器:
	                              当定义局部的拦截器,外面全局(默认)的拦截器则不会走,只会走局部的拦截器,
	                              所以,我们在定义局部拦截器的同时,也要引用Struts2自带的默认拦截器defaultStack。 
	                              不引用defaultStack至少会遭成取不到form表单提交的值。
                <interceptor-ref name="defaultStack"/>
                <interceptor-ref name="myOther"/>
            -->
			<interceptor-ref name="myOther"/>
		 	<!-- name属性不写默认success -->
		 	<result>/home.jsp</result>
            <result name="login">/file.jsp</result>
            <result name="input">/login.jsp</result>

进入拦截器OtherInterceptor,输出步骤四,进入Action,然后跳转到失败页面

控制台输出:

局部拦截器
执行顺序:4进入login()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

荒--

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值