JAVAEE就业工程师教程之第4章 Struts2拦截器

第4章 Struts2拦截器

本章学习目标

· 理解拦截器的意义

· 熟练掌握Struts2拦截器的使用

· 掌握自定义拦截器的使用

拦截器是Struts2框架的一个核心组件,也是Struts2的一大特色,很多功能都是构建在内置拦截器的,如数据校验、国际化等。Struts2利用其内建的拦截器可以完成大部分操作,当内置拦截器不能满足时,开发者也可以自己扩展自定义拦截器。

使用Struts2拦截器只需在配置文件配置即可,取消拦截器只需取消其配置,这是一种插拔式的设计,具有非常好的可扩展性,掌握并熟练运用拦截器,会大大提高开发效率,本章将详细讲解拦截器的相关知识。

4.1 拦截器的概述和意义

在JavaWeb阶段学过Filter过滤器,多个Filter构成过滤器链。Filter与拦截器很像,二者都是AOP编程思想的体现,都能实现权限检查、日志记录等,但他们也稍有不同,Filter只能用于Web程序中,是Servlet规范中定义的,另外它们处理的粒度也是不同的,Filter只在Servlet前后起作用,而拦截器能深入方法前后、异常抛出前后等,因此拦截器有更强大的功能,本节将介绍拦截器的概念和意义。

4.1.1 拦截器概述

拦截器是在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作,拦截器就是AOP的一种实现策略。

Webwork的中文文档的解释为拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。

谈到拦截器,还有一个词读者应该知道——拦截器链(Interceptor Chain,在Struts 2中称为拦截器栈Interceptor Stack)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。

拦截器的调用是分开的,各个拦截器负责自己的功能,不去管其他的拦截器,这种模块的设计更好地体现了MVC的组件化设计思想。另外,拦截器与Action存在着紧密的关系,如图4.1所示。


图4.1 拦截器与Action关系图

如上图所示,用户操作浏览器向Web应用发送HttpServletRequest请求,请求经过各种过滤器的过滤并传递给核心控制器,核心控制器会调用Action映射器ActionMapper,将用户情趣转发到对应的业务逻辑控制器,此业务逻辑控制器并不是用户实现的业务控制器,而是Struts2创建的Action代理ActionProxy,用户实现的Action类仅仅是Struts2的ActionProxy的代理目标,ActionProxy通过Configuration Manager在struts.xml的配置文件中搜索被请求的Action类,ActionProxy创建一个被请求Action的实例,用来处理客户端的请求信息,如果在struts.xml配置文件存在与被请求Action相关的拦截器配置,那么该Action实例被调用的前后,这些拦截器将会被执行,Action对请求处理完毕后会返回一个逻辑视图,由此逻辑视图找对相应物理视图返回给客户端。

4.1.2 拦截器的意义

拦截器是对调用方法的改进,实际上,拦截器也是一个类,类中包含方法,只不过这个方法是特殊的方法,它会在目标方法调用之前自动执行。

如果不使用拦截器,代码中需要显式通过代码来调动目标方法,这样会造成多个程序间的冗余,这种做法明显是不合理的,违背了软件开发的基本原则,不便于后期维护和扩展。

由上可见,拦截器是解决重复性问题的重要方式,它提供了更高层次的解耦和抽象,目标代码无须手动调用目标方法,由程序自动完成,这种调用从代码层面上升到了设计层面。

4.2 Struts2拦截器

学习了拦截器的概念和意义后,接下来详细讲解如何配置和使用拦截器。

4.2.1 配置并使用Struts2拦截器

在struts-default.xml文件中定义了很多拦截器,如果要使用其中的拦截器,只需要在struts.xml文件中通过"<include file="struts-default.xml" />"将struts-default.xml文件包含进来,并继承其中的struts-default包(package),最后在定义Action时,使用"<interceptor-ref name="xxx" />"引用拦截器或拦截器栈(interceptor stack)。接下来通过一个案例演示如何配置并使用拦截器,新建动态Web工程,在web.xml中配置Struts2核心控制器,这里就不再重复演示,然后编写后台代码,如例4-1所示。

1 TimerInterceptorAction.java

2 import com.opensymphony.xwork2.ActionSupport;

3 public class TimerInterceptorAction extends ActionSupport {

4 private static final long serialVersionUID = 1L;

5 @Override

6 public String execute() {

7 try {

8 // 模拟耗时的操作

9 Thread.sleep(500);

10 } catch (Exception e) {

11 e.printStackTrace();

12 }

13 return SUCCESS;

14 }

15 }

接着编写struts.xml配置文件,如例4-2所示。

例4-1 struts.xml

1 <?xml version="1.0" encoding="UTF-8"?>

2 <!DOCTYPE struts PUBLIC

3 "-//Apache Software Foundation//DTD Struts

4 Configuration 2.5//EN"

5 "http://struts.apache.org/dtds/struts-2.5.dtd">

6 <struts>

7 <include file="struts-default.xml" />

8 <package name="InterceptorDemo"

9 namespace="/"

10 extends="struts-default">

11 <action name="timer"

12 class="com.qianfeng.struts.action.TimerInterceptorAction"

13 method="execute">

14 <interceptor-ref name="timer" />

15 <result name="success">

16 /timer.jsp

17 </result>

18 </action>

19 </package>

20 </struts>

最后编写前台页面,如例4-3所示。

例4-2 timer.jsp

1 <%@ page language="java" contentType="text/html; charset=UTF-8"

2 pageEncoding="UTF-8"%>

3 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

4 "http://www.w3.org/TR/html4/loose.dtd">

5 <html>

6 <head>

7 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

8 <title>Insert title here</title>

9 </head>

10 <body>

11 <center>

12 <h1>timer页面</h1>

13 </center>

14 </body>

15 </html>

代码编写完成后,运行项目,在浏览器的地址栏访问"http://IP:端口号/项目名/timer.action",此时会跳转到timer.jsp页面,如图4.2所示。

图4.2 timer.jsp

此时查看后台控制台,如图4.3所示。

图4.3 后台控制台

在浏览器地址栏再次访问"http://IP:端口号/项目名/timer.action",此时再次查看后台控制台,如图4.4所示。

图4.4 后台控制台

程序运行时,由于机器性能不同,消耗的时间可能不同,但无论如何,2963ms和500ms还是相差太远了,这是因为第一次访问timer.action时,Struts2需要进行一定的初始化工作。上述例子是利用timer拦截器打印了execute方法运行的时间,可以用于粗略的代码测试等。

4.2.2 Struts2拦截器栈

在实际开发中,经常需要在Action执行前同时执行多个拦截器,如用户登陆、登陆日志记录以及权限检查等,这时,可以把多个拦截器组成一个拦截器栈,在使用时,可以将栈内多个拦截器当成一个整体来引用,当拦截器栈被附加到一个Action上时,在执行Action之前必须先执行拦截器栈中的每一个拦截器,在struts.xml中配置拦截器栈的语法格式如下。

<interceptors>

<interceptor-stack name="interceptorStackName" />

<interceptor-ref name="interceptorName" />

……

</interceptor-stack />

</interceptors>

如上示例中,interceptorStackName表示配置的拦截器栈的名称,interceptorName表示拦截器的名称,另外,在一个拦截器栈中还可以包含另一个拦截器栈,接下来通过一个案例演示如何配置拦截器栈,如例4-4所示。

例4-3 struts.xml

1 <?xml version="1.0" encoding="UTF-8"?>

2 <!DOCTYPE struts PUBLIC

3 "-//Apache Software Foundation//DTD Struts

4 Configuration 2.5//EN"

5 "http://struts.apache.org/dtds/struts-2.5.dtd">

6 <struts>

7 <include file="struts-default.xml" />

8 <package name="default"

9 namespace="/"

10 extends="struts-default">

11 <!-- 声明拦截器 -->

12 <interceptors>

13 <interceptor name="interceptor1"

14 class="interceptorClass1" />

15 <interceptor name="interceptor2"

16 class="interceptorClass2" />

17 <!-- 定义拦截器栈myStack -->

18 <interceptor-stack name="myStack">

19 <interceptor-ref name="defaultStack" />

20 <interceptor-ref name="interceptor3" />

21 <interceptor-ref name="interceptor4" />

22 </interceptor-stack>

23 </interceptors>

24 </package>

25 </struts>

例4-4中,定义了拦截器栈myStack,在myStack中除了引用自定义的两个拦截器外,还引用了一个内置拦截器defaultStack,这个拦截器是必须要引入的。

4.2.3 Struts2的内置拦截器

Struts2中内置了很多拦截器,这些拦截器以name-class对的形式配置在struts-deafult.xml文件中,文件中的部分内置拦截器如图4.5所示。

图4.5 Struts2内置拦截器

图4.5中,name是拦截器的名称,class指定了该拦截器所对应的实现类,自定义的包继承了Struts2的struts-default包,就可以使用默认包中定义的内置拦截器,这些内置拦截器的含义如表4.1所示。

表4.1 Struts2内置拦截器名称及功能

表4.1中列出了Struts2内置拦截器的名称及含义,这些拦截器的使用方式同4.2.1小节讲解的一样,熟练运用这些拦截器能大大提高开发效率,若想熟练掌握这些拦截器的使用,还需要读者在实践中不断练习。

4.2.4 Struts2的默认拦截器

如果想对一个包下的Action使用相同的拦截器,则需要为该包中的每个Action都重复指定同一个拦截器,这种做法明显比较烦琐,这是可以使用默认拦截器,默认拦截器可以对其指定的包中所有的Action都起到拦截的作用。一旦为某一个包指定了默认拦截器,且包中的Action未显式指定拦截器,则会使用默认拦截器,若包中Action显式指定了某个拦截器,则该默认拦截器会被屏蔽。此时,若还想使用默认拦截器,则需要用户手动配置默认拦截器的应用,语法格式如下。

<default-interceptor-ref name="拦截器栈的名称" />

如上示例中,name属性的值必须是已存在的拦截器或拦截器栈的名称,接下来通过一个案例演示如何配置默认拦截器,如例4-5所示。

例4-4 struts.xml

1 <?xml version="1.0" encoding="UTF-8"?>

2 <!DOCTYPE struts PUBLIC

3 "-//Apache Software Foundation//DTD Struts

4 Configuration 2.5//EN"

5 "http://struts.apache.org/dtds/struts-2.5.dtd">

6 <struts>

7 <package name="default"

8 namespace="/"

9 extends="struts-default">

10 <!-- 声明拦截器 -->

11 <interceptors>

12 <interceptor name="interceptor1" class="interceptorClass1" />

13 <interceptor name="interceptor2" class="interceptorClass2" />

14 <!-- 定义拦截器栈myStack -->

15 <interceptor-stack name="myStack">

16 <interceptor-ref name="interceptor1" />

17 <interceptor-ref name="interceptor2" />

18 <interceptor-ref name="defaultStack" />

19 </interceptor-stack>

20 </interceptors>

21 <!-- 配置包下的默认拦截器 -->

22 <default-interceptor-ref name="myStack" />

23 <action name="xxx" class="xxx">

24 <result name="xxx">

25 /xxx.jsp

26 </result>

27 </action>

28 </package>

29 </struts>

例4-5中,指定了包下的默认拦截器为一个拦截器栈,该拦截器栈将会作用于包下所有的Action,另外,每个包下只能定义一个默认拦截器,如需多个拦截器作为默认拦截器,则可以将这些拦截器定义为一个拦截器栈,再将拦截器栈作为默认拦截器使用。

4.3 自定义拦截器

Struts2提供了许多拦截器,这些内置拦截器实现了大部分功能,因此,大部分Web应用的通用功能都可以通过直接使用这些拦截器完成,但也有一些系统逻辑相关的通用功能,需要自定义拦截器来实现,本节将详细讲解自定义拦截器的相关内容。

4.3.1 开发自定义拦截器

Struts2有丰富的内置拦截器,上一节中详细讲解了内置拦截器,但是有一些特殊功能内置拦截器无法实现,这时就可以自定义拦截器,开发自定义拦截器时,需要实现com.opensymphony.xwork2.interceptor.Interceptor接口。实际上,所有Struts2拦截器都直接或间接地实现了该接口,Interceptor接口源代码如下所示。

public interface Interceptor extends Serializable {

void destroy();

void init();

String intercept(ActionInvocation invocation) throws Exception;

}

如上源码中,Interceptor接口定义了三个方法,实现该接口时,需要具体去实现这三个方法,三个方法的具体含义如表4.2所示。

表4.2 Interceptor接口方法及含义

表4.2介绍了Interceptor接口的三个方法,接下来通过一个案例演示实现该接口自定义拦截器,如例4-6所示。

例4-5 MyInterceptor1.java

1 import com.opensymphony.xwork2.ActionInvocation;

2 import com.opensymphony.xwork2.interceptor.Interceptor;

3 public class MyInterceptor1 implements Interceptor {

4 private static final long serialVersionUID = 1L;

5 @Override

6 public void init() {

7 // 初始化资源

8 }

9 @Override

10 public String intercept(ActionInvocation invocation)

11 throws Exception {

12 String className = invocation.getAction().

13 getClass().getName();

14 long start = System.currentTimeMillis();

15 String result = invocation.invoke();

16 long end = System.currentTimeMillis();

17 // 输出调用所用时间

18 System.out.println(className + "用时:" +

19 (end - start) + "ms");

20 return result;

21 }

22 @Override

23 public void destroy() {

24 // 销毁资源

25 }

26 }

例4-6用Interceptor接口的方式,自定义了一个拦截器,除了实现该接口自定义拦截器外,更常用的是继承抽象拦截器类AbstractInterceptor,该类实现了Interceptor接口,提供了init()方法和destory()方法的空实现,在类中只需实现intercept()方法即可,接下来通过一个案例演示继承该类自定义拦截器,如例4-7所示。

例4-6 MyInterceptor2.java

1 import com.opensymphony.xwork2.ActionInvocation;

2 import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

3 public class MyInterceptor2 extends AbstractInterceptor {

4 private static final long serialVersionUID = 1L;

5 @Override

6 public String intercept(ActionInvocation invocation)

7 throws Exception {

8 String className = invocation.getAction().

9 getClass().getName();

10 long start = System.currentTimeMillis();

11 String result = invocation.invoke();

12 long end = System.currentTimeMillis();

13 // 输出调用所用时间

14 System.out.println(className + "用时:" +

15 (end - start) + "ms");

16 return result;

17 }

18 }

例4-7中,通过继承AbstractInterceptor类实现自定义拦截器,功能与例4-6完全相同,但代码简洁了很多。

(脚下留心

intercept()方法的参数为ActionInvocation类型,该参数的invoke()方法直接调用下一个拦截器或Action中的execute()方法,实际上,这时拦截器方法还没有运行结束,因此在最后Action中的execute()方法调用结束之后,会继续调用未执行完成的代码。

4.3.2 配置自定义拦截器

编写完成自定义拦截器后,还需要在struts.xml文件中进行配置,自定义拦截器的配置只需要在<interceptor>的class属性中指定自定义的拦截器类就可以,假设有一个自定义的拦截器类为MyInterceptor,路径为com.qianfeng.acion,其配置方式的示例如下。

<interceptor name="myInterceptor"

class="com.qianfeng.acion.MyInterceptor" />

如上示例中,配置了一个名为myInterceptor的拦截器,其具体实现通过com.qianfeng.acion.MyInterceptor拦截器类来完成。

4.3.3 自定义拦截器案例

学习了如何开发自定义拦截器,这里通过一个登陆权限控制的案例,来演示自定义拦截器的使用,首先开发前台页面,这里需要三个页面,分别是welcome.jsp、login.jsp和show.jsp,编写welcome.jsp如例4-8所示。

例4-7 welcome.jsp

1 <%@ page language="java" contentType="text/html; charset=UTF-8"

2 pageEncoding="UTF-8"%>

3 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

4 "http://www.w3.org/TR/html4/loose.dtd">

5 <html>

6 <head>

7 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

8 <title>Insert title here</title>

9 </head>

10 <body>

11 <center>

12 <h1>

13 <a href="show.action" mce_href="show.action">

14 show

15 </a>

16 </h1>

17 </center>

18 </body>

19 </html>

接着编写login.jsp,如例4-9所示。

例4-8 login.jsp

1 <%@ page language="java" contentType="text/html; charset=UTF-8"

2 pageEncoding="UTF-8"%>

3 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

4 "http://www.w3.org/TR/html4/loose.dtd">

5 <html>

6 <head>

7 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

8 <title>Insert title here</title>

9 </head>

10 <body>

11 <form action="login.action" method="post">

12 User:<input type="text" name="username"><br>

13 Passoword:<input type="password" name="password"><br>

14 <input type="submit" value="登陆">

15 </form>

16 </body>

17 </html>

接着编写show.jsp,如例4-10所示。

例4-9 show.jsp

1 <%@ page language="java" contentType="text/html; charset=UTF-8"

2 pageEncoding="UTF-8"%>

3 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

4 "http://www.w3.org/TR/html4/loose.dtd">

5 <html>

6 <head>

7 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

8 <title>Insert title here</title>

9 </head>

10 <body>

11 <center>

12 <h1>欢迎光临</h1>

13 </center>

14 </body>

15 </html>

编写完前台页面后,接着编写后台代码,这里要编写四个类,分别为LoginFormAction、LoginAction、ShowAction和LoginInterceptor,其中LoginInterceptor是自定义的拦截器,检查是否登陆且用户名是否为root,密码是否为admin,如果检查都通过则放行,编写LoginFormAction如例4-11所示。

例4-10 LoginFormAction.java

1 import com.opensymphony.xwork2.ActionSupport;

2 public class LoginFormAction extends ActionSupport {

3 private static final long serialVersionUID = 1L;

4 public String exexcute() {

5 return "success";

6 }

7 }

编写LoginAction如例4-12所示。

例4-11 LoginAction.java

1 import com.opensymphony.xwork2.ActionContext;

2 import com.opensymphony.xwork2.ActionSupport;

3 public class LoginAction extends ActionSupport {

4 private static final long serialVersionUID = 1L;

5 private String username;

6 private String password;

7 public String getPassword() {

8 return password;

9 }

10 public void setPassword(String password) {

11 this.password = password;

12 }

13 public String getUsername() {

14 return username;

15 }

16 public void setUsername(String username) {

17 this.username = username;

18 }

19 private boolean isInvalid(String value) {

20 return (value == null || value.length() == 0);

21 }

22 public String execute() {

23 if (isInvalid(getUsername()))

24 return INPUT;

25 if (isInvalid(getPassword()))

26 return INPUT;

27 if (this.getUsername().equals("root")

28 && this.getPassword().equals("admin")) {

29 ActionContext.getContext().getSession().

30 put("user", getUsername());

31 ActionContext.getContext().getSession()

32 .put("password", getPassword());

33 return "success";

34 }

35 return "error";

36 }

37 }

编写ShowAction如例4-13所示。

例4-12 ShowAction.java

1 import com.opensymphony.xwork2.ActionSupport;

2 public class ShowAction extends ActionSupport {

3 private static final long serialVersionUID = 1L;

4 public String execute() {

5 return "success";

6 }

7 }

编写拦截器类LoginInterceptor如例4-14所示。

例4-13 LoginInterceptor.java

1 import java.util.Map;

2 import com.opensymphony.xwork2.Action;

3 import com.opensymphony.xwork2.ActionContext;

4 import com.opensymphony.xwork2.ActionInvocation;

5 import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

6 public class LoginInterceptor extends AbstractInterceptor {

7 private static final long serialVersionUID = 1L;

8 @Override

9 public String intercept(ActionInvocation invocation)

10 throws Exception {

11 // 取得请求相关的ActionContext实例

12 ActionContext ctx = invocation.getInvocationContext();

13 Map session = ctx.getSession();

14 String user = (String) session.get("user");

15 // 如果没有登陆,或者登陆所有的用户名不是root,都返回重新登陆

16 if (user != null && user.equals("root")) {

17 return invocation.invoke();

18 }

19 ctx.put("tip", "你还没有登录");

20 return Action.LOGIN;

21 }

22 }

前台和后台代码都编写完成,还需要编写struts.xml配置文件,如例4-15所示。

例4-14 struts.xml

1 <?xml version="1.0" encoding="UTF-8"?>

2 <!DOCTYPE struts PUBLIC

3 "-//Apache Software Foundation//DTD Struts

4 Configuration 2.5//EN"

5 "http://struts.apache.org/dtds/struts-2.5.dtd">

6 <struts>

7 <package name="authority" extends="struts-default">

8 <!-- 定义一个拦截器 -->

9 <interceptors>

10 <interceptor name="authority"

11 class="com.qianfeng.struts.action.LoginInterceptor">

12 </interceptor>

13 <!-- 拦截器栈 -->

14 <interceptor-stack name="mydefault">

15 <interceptor-ref name="defaultStack" />

16 <interceptor-ref name="authority" />

17 </interceptor-stack>

18 </interceptors>

19 <!-- 定义全局Result -->

20 <global-results>

21 <!-- 当返回login视图名时,转入/login.jsp页面 -->

22 <result name="login">

23 /login.jsp

24 </result>

25 </global-results>

26 <action name="loginform"

27 class="com.qianfeng.struts.action.LoginFormAction">

28 <result name="success">

29 /login.jsp

30 </result>

31 </action>

32 <action name="login"

33 class="com.qianfeng.struts.action.LoginAction">

34 <result name="success">

35 /welcome.jsp

36 </result>

37 <result name="error">

38 /login.jsp

39 </result>

40 <result name="input">

41 /login.jsp

42 </result>

43 </action>

44 <action name="show"

45 class="com.qianfeng.struts.action.ShowAction">

46 <result name="success">

47 /show.jsp

48 </result>

49 <!-- 使用此拦截器 -->

50 <interceptor-ref name="mydefault" />

51 </action>

52 </package>

53 </struts>

代码和配置文件编写完成,运行项目,在浏览器地址栏访问http://localhost:8080/struts2_04/welcome.jsp,如图4.6所示。

图4.6 welcome.jsp

点击"show",此时被拦截器拦截,跳转到登陆页面,如图4.7所示。

图4.7 login.jsp

此时如果输入的用户名不是root或者密码不是admin,则重新跳转回login.jsp页面,输入正确的用户名和密码后,成功登陆,跳转回welcome.jsp,如图4.8所示。

图4.8 welcome.jsp

此时再次点击"show",成功跳转到show.jsp页面,如图4.9所示。

图4.9 show.jsp

如上案例中,自定义拦截器通过验证用户名和密码是否正确,实现了页面访问的权限控制,关于拦截器还有更多的功能,需要读者在实践中不断尝试和挖掘。

4.4 本章小结

本章主要介绍了拦截器的知识,包括拦截器的配置和使用方法,然后介绍了自定义拦截器的开发和配置,最后根据自定义拦截器,完成了一个登陆权限控制的案例。

4.5 习题

1.填空题

(1) 拦截器就是 的一种实现策略。

(2) 是解决重复性问题的重要方式,它提供了更高层次的解耦和抽象,目标代码无须手动调用目标方法。

(3) 在 文件中定义了很多内置拦截器。

(4) 在实际开发中,经常需要在Action执行前同时执行多个拦截器,如用户登陆、登陆日志记录以及权限检查等,这时,可以把多个拦截器组成一个

(5) 开发自定义的拦截器除了可以通过实现Interceptor接口,还可以继承抽象拦截器类

3.思考题

(1) 请简述拦截器的意义。

(2) 请简述如何配置Struts2拦截器。

(3) 请说出至少三个Struts2的内置拦截器。

(4) 请简述什么是拦截器栈。

(5) 请简述如何自定义拦截器。

举报
评论 0