struts.jpg
1.Struts2 拦截器
1.1 AOP 思想
AOP 是 Aspect Objected Prograing(面向切面编程)的缩写。struts2 中的拦截器就是这种编程策略的一种实现,AOP 思想是在基本功能上,不通过修改源代码就可以扩展功能,提高代码的重用性。
1.2 拦截器概述
struts2 框架的许多功能都是基于拦截器的,struts2 中有很多拦截器,默认的拦截器每次都执行。说到拦截器,还有一个种重要的概念——拦截器链(在 struts2 中称为是拦截器栈)。拦截器链就是将一堆拦截器按照一定顺序联结成一条链,在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其定义的顺序调用。
1.3 struts2 拦截器原理
struts2 的拦截是通过 xml文件的配置实现的,默认的拦截器在 struts2-core-*.jar 包中的 struts-default.xml 文件,可以查看源代码。当收到一个请求时,struts2 会先查找xml配置文件,并根据配置来实例化拦截器对象,然后串成一条链,请求要通过每一个拦截器,才能执行 Action 中的方法,最终才能得到想要的结果。这么多拦截器,使用一个代理对象对它们进行动态调用。当 action 的请求到来时,创建 Action 的代理对象,这个代理对象在 Action 方法执行之前执行默认的拦截器和其他拦截器(用户自定义的拦截器),最后才是 Action 对应方法的调用,这里面是数据结构栈的思想。代理对象调用栈的最底层才是 Action 方法的调用,然后在返回给上一个拦截器,层层退出。
struts2 拦截器结构的设计是一个典型的责任链模式的应用,首先将整个执行的过程划分为若干相同类型的元素,每个元素具备不同的逻辑责任,并将这些元素放到一个栈式的数据结构中,每个元素又有责任负责下一个元素的执行调用。将一个复杂的系统,分而治之,从而将每个部分的逻辑能够高度重用并具备高度扩展性,拦截器在 struts2 中的设计实乃精彩。
实现原理
查看源代码
找到 web.xml 文件中配置拦截器类的位置 org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
,找到这个类中的 execute.executeAction(request, response, mapping)
方法,进入这个方法查看,dispatcher.serviceAction(request, response, mapping)
,在进入这个方法找到 ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, extraContext, true, false)
,这个方法就会创建 action 的代理对象,不是直接 new 出来的,继续往下找到 proxy.execute()
方法,进入方法查看,会发现这是一个接口,所以找到这个方法的实现类 StrutsActionProxy
,在这个类中找到 execute()
方法,该方法的返回值是 invocation.invoke()
,表示执行 Action 中的方法,进入方法查看也是一个接口,找到这个方法的实现类 DefaultActionInvocation
,在这个类的 invoke()
方法中有一个遍历拦截器的操作 if (interceptors.hasNext())
,一个拦截器执行完后执行下一个拦截器,最后返回invocation.invoke()
,执行 Action 中的方法。
1.4 拦截器和过滤器的区别
- 过滤器理论上可以过滤任意内容,比如 jsp页面,html页面,servlet,图片路径等等
- 拦截器只能拦截 Action,每次访问时就会创建一个 Action,多个 Action 的对象。
1.4 自定义拦截器
在 struts2 中有很多默认的拦截器,打开 struts2-core-*.jar 包中的 struts-default.xml 文件,在 <interceptor-stack name="paramsPrepareParamsStack">
标签中的拦截器就是 struts2 的默认拦截器。在实际的开发中,如果想使用是 struts2 中没有的拦截器功能,这时就要自己写自定义的拦截器。
查看源代码查看拦截器类的结构
在 struts-default.xml 文件中,有很多拦截器,在 <interceptors>
标签中有拦截器的包名,可以找一个进入查看,这些默认的拦截器类都继承自抽象的拦截器类 AbstractInterceptor
,抽象拦截器类中有 init(),destroy(),intercept() 三个方法。init() 方法执行拦截器的初始化操作,destroy() 方法执行拦截器的销毁操作,intercept() 方法执行拦截器具体的逻辑操作。但在开发中建议继承 MethodFilterInterceptor
类来实现自定义的拦截器,这样可以对 Action 中的某些方法不进行拦截。
拦截器和 Action 建立关系不是直接在 Action 中调用拦截器的方法,而是通过配置文件的方式让两者建立关系的。
拦截器实现的步骤:
- 创建拦截器类,继承 MethodFilterInterceptor 类
- 重写 MethodFilterInterceptor 类中的 doIntercept() 方法,在这个方法写拦截器的逻辑
拦截器配置的步骤:
- 在要拦截的
action
标签所在的package
标签中声明拦截器
- 在要拦截的
- 在具体的
action
标签中使用声明的自定义拦截器
- 在具体的
- 手动配置 struts2 的默认拦截器
下面就通过一个用户登陆的案例来说明自定义拦截器的使用。
在 Web 应用中,用户需要在登录之后才能使用主页面的功能,如果用户没有登录,则在使用主页面的功能之前先让其登录,用户登录成功,在 session 中保存用户名。
示例代码如下:
拦截器类
代码语言:javascript复制package cc.wenshixin.interceptor;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
public class LoginInteceptor extends MethodFilterInterceptor{
@Override
//在这个方法中写拦截器的逻辑
protected String doIntercept(ActionInvocation invocation) throws Exception {
//判断 session 中是否有用户名,也即是判断用户是否登录
HttpServletRequest request = ServletActionContext.getRequest();
Object obj = request.getSession().getAttribute("username");
if(obj != null)
{
//已经登录,则执行 Action 中的方法
return invocation.invoke();
}else
{
//未登录,则返回值使其跳转到登录页面
return "login";
}
}
}
Action 类
代码语言:javascript复制package cc.wenshixin.action;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class ActionDemo extends ActionSupport{
public String login()
{
//获取表单数据
HttpServletRequest request = ServletActionContext.getRequest();
String username = request.getParameter("username");
String password = request.getParameter("password");
//这里为了简单就直接写死判断了,实际开发中要在数据库查询
if("admin".equals(username) && "123".equals(password))
{
//登录成功,设置session值,并挑转到主页面
request.getSession().setAttribute("username", username);
return "success";
}else
{
//用户名密码错误,则跳转回登录页面
return "login";
}
}
public String add()
{
//跳转到添加页面
return "add";
}
public String delete()
{
//跳转到删除页面
return "delete";
}
}
struts2 配置文件
注意:如果在配置文件中配置自定义的拦截器,默认的 struts2 拦截器就不会执行了,所以要把默认的拦截器手动使用一下。
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<package name="demo" extends="struts-default" namespace="/">
<!-- 声明拦截器 -->
<interceptors>
<interceptor name="loginInteceptor" class="cc.wenshixin.interceptor.LoginInteceptor"></interceptor>
</interceptors>
<action name="user-*" class="cc.wenshixin.action.ActionDemo" method="{1}">
<!-- 使用自定义的拦截器 -->
<interceptor-ref name="loginInteceptor">
<!-- 配置不拦截的方法,多个方法用逗号隔开 -->
<param name="excludeMethods">login</param>
</interceptor-ref>
<!-- 手动使用默认的拦截器 -->
<interceptor-ref name="defaultStack"></interceptor-ref>
<!-- 设置页面跳转为重定向方式 -->
<result name="success" type="redirect">/index.jsp</result>
<result name="login" type="redirect">/login.jsp</result>
<result name="add" type="redirect">/add.jsp</result>
<result name="delete" type="redirect">/delete.jsp</result>
</action>
</package>
</struts>
jsp 页面
- 登录页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>登录页面</h1>
<form action="{pageContext.request.contextPath}/user-login.action" method="post">
用户名:<input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
- 主页页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>欢迎 ${sessionScope.username} 主页面</h1>
<a href="{pageContext.request.contextPath}/user-add.action" target="_parent">跳转到添加页面</a>
<a href="{pageContext.request.contextPath}/user-delete.action" target="_parent">跳转到删除页面</a>
</body>
</html>
- 添加页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>这是添加页面</h1>
</body>
</html>
- 删除页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>这是删除页面</h1>
</body>
</html>
2.struts2 标签库
2.1 普通标签
- <s:property>:和 OGNL 表达式一起使用在 jsp 页面中获取值栈中的数据
- <s:iterator>:获取值栈 list 集合的数据,遍历 list 集合中的值
- <s:debug>:查看值栈结构和数据
2.2 表单标签
2.2.1 HTML 表单相关标签回顾
form 表单
<form action="" method="" enctype="">
输入项
普通输入项: <input type="text">
密码输入项: <input type="password">
单选输入项: <input type="radio">
复选输入项: <input type="checkbox">
文件上传项: <input type="file">
隐藏输入项: <input type="hidden">
普通按钮: <input type="button">
提交按钮: <input type="submit">
图片按钮: <input type="image">
重置按钮: <input type="reset">
下拉输入项: <select><option></select>
文本域: <textarea rows="" cols=""></textarea>
2.2.2 struts2 中对应的表单标签(知道即可)
注意要在 jsp 中引入 struts2 的标签库。
代码语言:javascript复制 <s:form>
<!-- 普通输入项 -->
<s:textfield name="username" label="用户名"></s:textfield>
<!-- 密码输入项 -->
<s:password name="password" label="密码"></s:password>
<!-- 单选输入项 -->
<!-- value的值和显示的值一样的写法 -->
<s:radio list="{'男','女','保密'}" name="sex" label="性别"></s:radio>
<!-- value的值和显示的值不一样的写法 -->
<s:radio list="#{'nan':'男','nv':'女','secret':'保密'}" name="sex" label="性别"></s:radio>
<!-- 复选框输入项 -->
<s:checkboxlist list="{'看书','写字','画画'}" name="love" label="爱好"></s:checkboxlist>
<!-- 下拉输入项 -->
<s:select list="{'北京','上海','广州'}" name="address" label="地址"></s:select>
<!-- 文件输入项 -->
<s:file name="file" label="上传文件"></s:file>
<!-- 隐藏输入项 -->
<s:hidden name="hidden" value="123"></s:hidden>
<!-- 文本域 -->
<s:textarea rows="10" cols="10"></s:textarea>
<!-- 提交按钮 -->
<s:submit value="提交"></s:submit>
<!-- 重置按钮 -->
<s:reset value="重置"></s:reset>
</s:form>
页面效果如下图所示
效果图
页面源码如下图所示
查看网页源代码,可以发现,struts2 的 form 标签里面是通过 table 来布局的,这并不符合现在的网页布局方式(Div 布局),也不利于页面的美化,所以 struts2 的 form 标签基本不用。
页面源码