本讲将结合模拟用户权限验证展开:判断用户是否有权限请求访问某一模块或页面.
第九讲、自定义拦截器
9.1 首先定义一个User实体类,如下:
- publicclass User implements Serializable {
- private String username;
- private String password;
- public String getUsername() {
- return username;
- }
- publicvoid setUsername(String username) {
- this.username = username;
- }
- public String getPassword() {
- return password;
- }
- publicvoid setPassword(String password) {
- this.password = password;
- }
- }
public class User implements Serializable {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
9.2 编写一个用户登陆的Action类,如下:
- publicclass UserLoginAction {
- //接收复合类型的参数,与struts1.x相类似,此时表单的元素名称
- //应该为:user.username user.password
- private User user;
- public User getUser() {
- return user;
- }
- publicvoid setUser(User user) {
- this.user = user;
- }
- public String execute(){
- //模拟用户登陆,实际应用是从数据库里取的,这里只是为了方便测试和学习
- if("yulon".equals(user.getUsername())&&"123456".equals(user.getPassword())){
- //为了不让我们看到真正的session,struts2框架对其作了一层封装,用一个Map对象来存储.
- //Map map = ActionContext.getContext().getSession();
- //map.put("user", user);
- //把两段代码合成一段,就不用导入Map类
- ActionContext.getContext().getSession().put("user", user);
- return Action.SUCCESS; //返回到欢迎页面
- }
- ActionContext.getContext().put("msg", "用户登陆失败!");
- //返回到登陆页面,struts定义了一系列字符串常量,方便用户使用,统一的好处
- return Action.LOGIN;
- }
- }
public class UserLoginAction {
//接收复合类型的参数,与struts1.x相类似,此时表单的元素名称
//应该为:user.username user.password
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String execute(){
//模拟用户登陆,实际应用是从数据库里取的,这里只是为了方便测试和学习
if("yulon".equals(user.getUsername())&&"123456".equals(user.getPassword())){
//为了不让我们看到真正的session,struts2框架对其作了一层封装,用一个Map对象来存储.
//Map map = ActionContext.getContext().getSession();
//map.put("user", user);
//把两段代码合成一段,就不用导入Map类
ActionContext.getContext().getSession().put("user", user);
return Action.SUCCESS; //返回到欢迎页面
}
ActionContext.getContext().put("msg", "用户登陆失败!");
//返回到登陆页面,struts定义了一系列字符串常量,方便用户使用,统一的好处
return Action.LOGIN;
}
}
知识提示:注意这里返回的是login,而不是LOGIN.查看ActionContext部分源码及相关文档可知,它是一个与线程相关的类,同一个线程内获取的都是同一个ActionContext实例,原理与ThreadLocal相关,之前有讲过ThreadLocal相关知识,ThreadLocal实现在同一个线程内的数据共享,可以更深入得理解ActionContext底层的实现原理.
9.3 在struts.xml配置文件里新增一个package包,名称叫testinterceptor,命名空间定义为/test2,如下配置:
- <package name="testinterceptor" namespace="/test2"extends="struts-default">
- <!--定义一个转发到登陆页面的Action-->
- <action name="loginUI">
- <result>/WEB-INF/jsp/login.jsp</result>
- </action>
- <action name="login"class="cn.gkit.action.UserLoginAction" method="execute">
- <!--重定向到同一个包内名字叫index的Action-->
- <result name="success" type="redirectAction">index</result>
- <result name="login">/WEB-INF/jsp/login.jsp</result>
- </action>
- <!--为了方便测试,同时也定义一个登出的Action-->
- <action name="logout"class="cn.gkit.action.UserLogoutAction" method="execute">
- <result name="success" type="redirectAction">loginUI</result>
- </action>
- <!--用户登陆成功后的欢迎页面-->
- <action name="index">
- <result name="success">/WEB-INF/jsp/welcome.jsp</result>
- <result name="login" type="redirectAction">loginUI</result>
- </action>
- </package>
<package name="testinterceptor" namespace="/test2" extends="struts-default">
<!--定义一个转发到登陆页面的Action-->
<action name="loginUI">
<result>/WEB-INF/jsp/login.jsp</result>
</action>
<action name="login" class="cn.gkit.action.UserLoginAction" method="execute">
<!--重定向到同一个包内名字叫index的Action-->
<result name="success" type="redirectAction">index</result>
<result name="login">/WEB-INF/jsp/login.jsp</result>
</action>
<!--为了方便测试,同时也定义一个登出的Action-->
<action name="logout" class="cn.gkit.action.UserLogoutAction" method="execute">
<result name="success" type="redirectAction">loginUI</result>
</action>
<!--用户登陆成功后的欢迎页面-->
<action name="index">
<result name="success">/WEB-INF/jsp/welcome.jsp</result>
<result name="login" type="redirectAction">loginUI</result>
</action>
</package>
9.4 编写UserLogoutAction类
- publicclass UserLogoutAction {
- public String execute(){
- //取出当前登陆的用户
- User user = (User)ActionContext.getContext().getSession().get("user");
- if(user!=null){
- //注销用户信息
- ActionContext.getContext().getSession().remove("user");
- }
- return Action.SUCCESS;
- }
- }
public class UserLogoutAction {
public String execute(){
//取出当前登陆的用户
User user = (User)ActionContext.getContext().getSession().get("user");
if(user!=null){
//注销用户信息
ActionContext.getContext().getSession().remove("user");
}
return Action.SUCCESS;
}
}
9.5 编写\WEB-INF\jsp\login.jsp页面
- <%@ page language="java"import="java.util.*" pageEncoding="utf-8"%>
- <%@ taglib prefix="s" uri="/struts-tags"%>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>用户登陆</title>
- </head>
- <span>${msg}</span>
- <body>
- <s:form action="login" namespace="/test2" method="post">
- 用户名: <s:textfield title="填写用户名" name="user.username" ></s:textfield><br/>
- 密 码 :<s:password title="填写密码" name="user.password"></s:password><br/>
- <s:submit value="登陆"></s:submit>
- </s:form>
- </body>
- </html>
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>用户登陆</title>
</head>
<span>${msg}</span>
<body>
<s:form action="login" namespace="/test2" method="post">
用户名: <s:textfield title="填写用户名" name="user.username" ></s:textfield><br/>
密 码 :<s:password title="填写密码" name="user.password"></s:password><br/>
<s:submit value="登陆"></s:submit>
</s:form>
</body>
</html>
知识提示:使用struts2标签首先在导入相关uri, 可以在struts2核心包下的META-INF目录下找到struts-tags.tld文件,里面就有uri的定义,具体标签的使用暂不在本章细讲,大家可以先琢磨一下.
9.6 编写\WEB-INF\jsp\welcome.jsp
- <%@ page language="java"import="java.util.*" pageEncoding="utf-8"%>
- <%@ taglib prefix="s" uri="/struts-tags"%>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>欢迎首页</title>
- </head>
- <body>
- 欢迎用户${sessionScope.user.username}的到来!<br />
- <a href='<s:url action="logout" namespace="/test2" />'>退出</a>
- </body>
- </html>
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>欢迎首页</title>
</head>
<body>
欢迎用户${sessionScope.user.username}的到来!<br />
<a href='<s:url action="logout" namespace="/test2" />'>退出</a>
</body>
</html>
在没有使用拦截器之前,是可以直接访问/struts2.1/test2/index请求的.
9.7实现自己的拦截器cn.gkit.web.interceptor.UserAuthInterceptor(有三种方法)
第一种方法是实现com.opensymphony.xwork2.interceptor.Interceptor接口,实现相应的方法就行.
- publicclass UserAuthInterceptor implements Interceptor{
- publicvoid destroy() {
- }
- publicvoid init() {
- init() System.out.println("服务器启动过程中自动加载");
- }
- public String intercept(ActionInvocation invocation) throws Exception {
- //ActionContext.getContext().getSession().get("user");
- Map map = invocation.getInvocationContext().getSession();
- if(map.get("user")==null){
- System.out.println("用户还没登陆");
- return Action.LOGIN; //struts定义了一系统字符串常量,注意这里返回的是login,而不是LOGIN.
- }else
- {
- String result = invocation.invoke();//如果用户已登陆就通过验证,继承执行下一个拦截器
- System.out.println("返回结果:"+result);
- return result;
- }
- }
- }
public class UserAuthInterceptor implements Interceptor{
public void destroy() {
}
public void init() {
init() System.out.println("服务器启动过程中自动加载");
}
public String intercept(ActionInvocation invocation) throws Exception {
//ActionContext.getContext().getSession().get("user");
Map map = invocation.getInvocationContext().getSession();
if(map.get("user")==null){
System.out.println("用户还没登陆");
return Action.LOGIN; //struts定义了一系统字符串常量,注意这里返回的是login,而不是LOGIN.
}else
{
String result = invocation.invoke();//如果用户已登陆就通过验证,继承执行下一个拦截器
System.out.println("返回结果:"+result);
return result;
}
}
}
知识提示:invocation.getInvocationContext()获取到的是一个与ActionInvocation相关的ActionContext ,但使用ActionContext.getContext()也一样可以取得.
第二种方法是继承AbstractInterceptor抽象,此类的设计思想跟之前讲的类型转换器类似.它本身也是实现了Interceptor接口,由于我们一般都不用到init()方法和destroy()方法,因此通常情况下都是通过继承AbstractInterceptor来实现我们的拦截器.如下:
- publicclass UserAuthInterceptor extends AbstractInterceptor{
- @Override
- public String intercept(ActionInvocation invocation) throws Exception {
- //ActionContext.getContext().getSession().get("user");
- Map map = invocation.getInvocationContext().getSession();
- if(map.get("user")==null){
- System.out.println("用户还没登陆");
- return Action.LOGIN;
- }else
- {
- String result = invocation.invoke();
- System.out.println("返回结果:"+result);
- return result;
- }
- }
- }
public class UserAuthInterceptor extends AbstractInterceptor{
@Override
public String intercept(ActionInvocation invocation) throws Exception {
//ActionContext.getContext().getSession().get("user");
Map map = invocation.getInvocationContext().getSession();
if(map.get("user")==null){
System.out.println("用户还没登陆");
return Action.LOGIN;
}else
{
String result = invocation.invoke();
System.out.println("返回结果:"+result);
return result;
}
}
}
知识提示:查看AbstractInterceptor源码,你会感到惊讶,如下:
- publicabstractclass AbstractInterceptor implements Interceptor {
- publicvoid init() {
- }
- publicvoid destroy() {
- }
- publicabstract String intercept(ActionInvocation invocation) throws Exception;
- }
public abstract class AbstractInterceptor implements Interceptor {
public void init() {
}
public void destroy() {
}
public abstract String intercept(ActionInvocation invocation) throws Exception;
}
知识提示:你没有看错,就是几行代码,它只是帮我们实现了两个不常用到的方法,并且还是空实现. 但里面包含的一种设计思想值得我们去学习研究,所以说学习框架的最高境界是学习它的思想,思想就存在代码当中,大家有时间可以多查看一下它的源码.
9.8 将我们编写好的拦截器类加载到struts.xml配置文件中,如下定义:
- <interceptors>
- <!-- 加载自己编写的拦截器 -->
- <interceptor name="authInterceptor"class="cn.gkit.web.interceptor.UserAuthInterceptor" />
- </interceptors>
<interceptors>
<!-- 加载自己编写的拦截器 -->
<interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" />
</interceptors>
9.9 将我们编写好的拦截器应用到具体的action类上,将struts.xml 名称为index的<action>改如下:
- <action name="index">
- <result name="success">/WEB-INF/jsp/welcome.jsp</result>
- <result name="login" type="redirectAction">loginUI</result>
- <interceptor-ref name="authInterceptor"></interceptor-ref>
- </action>
<action name="index">
<result name="success">/WEB-INF/jsp/welcome.jsp</result>
<result name="login" type="redirectAction">loginUI</result>
<interceptor-ref name="authInterceptor"></interceptor-ref>
</action>
此时:若访问/struts2.1/test2/index,请求将先会被自己定义的authInterceptor拦截器拦截,执行intercept方法的代码,判断用户若没有登陆则转到登陆页面,若存在才会放行(即执行下一步,这里的下一步会执行请求对应的Action方法).
问题:为什么说拦截器是struts2的核心呢?是因为struts2的很多工作都是通过拦截器来实现的,在你定义的<package>的同时,只要你extends 了struts-default包,就同时拥有了struts-default包内定义的全部内容.通过查看struts-default.xml文件可知,它里面定义了很多拦截器,每个拦截器负责完成不同的工作.如里面名字为params的拦截器的作用是会将页面表单的参数会自动赋值到action里的属性。其中在最后定义了一个默认的拦截器栈<default-interceptor-ref name="defaultStack"/>,默认情况下默认拦截器栈会应用到包内定义的所有action身上. 但如果你手工在一个<action>添加一个额外的拦截器后,此时默认的拦截器栈对本<action>不起作用.解决的办法如下:
第一种解决方案:重新引入defaultStack拦截器栈,将index改如下:
- <action name="index">
- <result name="success">/WEB-INF/jsp/welcome.jsp</result>
- <result name="login" type="redirectAction">loginUI</result>
- <interceptor-ref name="authInterceptor"></interceptor-ref>
- <!-- 重新引入 defaultStack-->
- <interceptor-ref name="defaultStack"></interceptor-ref>
- </action>
<action name="index">
<result name="success">/WEB-INF/jsp/welcome.jsp</result>
<result name="login" type="redirectAction">loginUI</result>
<interceptor-ref name="authInterceptor"></interceptor-ref>
<!-- 重新引入 defaultStack-->
<interceptor-ref name="defaultStack"></interceptor-ref>
</action>
第二种解决方案:定义自已的拦截器栈,如下:
- <interceptors>
- <!-- 加载自己编写的拦截器 -->
- <interceptor name="authInterceptor"class="cn.gkit.web.interceptor.UserAuthInterceptor" />
- <!-- 定义拦截器栈 -->
- <interceptor-stack name="myInterceptorStack">
- <!-- 引用用户定义的拦截器 -->
- <interceptor-ref name="authInterceptor"></interceptor-ref>
- <!-- 同时也要重新把默认的拦截器栈引入进来 -->
- <interceptor-ref name="defaultStack"></interceptor-ref>
- </interceptor-stack>
- </interceptors>
<interceptors>
<!-- 加载自己编写的拦截器 -->
<interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" />
<!-- 定义拦截器栈 -->
<interceptor-stack name="myInterceptorStack">
<!-- 引用用户定义的拦截器 -->
<interceptor-ref name="authInterceptor"></interceptor-ref>
<!-- 同时也要重新把默认的拦截器栈引入进来 -->
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
将自己定义好的拦截器栈应用到具体action类中,如将<action>改写成:
- <action name="index">
- <result name="success">/WEB-INF/jsp/welcome.jsp</result>
- <result name="login" type="redirectAction">loginUI</result>
- <!-- 引入自定义拦截器栈 -->
- <interceptor-ref name="myInterceptorStack"></interceptor-ref>
- </action>
<action name="index">
<result name="success">/WEB-INF/jsp/welcome.jsp</result>
<result name="login" type="redirectAction">loginUI</result>
<!-- 引入自定义拦截器栈 -->
<interceptor-ref name="myInterceptorStack"></interceptor-ref>
</action>
第三种解决方案:重新定义默认拦截器栈
- <interceptors>
- <!-- 加载自己编写的拦截器 -->
- <interceptorname="authInterceptor"class="cn.gkit.web.interceptor.UserAuthInterceptor"/>
- <!-- 定义拦截器栈 -->
- <interceptor-stackname="myInterceptorStack">
- <!-- 引用用户定义的拦截器 -->
- <interceptor-refname="authInterceptor"></interceptor-ref>
- <!-- 同时也要重新把默认的拦截器栈引入进来 -->
- <interceptor-refname="defaultStack"></interceptor-ref>
- </interceptor-stack>
- </interceptors>
- <!-- 重新定义默认的拦截器栈,覆盖掉struts-default定义的默认拦截器栈 -->
- <default-interceptor-refname="myInterceptorStack"></default-interceptor-ref>
<interceptors> <!-- 加载自己编写的拦截器 --> <interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" /> <!-- 定义拦截器栈 --> <interceptor-stack name="myInterceptorStack"> <!-- 引用用户定义的拦截器 --> <interceptor-ref name="authInterceptor"></interceptor-ref> <!-- 同时也要重新把默认的拦截器栈引入进来 --> <interceptor-ref name="defaultStack"></interceptor-ref> </interceptor-stack> </interceptors> <!-- 重新定义默认的拦截器栈,覆盖掉struts-default定义的默认拦截器栈 --> <default-interceptor-ref name="myInterceptorStack"></default-interceptor-ref>
注意:此时在testinterceptor包内定义的所有<action>默认都会被myInterceptorStack拦截器拦截,index不用重新定义拦截器,改如下:
- <actionname="index">
- <resultname="success">/WEB-INF/jsp/welcome.jsp</result>
- <resultname="login"type="redirectAction">loginUI</result>
- </action>
<action name="index"> <result name="success">/WEB-INF/jsp/welcome.jsp</result> <result name="login" type="redirectAction">loginUI</result> </action>
具体使用哪一种方法按项目需求而定
9.10 在 9.7节里面还有一种自定义拦截器的方法没讲到:就是方法过滤拦截器(MethodFilterInterceptor)
编写方法过滤拦截器第一步:编写继承于MethodFilterInterceptor抽象类的拦截器
- publicclass MyMethodFilterInterceptor extends MethodFilterInterceptor {
- @Override
- protected String doIntercept(ActionInvocation invocation) throws Exception {
- System.out.println("执行了MyMethodFilterInterceptor拦截器");
- String resultString = invocation.invoke();
- System.out.println("返回的结果:"+resultString);
- return resultString;
- }
- }
public class MyMethodFilterInterceptor extends MethodFilterInterceptor {
@Override
protected String doIntercept(ActionInvocation invocation) throws Exception {
System.out.println("执行了MyMethodFilterInterceptor拦截器");
String resultString = invocation.invoke();
System.out.println("返回的结果:"+resultString);
return resultString;
}
}
第二步,在上次编写的test包内加载这个拦截器,如下:
- <interceptors>
- <interceptorname="mymethodinterceptor"class="cn.gkit.web.interceptor.MyMethodFilterInterceptor"></interceptor>
- </interceptors>
<interceptors> <interceptor name="mymethodinterceptor" class="cn.gkit.web.interceptor.MyMethodFilterInterceptor"></interceptor> </interceptors>第三步,在具体的action里应用该拦截器,如下:
- <packagename="test"namespace="/test"extends="gkit">
- <interceptors>
- <!-- 定义方法过滤拦截器 -->
- <interceptorname="mymethodinterceptor"class="cn.gkit.web.interceptor.MyMethodFilterInterceptor"/>
- </interceptors>
- <actionname="*User"class="cn.gkit.action.HelloWorldAction"method="{1}">
- <paramname="message">属性注入</param>
- <resultname="success">/WEB-INF/jsp/helloworld.jsp</result>
- <interceptor-refname="mymethodinterceptor">
- <!--指定要拦截的方法 includeMethods的优先级比excludeMethods的要高-->
- <paramname="includeMethods">add,execute</param>
- </interceptor-ref>
- <interceptor-refname="defaultStack"></interceptor-ref>
- </action>
- </package>
<package name="test" namespace="/test" extends="gkit"> <interceptors> <!-- 定义方法过滤拦截器 --> <interceptor name="mymethodinterceptor" class="cn.gkit.web.interceptor.MyMethodFilterInterceptor"/> </interceptors> <action name="*User" class="cn.gkit.action.HelloWorldAction" method="{1}" > <param name="message">属性注入</param> <result name="success">/WEB-INF/jsp/helloworld.jsp</result> <interceptor-ref name="mymethodinterceptor"> <!--指定要拦截的方法 includeMethods的优先级比excludeMethods的要高--> <param name="includeMethods">add,execute</param> </interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> </action> </package>