zoukankan      html  css  js  c++  java
  • struts2框架学习之第三天

                                          day03

    上传下载

    1        上传下载组件介绍

    l  jspSmartUpload(model1的年代);

    l  apache-commons-fileupload,Struts2默认上传组件;

    l  Servlet3.0使用的Part,但Servlet3.0还没有普及;

    l  COS,Struts2支持,不过已经停止更新很久了;

    l  pell,Struts2支持。

    2        fileUpload的拦截器

    Struts2默认使用的是commons-fileUpload组件完成上传的,使用Struts2会大量简化上传文件的开发。这一工作由fileUpload拦截器来完成。它会查看当前请求的enctype是否为multipart/form-data,如果不是就会直接“放行”;如果是,那么它会去解析表单,然后把解析的结果传递给Action的属性!

      fileUpload拦截器对会对Action提供很大的“帮助”,同时它也会对Action提出一些“小小的要求”。Action需要提供3个属性:

    l  File fieldName

    l  String fieldNameContentType

    l  String fieldNameFileName;

    三个属性的前缀都(fieldName)必须与文件表单项名称一致,例如有文件表单项内容为:<input type=”file” name=”myUpload”/>,其中表单项名称为:myUpload,那么Action就必须要有如下3个属性:

    l  private File myUpload

    l  private String myUploadContentType

    l  private String myUploadFileName

    3 演示上传

    上传文件首先我们需要给出一个页面,页面中要有一个表单:

    upload.jsp

        <form action="<c:url value='/UploadAction.action'/>" method="post" enctype="multipart/form-data">

        用户名: <input type="text" name="username"/><br/>

        文 件:<input type="file" name="myUpload[崔1] "/><br/>

            <input type="submit" value="Submit"/>

        </form>

    UploadAction

    public class UploadAction extends ActionSupport {

        private String username;

       

        public void setUsername(String username) {

           this.username = username;

        }

     

        private File myUpload;

        private String myUploadContentType;

        private String myUploadFileName;

    [崔2]    private String savepath = "/WEB-INF/uploads";

     

        public void setMyUpload(File myUpload) {

           this.myUpload = myUpload;

        }

     

        public void setMyUploadContentType(String myUploadContentType) {

           this.myUploadContentType = myUploadContentType;

        }

     

        public void setMyUploadFileName(String myUploadFileName) {

           this.myUploadFileName = myUploadFileName;

        }

     

        public String execute() throws Exception {

           System.out.println(username);

           System.out.println(this.myUploadContentType);

           System.out.println(this.myUploadFileName);

           System.out.println(this.myUpload.getAbsolutePath());

           this.savepath = ServletActionContext.getServletContext().getRealPath(savepath);

           File destFile = new File(savepath, myUploadFileName);

           FileUtils.copyFile(myUpload, destFile);

           return NONE;

        }

    }

    struts.xml

        <package name="s8" namespace="/" extends="struts-default">

           <action name="UploadAction" class="cn.itcast.upload.action.UploadAction">

           </action>

        </package>

    4 上传配置

    可以通过Struts2的常量来完成对上传的配置,下面是与上传相关的常量:

    l  struts.multipart.parser:指定使用的上传组件,默认值为jakarta,表示使用commons-fileupload组件,Struts2还支持cos和pell;

    l  struts.multipart.saveDir:临时目录,如果没有指定临时目录,那么临时文件会在Tomcat的work目录中;

    l  struts.multipart.maxSize:整个大小限制,默认值为2097152,即2M。注意,这个限制是整个请求的大小,而不是单一文件的大小。

    当上传的表单超出了限制时,拦截器会向actionError中添加错误信息!当执行wokflow拦截器时,会发现actionError中存在错误,这时就会跳转到input视图,所以我们需要为Action指定input视图。

    fileUpload拦截器也有3个参数,我们可以给fileUpload拦截器配置这3个参数:

    l  maximumSize:上传的单个文件的大小限制;

    l  allowedTypes:允许上传文件的类型,多个类型以逗号隔开;

    l  allowedExtensions:允许上传文件的扩展名,多个扩展名以逗号隔开;

    <struts>

        <constant name="struts.devMode" value="true" />

        <constant name="struts.multipart.maxSize" value="1048576[崔3] " />

        <package name="s8" namespace="/" extends="struts-default">

           <action name="UploadAction" class="cn.itcast.upload.action.UploadAction">

               <result name="input">/demo1/upload.jsp</result>

               <param name="savepath">/WEB-INF/uploads</param>

               <interceptor-ref name="defaultStack[崔4] ">

                  <!-- 限制单个文件大小上限为512K -->

                  <param name="fileUpload.maximumSize">524288[崔5] </param>

                  <param name="fileUpload.allowedExtensions">jpg,png,bmp[崔6] </param>

               </interceptor-ref>

           </action>

        </package>

    </struts>

    5 上传相关的错误信息国际化

    在上传文件时如果出现错误,那么在input视图显示的错误信息都是英文的。如果想替换这些信息,需要知道这些错误信息的资源key,然后在我们自己的国际化资源文件中指定这些key的新值即可。

    与上传相关的错误信息都在org.apache.struts2包下的struts-message.properties文件中。

    struts.messages.error.uploading[崔7] =Error uploading: {0}

    struts.messages.error.file.too.large[崔8] =The file is to large to be uploaded: {0} "{1}" "{2}" {3}

    struts.messages.error.content.type.not.allowed[崔9] =Content-Type not allowed: {0} "{1}" "{2}" {3}

    struts.messages.error.file.extension.not.allowed[崔10] =File extension not allowed: {0} "{1}" "{2}" {3}

    struts.messages.upload.error.SizeLimitExceededException[崔11] =Request exceeded allowed size limit! Max size allowed is: {0} but request was: {1}!

      我们可以在src下res.properties文件,在这个文件中对象以上资源key进行替换。然后在struts.xml文件中给出<constant name="struts.custom.i18n.resources" value="res/">即可。

    6 多文件上传

    当需要上传多个文件时,如果每个<input type=”file”>对应Action的3个属性,那么这会使Action的属性过多的现象。处理这一问题的方法是在表单中设置所有<input type=”file”>的名称为相同名称,然后在Action中给出数组属性即可。

    uploads.jsp

        <s:fielderror />

        <s:actionerror/>

        <form action="<c:url value='/UploadsAction.action'/>" method="post" enctype="multipart/form-data">

        用户名: <input type="text" name="username"/><br/>

        文 件1:<input type="file" name="myUpload"/><br/>

        文 件2:<input type="file" name="myUpload"/><br/>

        文 件3:<input type="file" name="myUpload"/><br/>

    [崔12]         <input type="submit" value="Submit"/>

        </form>

    UploadsAction

    public class UploadsAction extends ActionSupport {

        private File[] myUpload;

        private String[] myUploadContentType;

        private String[] myUploadFileName;

    [崔13]    private String savepath;

        public void setSavepath(String savepath) {

           this.savepath = savepath;

        }

        public void setMyUpload(File[] myUpload) {

           this.myUpload = myUpload;

        }

        public void setMyUploadContentType(String[] myUploadContentType) {

           this.myUploadContentType = myUploadContentType;

        }

        public void setMyUploadFileName(String[] myUploadFileName) {

           this.myUploadFileName = myUploadFileName;

        }

        public String execute() throws Exception {

           savepath = ServletActionContext.getServletContext().getRealPath(savepath);

           for(int i = 0; i < myUpload.length; i++) {

               System.out.println("文件名:" + myUploadFileName[i]);

               System.out.println("文件类型:" + myUploadContentType[i]);

               FileUtils.copyFile(myUpload[i], new File(savepath, myUploadFileName[i]));

           }

           return NONE;

        }

    }

    其中Struts2也允许我们使用List类型来代替数组属性。

    UploadsAction.java

    public class UploadsAction extends ActionSupport {

        private List<File> myUpload;

        private List<String> myUploadContentType;

        private List<String> myUploadFileName;

    }

    文件下载

    1 回忆JavaWeb文件下载

    其实也可以让用户通过浏览器直接访问下载资源,但这样程序无法控制下载流程了。所以在JavaWeb中通过Servlet完成下载。

    下载是与响应相关的操作,在JavaWeb中下载要做如下操作:

    l  设置Content-type响应头,其实这个头你不设置浏览器也会自己通过文件名来识别;

    l  设置Disposition响应头,它默认值是inline,表示在页面中打开,下载时应该设置为attachment,表示让浏览器弹出下载框;

    l  处理文件中乱码问题;

    l  获取被下载文件,把被下载文件的内容写入到response.getOutputStream()流中;

    2 Struts2结果类型之stream

    在Struts2中,所有与响应相关的工作都由<result>来处理。我们知道,<result>的默认类型为dispatcher,表示请求转发到JSP,如果设置<result>的type为redirect,那么表示重定向。也就是说所有和响应相关的事情都交给<result>元素指定的类来处理。

    Struts2专门提供了一个stream类型的Result,它用来完成下载。也就是说在下载时我们的Action中给出的<result>的type类型应该指定为stream。stream结果对应的结果类型为StreamResult类!这个类来完成设置响应头,以及向response.getOutputStream()中写入数据。

    StreamResult类对Content-type提供的默认值为text/plain,这不是我们想要的,所以我们需要给StreamResult设置新的Cotnent-type。

    StreamResult类对disposition头提供的默认值为inline,这也不是我们想要的,所以我们也需要给它设置为attachment。

    在StreamResult类中提供了两个属性:contentType和contentDisposition,我们需要给这两个属性赋值,来替换默认值。

    StreamResult类还要求Action提供一个getInputStream()方法,它会通过这个方法来获取一个InputStream对象,它会把这个流中的内容写入到response.getOutputStream()中。

    dowload.jsp

        <a href="<c:url value='/DownloadAction.action'/>">我的拍子</a>

    DownloadAction

    public class DownloadAction extends ActionSupport {

        public InputStream getInputStream[崔14] () throws FileNotFoundException {

           return new FileInputStream("F:\我的拍子.jpg");

        }

        public String execute() throws Exception {

           return SUCCESS;

        }

    }

    struts.xml

    <action name="DownloadAction" class="cn.itcast.download.action.DownloadAction">

        <result type="stream">

           <param name="contentType">image/jpeg</param>[崔15] 

           <param name="contentDisposition">attachment;filename=a.jpg</param>[崔16] 

        </result>

    </action>

     

    3 OGNL表达式获取Action属性值

      上例中要下载的文件是硬编码,文件的ContentType类型是硬编码,下载框中的文件名也是硬编码,这些都是需要处理的。

           <action name="Download1Action" class="cn.itcast.download.action.Download1Action">

               <param name="downloadDir">/WEB-INF/downloads</param>

               <result type="stream" name="success">

                  <param name="contentType">${contentType}[崔17] </param>

                  <param name="contentDisposition">attachment;filename=${filename}[崔18] </param>

               </result>

           </action>

    Download1Action

    public class Download1Action extends ActionSupport {

        private String downloadDir;

        private InputStream inputStream;

        private String filename;

       

        public void setDownloadDir[崔19] (String downloadDir) {

           this.downloadDir = ServletActionContext.getServletContext().getRealPath(downloadDir)[崔20] ;

        }

     

        public void setFilename[崔21] (String filename) throws UnsupportedEncodingException {

           // 处理GET请求中文编码问题

           this.filename = new String(filename.getBytes("iso-8859-1"), "utf-8")[崔22] ;

        }

     

        public String getFilename[崔23] () throws UnsupportedEncodingException {

           // 在下载框中显示的文件名需要处理编码问题

           return new String(filename.getBytes("GBK"), "iso-8859-1")[崔24] ;

        }

       

        public String getContentType[崔25] () {

           // 文件的MIME类型

           return ServletActionContext.getServletContext().getMimeType(filename);[崔26] 

        }

     

        public InputStream getInputStream[崔27] () throws FileNotFoundException {

           return this.inputStream;

        }

     

        public String execute() throws Exception {

           File file = new File(downloadDir, filename);[崔28] 

           inputStream = new FileInputStream(file);

           return SUCCESS;

        }

    }

    OGNL表达式

    1 什么是OGNL语言

    你还记得 EL表达式语言么?没错OGNL也是表达式语言,不过它比EL可强大太多了。OGNL的全称为Object Graphic Navigation Language(对象图导航语言)。它是Struts2的默认表达式语言!

    使用OGNL需要导入OGNL的Jar包:ognl-3.0.5.jar

    OGNL的功能介绍:

    l  EL一样的JavaBean导航;

    l  调用对象方法;

    l  调用类的静态方法;

    l  索引数组元素;

    l  操作集合;

    1. EL可以读取JavaBean属性,OGNL使用起来与EL表达式有点相似
    2. EL操作的数据是域对象,OGNL操作的数据OgnlContext,可以在OgnlContext指定一个root对象,在不指定操作的对象时默认为root对象。
    2 OGNL入门

    为了可以执行OGNL表达式,所以我们必须要先学会怎么来使用Ognl类。为了更好的演示Ognl我们需要先创建一些JavaBean用来做测试。

    public class Address {

        private String country;

        private String city;

        private String street;

    ……

    }

    public class Employee {

        private String name;

        private double salary;

        private Address address;

    ……

    }

    我们可以回忆一下EL的使用:${name},它会在所有域中去查找name这个域属性。也就是说,EL表达式也需要一个查询的范围。相同的道理,OGNL也需要一个查找的范围,它查询的范围是OgnlContext。OgnlContext其实就是一个Map,你可以向其内添加N个键值对。它还可以指定多个键值中的一个为根对象(root)!

           OgnlContext cxt = new OgnlContext();[崔29] 

           Address add1 = new Address("中国", "北京", "大北窑");

           Employee e1 = new Employee("张三", 10000, add1);

           Address add2 = new Address("中国", "北京", "西三旗");

           Employee e2 = new Employee("李四", 12000, add2);    

           cxt.put("emp1", e1);

           cxt.put("emp2", e2);

    [崔30]       cxt.setRoot(e1);[崔31] 

    演示OGNL表达式需要使用Ognl类,我们先从它的getValue()方法开始。这个方法需要三个参数,分别是:

    l  OGNL表达式;

    l  上下文对象;

    l  根对象。

           String name = (String)Ognl.getValue("#emp2.name", cxt, cxt.getRoot()[崔32] );

           System.out.println(name)[崔33] ;

    其中“#emp2.name”是OGNL表达式,查找上下文中的元素必须以“#”开关,其中emp2对应上下文中的键,“.name”表示调用该元素的getName()方法,即JavaBean导航。如果它这个OGNL表达式翻译成Java代码,即:((Employee)cxt.get(“emp2”)).getName()。

           String name = (String)Ognl.getValue("name", cxt, cxt.getRoot());

           System.out.println(name);

    其中“name”为OGNL表达式,当没有使用“#”开始时,表达在使用根对象。所以“name”表达式可以理解为:cxt.getRoot().getName()。即输出“张三”。

           String name = (String)Ognl.getValue("address.street", cxt, cxt.getRoot());

           System.out.println(name);

      因为没有使用“#”开头,那么就是在使用根对象,即cxt.getRoot().getAddress().getStreet(),即输出“大北窑”。

    1. OgnlContext就是一个Map,指定操作的对象时需要使用“#”为前缀,后缀为Map的key为后缀;
    2. 当没有使用“#”时,表示操作的对象为root对象。
    3 OGNL常量

    OGNL也可以使用常量:

    l  字符串常量:使用双引号,或单引号。如果字符串只有唯一字符时,必须使用双引号;

    l  字符常量:使用单引号;

    l  数值常量:与Java相同;

    l  boolean常量:与Java相同。

    l  OGNL也支持null常量。

    1. “hello”是字符串常量,’hello’也是字符串常量,’h’是字符常量;
    2. true、false为boolean类型常量;
    3. 1.0、100为数值类型常量;
    4. null也是常量。
    4 运算符

    OGNL运算符基本与Java相同,但它还有一些特殊的运算符:

    l  逗号(,):用逗号分隔多个OGNL表达式,这些表达式会依次被执行,而最后一个表达式的值为整个表达式的值;

    l  大括号({}):用来创建列表,例如:{‘zhangSan’,’liSi’,’wangWu’}表示一个List;

    l  in和not in:in表示判断某个值是否在集合中,not in表达某个值是否不在集合中。

           double s = (Double)Ognl.getValue("name,#emp2.name,salary", cxt, cxt.getRoot());

           System.out.println(s);

      上面使用了逗号表达式,一共是3个OGNL表达式,它们会被依次运行,但整个整句表达式的值为最后一个OGNL表达式的值,即salary的值。

           boolean bool = (Boolean)Ognl.getValue("name in {'张三','李四'}", cxt, cxt.getRoot());

           System.out.println(bool);

      表达式“name in {‘张三’,’李四’}”首先需要得到name的结果,即根对象的name属性值,然后判断这个值是否在列表中存在,所以返回为true。

    1. 逗号运算符很少使用,它的作用是把多个OGNL表达式连接成一个OGNL表达式,每个子表达式会从左到右依次运行,完整表达式的值来最右边表达式的值;
    2. in和not in,用于判断某个值在集合中是否存在;
    3. {}可以用来创建List集合。
    4 设置JavaBean属性值(重点)

    OGNL表达式不只是可以获取JavaBean属性值,不可以设置JavaBean属性值。但这需要使用Ognl类的setValue()方法。

           Ognl.setValue("#emp2.address.street", cxt, cxt.getRoot(), "育新");

           System.out.println(Ognl.getValue("#emp2.address.street",cxt, cxt.getRoot()));

    表达式指定的是cxt中键为emp2的对象,然后导航到它的address属性,再导航到street属性,最终给该属性设置值为“育新”。

    1. “name”表示root对象的name属性;
    2. “address.city”表示root对象的address属性的city属性;
    3. “#emp.salary”表示上下文中key为emp的对象的salary属性。
    5 调用对象方法

    OGNL不只可以获取JavaBean属性,还可以调用对象方法。

           String s = (String)Ognl.getValue("'OGNL'.toLowerCase()", cxt, cxt.getRoot());

           System.out.println(s);

    表达式“’OGNL’.toLowerCase()”表示调用字符串OGNL的toLowerCase()方法,千万要小心,在OGNL上添加单引或双引,指定其为字符串常量,不然会被误会来根对象的属性的。

    当然,不只是可以调用字符串的方法,什么对象的方法都可以调用。

    6 静态方法和静态属性

    OGNL还可以调用类的静态方法,以及读取静态属性。操作静态方法或属性时,需要使用完整的类名称,并且需要使用两个“@”把类名称包含起来!

           String s = (String) Ognl.getValue(

                  "@java.lang.String@format('%tF %<tT', new java.util.Date())",

                  cxt, cxt.getRoot());

           System.out.println(s);

    上面的表达式可以翻译成:java.lang.String.format(“%tF %<tT”, new java.util.Date())

    OGNL对java.lang.Math对有特殊的待遇,如果是操作Math类的静态属性或静态方法,可以不用给出类名称,但“@”不可以省略。

           int max = (Integer)Ognl.getValue("@@max(1,2)", cxt, cxt.getRoot());

           System.out.println(max);

           double pi = (Double)Ognl.getValue("@@PI", cxt, cxt.getRoot());

           System.out.println(pi);

    7 操作数组

      OGNL可以用来操作数组,使用下标法即可:

           String[] names = {"zhangSan", "liSi", "wangWu"};

           cxt.put("ns", names);

           String name = (String)Ognl.getValue("#ns[0]", cxt, cxt.getRoot());

           System.out.println(name);

    8 操作集合

    OGNL可以用来创建List和Map,先来创建一个List它。

           List<String> names = (List<String>)Ognl.getValue("{'zhangSan', 'liSi', 'wangWu'}", cxt, cxt.getRoot());

           System.out.println(names);

    这段代码创建一个List,类型为ArrayList。

    创建Map必须以“#”开头

           Map<String,String> map = (Map<String, String>)Ognl.getValue("#{'a':'A','b':'B'}", cxt, cxt.getRoot());

           System.out.println(map);

    创建的Map默认类型为HashMap类型。还可以创建Map时指定Map的类型:

           Map<String, String> map = (Map<String, String>) Ognl.getValue(

                  "#@java.util.LinkedHashMap@{'aa':'AA','bb':'BB'}", cxt,

                  cxt.getRoot());

           System.out.println(map);

    获取Map的值。

           String s = (String) Ognl.getValue(

                  "#@java.util.LinkedHashMap@{'aa':'AA','bb':'BB'}['bb']", cxt,

                  cxt.getRoot());

           System.out.println(s);

    9 投影和选择

    我们先来聊聊投影,如果有一个Employee的集合,可以通过投影获取所有的Employee的name属性值。这与数据库操作的查询指定列相似。

    List<Employee> list = new ArrayList<Employee>();

    list.add(new Employee("zhangSan", 10000));

    list.add(new Employee("liSi", 11000));

    list.add(new Employee("wangWu", 12000));

    cxt.put("empList", list);

    List<String> nameList = (List<String>) Ognl.getValue("#empList.{name}", cxt, cxt.getRoot());

    System.out.println(nameList);

    获取所有Employee的name属性。这相当于循环遍历list集合,然后调用每个Employee对象的getName()方法。

    选择是给出一个条件,获取集合中满足条件的元素。这相当与select * from xxx where con例如我们要获取salary > 10000的Employee对象。

           List<Employee> list = new ArrayList<Employee>();

           list.add(new Employee("zhangSan", 10000));

           list.add(new Employee("liSi", 11000));

           list.add(new Employee("wangWu", 12000));

           cxt.put("empList", list);

           List<Employee> empList = (List<Employee>) Ognl.getValue(

                  "#empList.{?salary > 10000}", cxt, cxt.getRoot());

           System.out.println(empList);

      其中“{?salary > 10000}”中的“?”表示所有满足条件的元素都查询出来。还可以使用“^”或“$”,其中“^”表示查询第一个满足条件的元素,而“$”表示查询最后一个满足条件的元素。

    Struts2中的OGNL

    1 Struts2中OGNL操作的上下文和根对象

    l  上下文对象是ActionContext;

    l  根对象是ValueStack,它是一个栈结构,提供了压栈和弹栈等方法,通常栈顶元素是当前方法的Action对象;

    l  每个请求都有自己的ActionContext,每个ActionContext中都有自己的ValueStack;

    Struts2中的上下文对象就是ActionContext对象,而根对象是ValueStack对象。但Struts2中根对象ValueStack很特殊,它不是一个对象,而是一组对象(底层是ArrayList的栈结构)。你也可以理解为Struts2的根对象不是一个对象,而是一堆对象。

    可以通过ActionContext来获取ValueStack:ActionContext.getContext().getValueStack(),也可以从request中获取ValueStack:request.getAttribute(“struts.valueStack”)。

    ActionContext内部的数据:

    l  请求参数和域属性:

    • parameters:Map类型,对应HttpServletRequest中的请求参数;
    • request:Map类型,对应HttpServletRequest中的属性;
    • session:Map类型,对应HttpSession中的属性;
    • application:Map类型,对应ServletContext中的属性;
    • attr:Map类型,对应request、session、application三个域,即全域查询的Map;

    l  域对象:

    • com.opensymphony.xwork2.dispatcher.HttpServletRequest:HttpServletRequest对象
    • com.opensymphony.xwork2.dispatcher.HttpServletResponse:HttpServletResposne对象
    • com.opensymphony.xwork2.dispatcher.ServletContext:ServletContext对象;

    l  其他信息:

    • com.opensymphony.xwork2.ActionContext.name:<action>的name属性值;
    • action:当前Action对象;
    • com.opensymphony.xwork2.util.ValueStack.ValueStack:ValueStack对象;
    • com.opensymphony.xwork2.ActionContext.locale:当前的Locale。

    ValueStack内部的数据:

    l  Action对象;

    l  模型对象(ModelDriven)。

      不同的请求对应不同的ValueStack,通过在请求一个Action时,都会把当前Action的引用放到ValueStack中。一般情况下,当前Action就在ValueStack的栈顶!

    2 Struts2标签使用OGNL表达式

    l  使用<s:property>标签可以执行OGNL表达式,例如:<s:property value=”name”/>;

    l  当OGNL访问的是根对象时(例如OGNL表达式name),Struts2会把ValueStack中每个元素当成根对象,从栈顶依次向下查找,直到找到某个元素有name属性。

    Struts2中有很多标签都支持OGNL表达式,我们也知道OGNL表达式访问的数据是在OgnlContext中。在Struts2中上下文对象就是ActionContext,其中根对象就是ValueStack。

    例如<s:property value=”name”/>这个标签的value属性值name就必须是一个OGNL表达式。我们知道,没有使用“#”为前缀的OGNL表达式表示是在获取根对象的name属性值,但在Struts2中根对象是ValueStack,它不是一个对象,而是一组对象,一个栈!Struts2的根对象特殊之处是,它会在ValueStack中从栈顶开始,直接到栈尾遍历每个栈中的对象,查看是否有name属性,如果找到栈中某个对象存在name属性,那么就不会再向下去找!也就是说,如果栈顶元素就有name属性,那么就算第二个元素也有name属性,那么输出的也是栈顶元素的name属性值。

    前面我们也说过了,当访问一个Action后,Struts2会把当前Action对象压入到值栈中,即栈顶位置上(当然我们也可以调用ValueStack的push()方法来向其压入对象)。然后在Action跳转的JSP页面中使用<s:property>标签来获取值栈中元素的属性值。

    public class OgnlDemo1Action extends ActionSupport {

        private String name;

        public String getName() {

           return name;

        }

        public void setName(String name) {

           this.name = name;

        }

        @Override

        public String execute() throws Exception {

           return SUCCESS;

        }

    }

           <action name="demo1" class="cn.itcast.action.OgnlDemo1Action">

               <result>/index.jsp</result>

           </action>

    http://localhost:8080/ognl/demo1.action?name=zhangSan

    <s:property value="name"/>

    当我们在浏览器中访问http://localhost:8080/ognl/demo1.action?name=zhangSan时,给Action的属性name赋值为zhangSan,然后Action跳转到index.jsp页面中,在index.jsp页面中我们使用<s:property>标签打印name属性值。因为“name”是一个OGNL表达式,它表示访问根对象的name属性,即调用根对象的getName()方法!但我们不要忘了,在Struts2中,这个动作表示在ValueStack中从上而下,依次查看每个元素是否有getName()方法,直到找到第一个有getName()方法的元素,然后返回它getName()方法的结果,才会结束。

    因为刚刚访问了Action,把当前Action对象会被压入到ValueStack中,它是在ValueStack的栈顶,所以其实就是在获取当前Action的getName()。

    我们知道,还可以通过OGNL表达式访问上下文中其他的对象,但这需要以“#”为前缀。例如可以访问parameters、request、session、application、attr这些Map对象。

    <s:property value="#parameters.password"/><br/>

    <s:property value="#request.hello"/><br/>

    在ActionContext中存放着很多个Map:

    key

    value

    request

    Map类型,对应request中的属性,但它不是request对象本身

    session

    Map类型,对应session中的属性

    application

    Map类型,对应application中的属性

    parameters

    Map类型,对应request中请求参数

    attr

    Map类型,对应所有域对象,依次查找。

    #request表示一个Map,而#request.hello表示获取#request这个Map中key为hello的值。当然,如果你只有request.setAttribute(“hello”, “xxx”)过,才能获取到值。

    3 [N]语法

    [N]语法用来获取子栈,例如[3]表示截取从下标3位置开始到栈底的一个子栈。例如[3].name表示从[3]子栈中查找每个元素是否存在name属性,如果下标3元素就存在name属性,那么直接返回即可。

    例如我们向ValueStack中压入两个对象:

    public class OgnlDemo1Action extends ActionSupport {

        private String name;

        public String getName() {

           return name;

        }

        public void setName(String name) {

           this.name = name;

        }

        @Override

        public String execute[崔34] () throws Exception {

           Emp emp = new Emp();

           emp.setName("emp_zhangSan");

           Student stu = new Student();

           stu.setName("stu_liSi");

          

           ValueStack vs = ActionContext.getContext().getValueStack();

           vs.push(emp);

           vs.push(stu);

           return SUCCESS;

        }

    }

    <s:property value="[0].name[崔35] "/><br/>

    <s:property value="[1].name[崔36] "/><br/>

    <s:property value="[2].name[崔37] "/><br/>

      千万注意,[1]不是表示下标1的元素,而是一个子栈。

    4 top关键字

    top语法用来获取栈顶元素本身。例如还是上面的例子,值栈中从上到下依次是Student、Emp、Action三个对象,那么top表示的就是Student对象本身。而[1].top表示获取[1]子栈的栈顶元素,即Emp对象。

    <s:property value="[0].top[崔38] "/><br/>

    <s:property value="[1].top"/><br/>

    <s:property value="[2].top"/><br/>

    5 访问静态成员

    我们知道OGNL可以访问类的静态方法和属性,但在Struts2中默认是不允许OGNL访问静态成员的,如果想访问类的静态成员,需要设置一个常量:struts.ognl.allowStaticMethodAccess,该常量默认值为false,我们需要把它设置为true,这样就可以访问静态成员了。

    <constant name="struts.ognl.allowStaticMethodAccess" value="true"/>

    <s:property value="@@min(10,20)[崔39] "/><br/>

    除了使用标准的OGNL表达式访问静态属性和静态方法外,Struts2还允许你不指定完整的类名,而是通过“vs”前缀来调用保存在值栈中对象的静态属性和静态方法。

    例如:@vs@hello(),表示调用栈顶元素的静态方法hello(),如果栈顶元素是MyAction类型的对象,那么@vs@hello()就表示MyAction.hello()!

    还可以指定值栈中元素序号来调用静态成员,例如@vs1@hello()表示调用的是值栈中第一个元素的静态方法,其实它与@vs@hello()是相同的。因为不存在@vs0@,序号是从1开始的。@vs2@hello()表示调用值栈中第2个元素的静态方法hello()。

    6 模型驱动与ValueStack

    我们知道,Struts2会把当前Action对象压入到值栈中,所以我们在JSP页面中总是可以直接使用OGNL来访问Action元素的属性:<s:property value=”username”/>,如果我们没有自己向值栈中压入其他元素,那么Action就是栈顶元素了,所以上面的标签输出的就是当前Action的username属性值。

    当Action使用模型驱动时,ModelDrivenInterceptor拦截器会把当前model对象压入值栈,这时值栈中model对象就是栈顶元素,所以<s:property value=”username”/>访问的是model对象的username属性。

    7 EL表达式访问值栈

    让人感到奇怪的是,可以通过EL表达式访问到值栈。例如在JSP页面中使用${name},但在域对象中没有name这个域属性,奇怪的是它会去到值栈中找到元素的name属性值。

    这是因为Struts2对request对象进行了装饰,这个装饰类是StrutsRequestWrapper,它对getAttribute()方法进行了增强,它会先到域对象中查找名为name的属性值,但如果没有找到它会去到ValueStack中去找,所以EL才会有这个本事。

    8 Struts2 OGNL中的#、%、$

    在Struts2的标签中,有些标签可以直接使用OGNL表达式,但有些是不能直接使用的。例如我们使用过的<s:property>标签的value属性就是默认为OGNL表达式的。但<s:url>标签的action属性默认是字符串,而不是OGNL表达式。例如<s:url action=”demo1”/>会在页面中输出/day03/damo1.action。如果你想在<s:url>标签的action属性中使用OGNL,那么需要使用%{},这样Struts2就知道你给出的值是一个OGNL表达式了,例如:<s:url action=”%{#request.url}”/>,其中#request.url会被Struts2当做OGNL表达式来解析!

    当然,你也可以在<s:property>标签的value属性中使用%{},但对于默认就是OGNL表达式的属性而言,添加%{}是没有意义的:<s:property value=”%{name}”/>

    对于OGNL表达式中的“#”,表示的是访问上下文中的非root对象时需要使用以“#”开头。这个应该没什么问题吧。

    ${}是在国际化资源文件中用来使用OGNL表达式用的,${}还可以在struts.xml文件用来使用OGNL表达式。

    例如在res.properties文件中:msg:我的名称叫${#request.name},然后在Action或JSP中就可以使用它了。

                 <s:i18n name="res">

                                <s:text name="msg"></s:text>

                       </s:i18n>

    例如在struts.xml文件中使用:

    <result type=”stream”>

      <param name=”contentType”>${type}</param>

    </result>

    其中${type}表示OGNL表达式,即调用当前Action的getType()方法(因为Action在值栈的栈顶位置)。

    9 从Action中向页面传递数据

    1.  一般性数据传递

    每个Action都有自己保存信息的地方,这是从ActionSupport类继承而来的!

    l  ActionSupport#addFieldError(“字段名”, “字段错误信息内容”):它会把信息添加到Map<String,List>中;

    l  ActionSupport#addActionError(“Action错误信息内容”):它会把信息添加到Collection<String>中;

    l  ActionSupport#addActionMessage(“Action通知信息内容”):它会把信息添加到Collection<String>中。

    因为Action会被压入到值栈中,所以在页面中Struts2的标签就可以来获取这些信息了。

    l  <s:fielderror fieldame=”字段名”/>:打印指定字段的所有错误信息;

    l  <s:actionerror/>:打印Action错误信息;

    l  <s:actioinmessage/>:打印Action通知信息。

      2. 复杂数据传递

    也可以把复杂类型的数据通过压入值栈来向页面传递:

    valueStack.push(new User(“zhangSan”, “123”));

    在页面中可以使用Struts2标签来获取值栈的内容:

    <s:property value=”username”/>

    <s:property value=”password”/>

    10 页面中查看ValueStack内部数据

      Struts2为开发人员提供了一个标签:<s:debug/>,它会在页面中生成一个超链接,点击链接会在页面中显示ValueStack的内部数据。

    Struts2标签

    打开struts-2.3.7docsWW ag-reference.html可以看到Struts2提供的所有标签。其中分为“通用标签”和“UI标签”两大部分。我们不可能把所有标签都介绍到,我们只会介绍其中比较有用的几个标签介绍一下。

    1 Struts2通用标签之数据标签

    l  <s:property>(重要)

    <s:property>标签用来执行OGNL表达式,最为常用的方式是使用它在页面中输出ValueStack或ActionContext中的数据。

    <s:property value=”#request.hello”/>,等于ActionContext.getContext().getRequest().get(“hello”)。

    名称

    必需

    默认值

    类型

    说明

    default

    String

    表示默认值。当结果为null时,输出默认值。

    escape

    true

    boolean

    表示是否进行转义。该值为true时,当结果中包含<、>、”、’、&时,对其进行转义。

    value

    栈顶对象

    Object

    Ognl表达式

    l  <s:set>

    <s:set>标签用来创建一个变量,保存到指定的范围内。

    <s:set var=”myVar” value=”#parameters.score[0]” scope=”page”/>,创建一个变量,保存到page范围,key为myVar,值为“#parameters.score[0]”的运算结果。

    名称

    必需

    默认值

    类型

    说明

    var

    String

    变量的域名字,默认不是Ognl表达式

    value

    栈顶对象

    Object

    Ognl表达式。

    scope

    Action

    String

    变量的范围。可选值为:application、session、request、page、action

    scope的可选值中的action是我们陌生的范围,它是scope的默认值。它表示保存到request和OgnlContext两个范围中。即比request范围多出了一个OgnlContext范围。

    <s:set var=”myVar” value=”#parameters.score[0]” />

    <s:property value=”#myVar”/>等同于ActionContext.getContext().get(“myVar”);

    <s:property value=”#request.myVar”/>等同于ActionContext.getContext.getReuqest().get(“myVar”);

    l  <s:push>

    <s:push>标签是把指定值暂时压入到值栈中,当执行完<s:push>标签后,压入到值栈中的值会被弹出。

    <s:push value=”’hello’”/>等于把hello字符串压入到值栈中后,马上又弹出了,相当于什么都没做。

    <s:push value=”#session.user”>把user压入到值栈中

      <s:property value=”username”/>打印值栈元素的username属性

      <s:property value=”password”/>打印值栈元素的password属性

    </s:push>把user从值栈中弹出

    l  <s:url>

    <s:url>标签通常用来生成action路径,它与<c:url>标签很相似。

    <s:url action=”TestAction”/>在页面中打印/contextpath/TestAction.action。也就是说它与<c:url>一样会生成全路径。而且无论给出后缀“.action”!action属性的值只需要与struts.xml文件中<action>元素的name属性值相同即可。

    <s:url action=”TestAction” namspace=”/” />还可以指定名称空间

    <s:url action=”TestAction”>

      <s:param name=”name” value=”’张三’”/>

    </s:url>

    页面中打印为:/ognl/TestAction.action?name=%E5%BC%A0%E4%B8%89

    还可以为URL指定参数,其中参数包含中文时自动使用URL编码。

    其中<s:param>是用来作为子标签的,它的作用是指定参数。它的value属性值为Ognl表达式,所以我们需要把“张三”用单引号引起来,表示Ognl表达式的字符串常量。

    名称

    必需

    默认值

    类型

    说明

    action

    String

    指定用于生成URL的action。

    value

    String

    指定用于生成URL的地址值。通常只使用action或value其中一个属性。

    var

    String

    如果指定了该属性,那么生成的URL不会输出到页面中,而是被保存到OgnlContext中。

    method

    String

    指定调用action中的方法。如果使用的是value属性,那么该属性无效。

    namespace

    String

    指定action所属的名称空间。

    forceAddSchemeHostAndPort

    false

    Boolean

    当该属性为true时,生成的URL为绝对路径,而且会包含主机名及端口号。

    l  <s:a>

    它用来生成超链接,与<s:url>相似!

    <s:a action=”TestAction” namespace=”/”>添加用户

      <s:param name=”name” value=”’张三’”/>

    </s:a>

    l  <s:debug>

    Debug标签用于调试,它在页面中生成一个“[Debug]”超链接,单击这个超链接,可以查看ValueStack和ActionContext中保存的所有对象。

    2 Struts2通用标签之控制标签

    控制标签很好理解,就是流程控制了。例如if、elseif等,以及iterator等。

    l  <s:if>、<s:elseif>、<s:else>

    这种标签大家应该一看就会用了。我们直接给出个小例子看看。

    <!—

    在浏览器中输入:http://localhost:8080/tagtest/index.jsp?score=85

    -->

     

    <s:set name="score" value="#parameters.score[0]"/>

    <s:property value="#score"/>:

    <s:if test="#score > 100 || #score < 0">

        <s:property value="'输入错误'"/>

    </s:if>

    <s:elseif test="#score >= 90">

        <s:property value="'A'" />

    </s:elseif>

    <s:elseif test="#score >= 80">

        <s:property value="'B'" />

    </s:elseif>

    <s:elseif test="#score >= 70">

        <s:property value="'C'" />

    </s:elseif>

    <s:elseif test="#score >= 60">

        <s:property value="'D'" />

    </s:elseif>

    <s:else>

        <s:property value="'E'"/>

    </s:else>

    l  <s:iterator>

    <s:iterator>标签可以用来迭代一个集合,可以迭代的集合有:Collection、Map、Enumeration、Iterator或者是数组。iterator标签在迭代过程中,会把当前对象暂时压入值栈,这样在子标签中就可以直接访问当前对象的属性(因为当前对象在栈顶),在标签体执行完毕后,位于栈顶的对象就会被删除,在循环的第二圈时,把新的当前对象再压入值栈中。

    <s:iterator value="{'zhangSan','liSi','wangWu' }">

        name: <s:property/><br/>

    </s:iterator>

    如果为<s:iterator>标签指定了var属性,那么当前对象不只是压入到了值栈中,而且还会被添加到OgnlContext中。

    <s:iterator value="{'zhangSan','liSi','wangWu' }" var="name">

        name: <s:property value="#name"/><br/>

    </s:iterator>

    名称

    必需

    默认值

    类型

    说明

    var

    String

    如果指定了该属性,那么迭代的集合中的元素将被保存到OgnlContext中,可以通过该属性的值来引用集合中的元素。该属性几乎不被使用。

    value

    Coolection、Map、Enumeration、Iterator 或数组

    指定迭代的集合。如果没有指定该属性,那么iterator标签将把位于值栈栈顶的对象放入一个新创建的List中进行迭代。

    status

    String

    如果指定了该属性,一个IteratorStatus实例将被放入到OgnlContext中,通过该实例可以获取迭代过程中的一些状态信息。

    IteratorStatus类在org.apahce.struts2.views.jsp包下。下面是对该类常用方法的介绍:

    l  public int getCount():得到当前已经迭代的元素的总数。

    l  public int getIndex():得到当前迭代的元素的索引。

    l  public boolean isEven():判断当前迭代的元素的个数是否是偶数。

    l  public boolean isOdd():判断当前迭代的元素的个数是否是奇数。

    l  public boolean isFirest():判断当前迭代的元素是否是第一个元素。

    l  public boolean isLast():判断当前迭代的元素是否是最后一个元素。

    在OGNL表达式中使用IteratorStatus类的方法时,可以直接使用:count、index、even、odd、first、last属性。

    <s:iterator value='{"one", "two", "three"}' status="status">

        <s:property value="#status.count"/>,

        <s:property value="#status.index"/>,

        <s:property value="#status.even"/>,

        <s:property value="#status.odd"/>,

        <s:property value="#status.first"/>,

        <s:property value="#status.last"/><br/>

    </s:iterator>

    <hr/>

    <s:iterator value="#{'1':'one','2':'two','3':'three','4':'four'}" status="st">

    <s:property value="key"/>:<s:property value="value"/><br/>

    </s:iterator>

    3 Struts2标签UI标签之表单标签

      Struts2的表单标签还是比较好用的,但它也存在一些败笔,例如主题这一部分就不是很灵活。所以导致开发中没有公司会使用它提供的主题。

    Struts2标签的优势:

    l  简化代码;

    l  自动数据回显;

    l  指定主题样式(说是优点,但很多人也会认为这是缺点);

    1. 表单标签入门

    我们首先使用一个登录表单来对比一下html表单和Struts2表单的区别。

    <form action="<c:url value='/user/LoginAction.action'/>" method="post">

      用户名 <input type="text" name="username"/><br/>

      密 码 <input type="password" name="password"/><br/>

      <input type="submit" value="登录"/>

    </form>

    <hr/>

    <s:form action="LoginAction" namespace="/user">

        <s:textfield name="username" label="用户名" />

        <s:password name="password" label="密 码" />

        <s:submit value="登录" />

    </s:form>

     

    <form action="/ognl/user/LoginAction.action" method="post">

      用户名 <input type="text" name="username"/><br/>

      密 码 <input type="password" name="password"/><br/>

      <input type="submit" value="登录"/>

    </form>

     

    <hr/>

     

    <form id="LoginAction" name="LoginAction" action="/ognl/user/LoginAction.action" method="post">

    <table class="wwFormTable">

    <tr>

        <td class="tdLabel"><label for="LoginAction_username" class="label">用户名:</label></td>

        <td

    ><input type="text" name="username" value="" id="LoginAction_username"/></td>

    </tr>

     

    <tr>

        <td class="tdLabel"><label for="LoginAction_password" class="label">密 码:</label></td>

        <td

    ><input type="password" name="password" id="LoginAction_password"/></td>

    </tr>

     

    <tr>

        <td colspan="2"><div align="right"><input type="submit" id="LoginAction_0" value="登录"/>

    </div></td>

    </tr>

     

    </table></form>

    通过上面生成的代码可以看来: 

    l  <s:form>

    • 通过action和namespace两部分来指定请求路径,action直接给<action>元素的name值即可,无需给出后缀“.action”;
    • method默认为post;
    • 会自动添加id属性,值与action属性值相同;
    • 整个表单会生成在<table>中。

    l  <s:textfield>

    • 对应<input type=”text”>标签;
    • 通过lable来生成<lable>标签;

    l  <s:password>

    • 对应<input type=”password”>标签;
    • 通过label来生成<lable>标签

    l  <s:submit>

    • 对应<input type=”submit”>标签;
    1. 表单主题

    我们发现,整个表单都会在<table>中生成,这也就说明无需为每个表单项添加<br/>。因为在表格中就无需再换行了。

    生成在表格中是因为<s:form>标签的theme属性的默认值为xhtml,它表示一个主题样式。这个主题样式由Freemarker模板来完成。如果你不希望使用这个xhtml主题,那么有下列3种方法来修改主题:

    l  在<s:textfield>的theme属性指定为simple,那么这个表单项就使用简单主题;

    l  在<s:form>的theme属性指定为simple,那么整个表单都使用简单主题;

    l  设置struts.ui.theme常量为simple,那么所有表单标签的默认主题都是simple。

    当表单设置为theme=”simple”之后:

    <s:form action="LoginAction" namespace="/user" theme="simple">

        <s:textfield name="username" label="用户名" />

        <s:password name="password" label="密 码" />

        <s:submit value="登录" />

    </s:form>

     
    <form id="LoginAction" name="LoginAction" action="/ognl/user/LoginAction.action" method="post">
     <input type="text" name="username" value="" id="LoginAction_username"/>
     <input type="password" name="password" id="LoginAction_password"/>
     <input type="submit" id="LoginAction_0" value="登录"/>
    </form>

      这时没有表格来格式化表单,而且也不能生成<lable>,这说明样式都要自来来设定。好处是你重新获得了自由,坏处是一切都要靠自己了。

    1. 自动回显

    我们知道,当表单提交后,再返回到表单页面后,html标签不可能帮我们回显数据。而Struts2的表单标签可以做到这一点。原因是当前Action就在值栈顶,而表单标签会从值栈中获取数据来回显。

    4 表单标签之选择性标签
    1. <s:redio>标签

    表单标签中简化比较大的标签可以不在需要写循环,还有就是自动回显。我们都知道在下拉列表、单选、复选中,手动回显是比较麻烦的事。但使用Struts2的表单标签就方便多了。

    <s:radio list="#{'male':'男','female':'女'}" name="gender"/>

     

    list指定的是一个Map,默认key为实际值,而value为显示值。

    也可以为list属性指定为list:

    <s:radio list="{'',''}" name="gender"/>

    这时实际值和显示值是相同的。

    1. <s:checkboxlist>标签

      这个东西也很好用,一下子可以显示很多复选按钮。而且自动回显!

    <s:checkboxlist list="#{'read':'看书','netplay':'上网','music':'音乐' }" name="hobby"/>

     
    1. <s:select>标签

    下拉列表与上面两个标签也一样,都是选择性的!使用它也是无需循环,无需处理循环。

    <s:select name="city" list="#{'bj':'北京','sh':'上海','gz':'广州'}"/>

     

    Struts2的秘密

    1 真实的上下文以及根对象

    在struts-2.3.7docsWWognl.html中可以看到下面的介绍。

    大意是:OGNL是对象图导航语言,OGNL的概念是处理一个Map,以及一个根对象,其中根对象的属性可以直接被引用,而其他Map中的对象需要一个“#”标记。

    该框架是我们ActionContext中设置的OGNL上下文,ValueStack是OGNL的根对象。(值栈是一组多个对象,但OGNL原来是一个单独的对象)。

     

    其实OgnlContext并不是ActonContext,而是ActionContext中包含了OgnlContext对象。ValueStatck也不是root对象,而是内部有root对象。其实在ActionContext中包含的OgnlContext对象,在ValueStack中也有它的引用。

     

    其实说ValueStack是root对象也没错,因为ValueStack提供的方法就是用来操作内部的CompoundRoot对象的,而CompoundRoot是ArrayList的子类。也就是说,CompoundRoot是用来存储数据的,而ValueStack提供的方法是用来操作数据的。

    OgnlContext才是真正的Ognl上下文,它内部包含了root对象,也保存了对request、session、application的解耦对象(Map类型)。外界不能直接访问它,而是通过ActionContext间接访问它。所以对于外界而言,ActionContext就是上下文对象。

    其实这些错综复杂的关系你可以不用去记,你还是把ValueStack看成是root对象,而ActoinContext是上下文对象即可。

     

    2 Struts2的核心控制器

    想知道Struts2的执行流程,还是看源代码比较好。

    请查看day03_res下的图片以及文件。

    防止重复提交

    1 什么是重复提交

    当用户提供一个表单后,然后使用浏览器后退后,再次提交表单!

    表单重复提交会给服务器带来访问压力!甚至会出现恶意刷票等!

    如何重复提交:

    l  转发到Action,这时地址栏中的地址是指向Action的,所以可以刷新页面来重复提交;

    l  通过浏览器后退回表单页面,再次提交;

    l  因为服务器很慢,在点击了提交后,页面还没有变化时,再次点击提交。

    2 防止重复提交的原理

    防止表单重复提交的原理就是使用令牌机制!所谓令牌机制就是在请求到达表单页面时,页面会生成令牌值(UUID),然后保存到session和表单的<input type=”hidden”>中。

    然后用户提交表单后,令牌值会发送给Action。这时Action获取session中的令牌值与请求参数中的令牌值进行比较,如果相同,说明这是正常提交,Action会执行操作,然后把session中的令牌值删除。

    当用户后退后,再次提交时,因为没有再次访问表单页面,那么就不会生成新的令牌值,所以参数中的令牌值还是上一次的,而session中已经没有令牌值了,所以Action会判断为重复提交。

    3 Struts2防止重复提交

    防止重复提交需要在页面中生成令牌值,然后在表单和session中分别保存,这个任务可以使用Struts2的标签:<s:token/>来未完成。这个标签会生成令牌值,并保存到session和表单的隐藏字段中。

    在Action中获取session和请求参数中的令牌值进行比较,然后在令牌值无误时,在session中删除令牌值;在令牌值不同时转发到invalid.token结果。这些任务由token拦截器来完成。

    <s:token>标签和token拦截器来完成防止表单重复提交。

    当token拦截器确定当前是重复提交后,会跳转到invalid.token结果,这说明我们需要在指定名为invlalid.token的结果。而且token拦截器还会在actionError中添加错误信息。

        <s:form action="tokenTest" namespace="/">

           <s:token />[崔40] 

           <s:submit value="呵呵" />

        </s:form>

    public class TokenAction extends ActionSupport {

        @Override

        public String execute() throws Exception {

           System.out.println("execute()...");

           return NONE;

        }

    }

           <action name="tokenTest" class="cn.itcast.action.TokenAction">

               <result name="invalid.token[崔41] ">/error.jsp</result>

               <interceptor-ref name="defaultStack" />

               <interceptor-ref name="token" />[崔42] 

           </action>

      <body>

        <s:actionerror/>[崔43] 

      </body>

    [崔44] 

    我们可以通过查看TokenInterceptor,知道这条错误令牌的key值是struts.messages.invalid.token,所以我们可以在国际化资源文件中覆盖这个key值。


     [崔1]文件表单项的名称为myUpload

     [崔2]一个文件表单项对应三个Action属性!Action属性名称要按fileupload拦截器的要求来。

     [崔3]请求总大小上限为1M

     [崔4]指定当前action的拦截器为defaultStack栈,对fileUpload拦截器进行了参数配置。

     [崔5]单个文件大小上限为512K

     [崔6]文件扩展名限制为jpg、png、bmp

     [崔7]上传出错

     [崔8]单个文件超出大小

     [崔9]非法文件类型

     [崔10]非法扩展名

     [崔11]请求总大小超出限制

     [崔12]所有文件表单项的名称是相同的

     [崔13]因为是多个文件表单项,所以需要使得数组属性

     [崔14]必须有getInputStream()方法,StreamResult会调用这个方法来获取被下载资源对应的输入流对象。

     [崔15]为StreamResult的contentType属性赋值

     [崔16]为StreamResult的contentDisposition属性赋值

     [崔17]OGNL表达式,相当于调用action对象的getContentType()方法

     [崔18]同上

     [崔19]获取<action>元素的<param>参数

     [崔20]获取真实路径

     [崔21]用来设置表单数据到Action中

     [崔22]因为是GET请求,所以需要处理一下编码问题

     [崔23]在<result>元素下的<param>中使用了${filename},这相当于调用了本方法。

     [崔24]处理下载框中的编码问题。

     [崔25]在<result>元素下的<param>中使用了${contentType}这相当于调用了本方法

     [崔26]通过文件名称获取对应的MIME类型

     [崔27]返回被下载对象对应的流对象。这个方法会被StreamResult对象调用。

     [崔28]通过下载目录和文件名创建文件流对象

     [崔29]创建Ognl上下文

     [崔30]向Ognl上下文中添加对象。

     [崔31]设置e1为根对象。

     [崔32]其中#emp2.name是OGNL表达式,cxt是查找的上下文对象,cxt.getRoot()是指定根对象。

     [崔33]输出结果为“李四”

     [崔34]因为Struts2会把当前Action压入值栈,所以默认是Action对象在栈顶,因为又向值栈中压入了emp和stu两个对象,那么值栈中元素的顺序从上到下分别是:Student、Emp、Action,当前栈顶元素为Student

     [崔35][0]表示从0下标开始到栈底的子栈,这个子栈其实与整个值栈是相同的,所以[0].name与name是相同的效果。即从下标0元素开始查找name属性,即Student对象的name属性会第一个被找到

     [崔36][1]表示从1下标开始到栈底的子栈,对于这个子栈而言,其栈顶就是下标1的元素了,即Emp对象,所以[1].name获取的是Emp对象的name属性。

     [崔37]同理,这是获取Action的name属性。

     [崔38]这个OGNL表达式与top相同!因为[0]子栈与整个值栈是相同的。

     [崔39]调用Math类的min()方法

     [崔40]该标签可以生成令牌值,并且保存到session中表单隐藏字段中。

     [崔41]当重复提交时,拦截器会跳转到invalid.token结果,所以我们需要配置它。

     [崔42]指定token拦截器,因为它不在defaultStack中。

     [崔43]error.jsp页面中打印actionError。

     [崔44]我们发现打印的错误令牌是英文的!

  • 相关阅读:
    May 1 2017 Week 18 Monday
    April 30 2017 Week 18 Sunday
    April 29 2017 Week 17 Saturday
    April 28 2017 Week 17 Friday
    April 27 2017 Week 17 Thursday
    April 26 2017 Week 17 Wednesday
    【2017-07-04】Qt信号与槽深入理解之一:信号与槽的连接方式
    April 25 2017 Week 17 Tuesday
    April 24 2017 Week 17 Monday
    为什么丑陋的UI界面却能创造良好的用户体验?
  • 原文地址:https://www.cnblogs.com/xyhero/p/9348771.html
Copyright © 2011-2022 走看看