Struts2框架详解第三课

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”/>