Struts2框架详解第三课
1. Struts2中的拦截器
1.1 拦截器的重要性
Struts2中的很多功能是由拦截器完成的,比如servletConfig,staticParam,params,modelDriven等等,是AOP编程思想的一种应用形式。
拦截器执行时机:
1.2 自定义拦截器
1.2.1 拦截器的类试图
1.2.2 自定义拦截器的步骤
1.2.2.1 编写一个类,继承AbStractInterceptor类或者实现interceptor接口,重写intercept方法
public class Demo1Interceptor extends AbstractInterceptor { @Override public String intercept(ActionInvocation invocation) throws Exception { String rtValue=invocation.invoke();//放行 return rtValue;//返回结果视图 } } |
1.2.2.2 配置拦截器:先声明然后使用
<package name="p1" extends="struts-default"> <!-- 1.声明拦截器 --> <interceptors> <interceptor name="demo1Interceptor" class="com.mangocity.web.interceptor.Demo1Interceptor"></interceptor> </interceptors> <action name="demo1" class="com.mangocity.web.action.Demo1Action"> <!-- 2.使用拦截器 --> <interceptor-ref name="demo1Interceptor"></interceptor-ref> <result>/success.jsp</result> </action> </package> |
问题:以上配置文件中,配置了自己的拦截器,导致默认的拦截器不起作用。
解决方法:把默认拦截器加入到配置文件中:
<package name="p1" extends="struts-default"> <!-- 1.声明拦截器 --> <interceptors> <interceptor name="demo1Interceptor" class="com.mangocity.web.interceptor.Demo1Interceptor"></interceptor> </interceptors> <action name="demo1" class="com.mangocity.web.action.Demo1Action"> <!-- 2.使用拦截器 --> <interceptor-ref name="demo1Interceptor"></interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> <result>/success.jsp</result> </action> </package> |
问题:以上配置文件中,当有多个拦截器时,需要修改的地方非常多
解决办法:声明拦截器栈,使用拦截器栈
<package name="p1" extends="struts-default"> <!-- 1.声明拦截器 --> <interceptors> <interceptor name="demo1Interceptor" class="com.mangocity.web.interceptor.Demo1Interceptor"></interceptor> <!-- 声明拦截器栈 --> <interceptor-stack name="myDefaultStack"> <interceptor-ref name="demo1Interceptor"></interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> </interceptor-stack> </interceptors> <action name="demo1" class="com.mangocity.web.action.Demo1Action"> <!-- 使用拦截器栈--> <interceptor-ref name="myDefaultStack"></interceptor-ref> <result>/success.jsp</result> </action> </package> |
问题:以上配置文件中每个动作方法都要引入拦截器栈,太繁琐。
解决方法:设置默认拦截器栈
<package name="p1" extends="struts-default"> <!-- 1.声明拦截器 --> <interceptors> <interceptor name="demo1Interceptor" class="com.mangocity.web.interceptor.Demo1Interceptor"></interceptor> <!-- 声明拦截器栈 --> <interceptor-stack name="myDefaultStack"> <interceptor-ref name="demo1Interceptor"></interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> </interceptor-stack> </interceptors> <!-- 定义默认拦截器栈 --> <default-interceptor-ref name="myDefaultStack"></default-interceptor-ref> <action name="demo1" class="com.mangocity.web.action.Demo1Action"> <result>/success.jsp</result> </action> </package> |
问题:当定义了默认拦截器栈,包里面的所有动作都要经过这些拦截器,但很有可能有些动作是不要经过自定义拦截器的。
解决方法:通过观察AbstractInterceptor的子类,有一个抽象的子类:MethodFilterInterceptor里面有两个属性ecxcludeMethods\includeMethods,可以通过注入属性,说明哪些动作需要被拦截
自定义拦截器继承MethodFilterInterceptor,重写里面的doIntercept方法:
public class Demo2Interceptor extends MethodFilterInterceptor { @Override protected String doIntercept(ActionInvocation invocation) throws Exception { HttpSession session=ServletActionContext.getRequest().getSession(); String user=(String) session.getAttribute("user"); //检查用户是否登陆,登陆了放行,没登录,返回登陆页面 if(StringUtils.isNotBlank(user)){ return invocation.invoke(); } return "input"; } } |
配置文件中配置需要拦截哪些方法,和需要放过哪些方法
package name="p1" extends="struts-default"> <interceptors> <!-- 声明拦截器的时候注入属性,说明哪些动作类是不被拦截的 --> <interceptor name="demo2Interceptor" class="com.mangocity.web.interceptor.Demo2Interceptor"> <param name="ecxcludeMethods ">login</param> </interceptor> <!-- 声明拦截器栈 --> <interceptor-stack name="myDefaultStack"> <interceptor-ref name="demo1Interceptor"></interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> </interceptor-stack> </interceptors> <!-- 定义默认拦截器栈 --> <default-interceptor-ref name="myDefaultStack"></default-interceptor-ref> <action name="demo1" class="com.mangocity.web.action.Demo1Action"> <result>/success.jsp</result> </action> </package> |
问题:以上配置文件是硬编码,我们在定义拦截器的时候并不知道哪些动作需要被拦截,哪些动作不需要被拦截
解决办法:需要在使用拦截器的时候注入参数:
<action name="login" class="com.mangocity.web.action.Demo1Action" method="login"> <interceptor-ref name="myDefaultStack"> <param name="demo2Interceptor.excludeMethods">login</param> </interceptor-ref> <result>/success.jsp</result> </action> |
2. 文件上传
2.1 文件上传的必要前提
a. 表单的method必须是post
b. 表单的enctype取值必须是multipart/form-data
c. 提供文件选择域
2.2 文件上传示例
2.2.1 jsp页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib uri="/struts-tags" prefix="s"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>struts2文件上传</title> </head> <body> <s:form action="upload.shtml" enctype="multipart/form-data"> <s:textfield name="username" label="用户名"></s:textfield> <s:file name="photo" label="照片"></s:file> <s:submit value="上传"></s:submit> </s:form> </body> </html> |
2.2.2 动作类
public class UploadAction extends ActionSupport {
private File photo; private String photoFileName; private String photoContentType;
public String execute(){ ServletContext application=ServletActionContext.getServletContext(); String realPath=application.getRealPath("/WEB-INF/upload"); File file=new File(realPath); if(!file.exists()){ file.mkdirs(); } photo.renameTo(new File(file,photoFileName)); return null; } public File getPhoto() { return photo; } public void setPhoto(File photo) { this.photo = photo; } public String getPhotoFileName() { return photoFileName; } public void setPhotoFileName(String photoFileName) { this.photoFileName = photoFileName; } public String getPhotoContentType() { return photoContentType; } public void setPhotoContentType(String photoContentType) { this.photoContentType = photoContentType; } } |
2.2.3 配置文件
<package name="p2" extends="struts-default"> <action name="upload" class="com.mangocity.web.action.UploadAction"></action> </package> |
2.3 文件上传的配置
2.3.1 文件上传大小限制
文件上传大小的限制默认是2MB,如果上传文件超过了这个默认值,upload拦截器会转向一个input的逻辑视图。
如何修改文件上传大小的限制:
a. 在struts.xml中设置文件上传大小的常量
<constant name="struts.multipart.maxSize" value="10485760"/> |
2.3.2 限制文件上传类型
2.3.2.1 通过限制上传文件的扩展名
给fileupload拦截器注入参数
<package name="p2" extends="struts-default"> <action name="upload" class="com.mangocity.web.action.UploadAction"> <interceptor-ref name="defaultStack"> <param name="fileUpload.allowedExtensions">.jpg,.png</param> </interceptor-ref> <result name="input">/upload.jsp</result> </action> </package> |
2.3.2.2 限制上传文件的MIME类型
<package name="p2" extends="struts-default"> <action name="upload" class="com.mangocity.web.action.UploadAction"> <interceptor-ref name="defaultStack"> <param name="fileUpload.allowedTypes">image/bmp,image/pjpeg,image/jpg</param> </interceptor-ref> <result name="input">/upload.jsp</result> </action> </package> |
2.3.3 出错后的错误信息中文提示
Struts2中报错的消息是英文的,我们需要让它显示中文。
Struts2中的默认信息提示在:struts2-core.jar\org.apache.struts2\struts-message.properties,我们可以用国际化消息资源包,把对应的key改成中文即可
3. 文件下载示例
动作类:
public class DownloadAction extends ActionSupport { private InputStream inputStream; private String filename; public String download() throws FileNotFoundException{ String realpath=ServletActionContext.getServletContext().getRealPath("/WEB-INF/upload/image.jpg"); inputStream=new FileInputStream(new File(realpath)); filename="image.jpg"; return SUCCESS; } public InputStream getInputStream() { return inputStream; } public void setInputStream(InputStream inputStream) { this.inputStream = inputStream; } public String getFilename() { return filename; } public void setFilename(String filename) { this.filename = filename; } } |
配置文件:
<package name="p3" extends="struts-default"> <action name="download" class="com.mangocity.web.action.DownloadAction" method="download"> <result type="stream"> <!-- 配置输入流 --> <param name="inputName">inputStream</param> <!-- 设置响应消息头告知浏览器以下载的方式打开 --> <param name="contentDisposition">attachment;filename=${filename}</param> <!-- 设置响应消息头告知浏览器响应正文的MIME类型 --> <param name="contentType">application/octet-stream</param> </result> </action> </package> |
4. ContextMap
4.1 动作类的生命周期
动作类是多例的,每次动作访问,动作类都会实例化,所以是线程安全的。
在每次动作执行前,核心控制器StrutsPrepareAndExecuteFilter都会创建一个ActionContext和ValueStack对象,且每次动作类访问都会创建。这两个对象存储了整个动作访问期间用到的数据,并且把数据绑定到了线程局部变量ThreadLocal上了,所以线程是安全的。
4.2 ContextMap中存放的内容
contextMap中存放的主要内容 |
||
Key |
Value |
说明 |
value stack (root) |
java.util.List |
没有root这个key。它是一个list。 |
application |
java.util.Map<String,Object> |
ServletContext中的所有属性。 |
session |
java.util.Map<String,Object> |
HttpSession中的所有属性。 |
request |
java.util.Map<String,Object> |
ServletRequest中的所有属性。 |
parameters |
java.util.Map |
参数 |
attr |
java.util.Map |
把页面、请求、会话、应用范围内的所有属性放到一起。 |
注意:除了Valuestack之外,全是map,其实就是Map中又封装的Map。查看contextMap中的数据在页面上使用<s:debug>
4.3 ContextMap中的数据操作
4.3.1 存数据
4.3.1.1 利用ActionContext存数据
//获取ContextMap的对象引用 ActionContext context=ActionContext.getContext(); //向contextMap中存入数据 context.put("contextMap","hello contextMap"); //向HttpSession域中存入数据的两种方式 //第一种:使用contextMap中的session Map<String,Object> sessionMap=context.getSession(); sessionMap.put("sessionMap","hello sessionMap"); //第二种:使用原始HttpSession对象 HttpSession session=ServletActionContext.getRequest().getSession(); session.setAttribute("sessionMap1","hello sessionMap1"); |
4.3.1.2 利用valueStack存数据
4.3.1.2.1 获取valueStack的三种方式
//第一种 ActionContext context=ActionContext.getContext(); Map<String,Object> requestMap=(Map<String, Object>) context.get("request"); ValueStack vs1=(ValueStack) requestMap.get("struts.valueStack");
//第二种 HttpServletRequest request=ServletActionContext.getRequest(); ValueStack vs2=(ValueStack) request.getAttribute("struts.valueStack");
//第三种 ValueStack vs3=context.getValueStack(); |
4.3.1.2.2 ValueStack中的getContext方法,获取的就是ActionContext
Map<String,Object> contextMap=vs1.getContext(); |
4.3.1.2.3 栈操作方法
ActionContext context=ActionContext.getContext(); ValueStack vs=context.getValueStack(); //1.压栈 vs.push(new Student("tom",21)); //2.setValue vs.setValue("name", "张三"); //第一个参数中没有使用#,会把ValueStack中第一个name属性,设置为张三 vs.setValue("#name", "李四"); //第一个参数中使用了#,会name作为key,李四作为value存放到contextMap中去 /*3.set(String key,Object value)方法, 如果栈顶元素是一个Map的话,直接把数据存入map中 如果栈顶不是一个map,就创建一个map,把数据存入map中,并把map压入栈顶*/ vs.set("s2",new Student("王五",30)); |
4.3.2 jsp页面获取数据
使用struts2的s:property标签
<s:property value=”name”/>会从valuestack栈顶开始获取第一个属性为name的值
<s:property value=”[1]name”/> 属性前可以用中括号传入索引,表明从valuestack的第几个元素开始找起
<s:property value=”name”/>会从contextMap中获取key为name的value值
4.3.3 ognl和el表达式获取数据的顺序问题
Struts2中对request进行了包装,当el表达式在request域中查找数据的时候,如果没有找到数据,还会去valuestack中查找,所以el表达式在struts2中查找数据顺序变为:
page Scope———>request Scope———>valueStack(根中)———>contextMap—>sessionScope———>applicationScope
ognl表达式获取数据的时候,没有写#号的前提下,如果在ValueStack中没有获取到数据,还会去contextMap中查找。
5. OGNL配合通用标签的使用
5.1 iterator标签
<s:iterator value=”students”var=”s” status=”vs”> 还有begin、end、step属性
Value: 是ognl表达式
Var:是一个字符串,如果指定了var属性,框架会把当前遍历的元素,以var值为key,放入contextMap中
如果不指定var属性,框架会把当前遍历的元素压入值栈中
Status:是一个字符串,记录着遍历数据的一些属性,以status为key,房屋contextMap中
Boolean isOdd()
Boolean isEven()
Boolean isFirst()
Boolean isLast()
Int getIndex()
Int getCount()
5.2 ognl投影:只输出符合条件的属性
5.2.1 使用过滤条件投影
<s:iterator value=”students.{?#this.age>25}status=”vs”>
a.?#:过滤所有符合条件的集合
b.^#:过滤第一个符合条件的元素
c.$#:过滤最后一个符合条件的元素
5.2.2 投影指定属性
<s:iterator value=”students.{name} status=”vs”>
只把students的name属性的值放入值栈的栈顶
5.3 struts2中#,$,%符号的使用
5.3.1 #
a. 取contextMap中key时使用
b. Ognl中创建Map对象时使用,<s:radiolist=”#{‘male’:’男’,’female’:女}
5.3.2 $
a. 在jsp页面使用EL表达式时使用
b. 在xml配置文件中,编写ognl表达式时使用
5.3.3 %
在struts2中有一部分标签value属性的取值是普通字符串,如果想把一个普通的字符串强制看成ognl表达式,就需要使用%{}把字符串套起来
5.4 其他标签
5.4.1 set标签
<s:set value=”abc” var=”str” scope=”session”/>
Value:存入map中属性的值,是一个ognl表达式
Var:存入map中属性的key
Scope:存入的范围,取值有application、session、request、page和action,如果不写,默认是action,它是在contextMap和request范围内各存一份。
5.4.2 action标签
<s:action name=”action1” executeResult=”true”/>
Name:动作名称
executeResult:是否执行动作,默认是false
5.4.3 if ifelse else标签
<s:set value=”’C’” var=”level” scope=”action”/>
<s:if test=”#level==’A’”>level A</s:if>
<s:ifelse test=”#level==’B’”>level B</s:ifelse>
<s:else>level C</s:else>
5.4.4 url和a标签
<s:url action=”action1” var=”url value=”action1”>
<paramname=”name” value=”tom”/>
</s:url>
S:url就是创建一个地址
Value属性:输出的是value的值,是一个普通字符串
Action属性:输出的action1的请求地址,它可以随着配置文件中的扩展名改变而改变。
Var属性:会把action的值存到contextMap中
<s:a action=”action1”>
<paramname=”name” value=”tom”/>
</s:a>
6. 防止表单重复提交
6.1 使用重定向
<result type=”redirect”>/success.jsp</result>
6.2 使用<s:token/>生成令牌配合token拦截器
页面的form表单中使用<s:token/>
配置文件中使用拦截器:
<interceptor-ref name=”token”/>
重复提交会返回invalid.token试图
6.3 使用<s:token/>生成令牌配合tokensession拦截器
页面的form表单中使用<s:token/>
配置文件中使用拦截器:
<interceptor-ref name=”tokenSession”/>
这样只会处理第一次请求,当重复提交时,不会再处理
7. Struts2中配置主题
<constant name=”struts.ui.theme” value=”simple”/>