• Struts2学习笔记


    Struts学习背景

      之前就学习自己看传智播客的视频学习过struts2,同时也将代码敲了好多遍,但是有些东西总是能够忘记,之前是没有进行任何的准备。之前刚好是公司试用期,需要完成一个点餐管理系统,对Struts2学习不是很深,只是边学边敲代码,然后写了一个点餐管理系统,现在这几天刚好闲 着没事,就打算在网上看一遍别人的额Struts2的总结,顺便回忆、复习一下,来总结一下。由于该文章是在别人的基础上进行编写的,详情请参照该原博客http://blog.csdn.net/hudie1234567/article/details/6730481

    Struts2简介

    struts2是在webwork2基础上发展而来的。和struts1一样,struts2也属于MVC框架。不过有一点需要注意的是:struts2和struts2虽然名字很相似,但是在两者在代码编写风格上几乎是不一样的。那么既然有了struts1,为什么还要推出struts2。主要的原因是struts2有以下优点:

    1.在软件设计上struts2没有像struts1那样跟servlet API和struts API有着紧密的耦合,struts2的应用可以不依赖于servlet API和struts API。struts2的这种设计属于无侵入式设计,而struts1却属于侵入式设计。

    2.struts2提供了拦截器,利用拦截器可以进行AOP编程,实现如权限拦截等功能。

    3.struts2提供了类型转换器,可以把特殊的请求参数转化成需要的类型。在struts1中,如果我们要实现同样的功能,就必须向struts1的底层实现BeanUtil注册类型转换器才行。

    4.struts2提供支持多种表现层技术,如:jsp、freemarker、velocity等。

    5.struts2的输入校验可以对指定的方法进行校验,解决了struts1长久之痛。

    6.提供了全局范围、包范围和Action范围的国际化资源文件实现。

    Struts2开发环境搭建

    1、下载Struts2的jar包并导入到项目中,Struts2的jar包官网下载地址为http://mirror.bit.edu.cn/apache/struts/(一般需要commons-fileupload-1.2.1.jar、commons-logging-1.0.4.jar、freemarker-2.3.15.jar、ognl-2.7.3.jar、struts2-core-2.1.8.1.jar和xwork-core-2.1.6.jar这6个jar文件,可以从struts2自带的示例项目中拷贝,粘贴到WebRoot/WEB-INF/lib下面)

    2、编写Struts2的配置文件Struts.xml,并放入src目录下(可以从struts2自带的示例项目中拷贝struts.xml,粘贴到src目录下,然后在这个基础上按照自己的需要来更改)。

    现在列出工作中用到的一个Struts.xml文件作为对照

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE struts PUBLIC 
    "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" 
    "http://struts.apache.org/dtds/struts-2.1.dtd">
    <struts>
        <!-- 开发配置 -->
        <constant name="struts.devMode" value="false" />
        <constant name="struts.serve.static.browserCache" value="false" />
        <!-- 常规配置 -->
        <constant name="struts.i18n.encoding" value="UTF-8" />
        <constant name="struts.locale" value="zh_CN" />
        <constant name="struts.custom.i18n.resources" value="i18n.enum,i18n.epgsync,i18n.menu,i18n.mess,i18n.operation,i18n.text,i18n.tip,i18n.valid" />
        <constant name="struts.multipart.maxSize" value="209715200"></constant>
        <!-- 基于文件域的防注入实现 -->
        <bean name="jakartaExt"
            type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" class="net.sunniwell.mop.security.struts.JakartaMultiPartRequestExt"
            scope="default" />
        <constant name="struts.multipart.handler" value="jakartaExt" />
        <!-- 排除websocket请求 -->
        <constant name="struts.action.excludePattern" value="/wsServlet"></constant>
        <package name="mop" extends="json-default" abstract="true">
            <interceptors>
                <!-- 定义权限检测拦截器 -->
                <interceptor name="authority"
                    class="net.sunniwell.mop.interceptor.AuthorityInterceptor" />
                <interceptor-stack name="globalStack">
                    <interceptor-ref name="authority" />
                    <interceptor-ref name="defaultStack">
                        <!-- 自定义表单验证错误后的视图 -->
                        <param name="workflow.inputResultName">ve</param>
                    </interceptor-ref>
                </interceptor-stack>
                <interceptor-stack name="uploadStack">
                    <interceptor-ref name="authority" />
                    <!-- 图片上传类型大小限制 -->
                    <interceptor-ref name="fileUpload">
                        <param name="allowedTypes">image/png,image/gif,image/jpeg</param>
                        <param name="maximumSize">2097152</param>
                    </interceptor-ref>
                    <interceptor-ref name="defaultStack" />
                </interceptor-stack>
            </interceptors>
            <default-interceptor-ref name="globalStack" />
            <!--全局的结果集配置-->
            <global-results>
                <result name="login">/login.jsp</result>
                <result name="main">/pages/index/main.jsp</result>
                <result name="ve" type="json">
                    <param name="contentType">text/json</param>
                    <!--是否忽略等级,默认为true,即不包含父类的属性 -->
                    <param name="ignoreHierarchy">false</param>
                    <param name="includeProperties">fieldErrors.*,actionErrors.*</param>
                </result>
            </global-results>
        </package>
    
        <!-- 不需要登录权限的请求 -->
        <package name="login" extends="mop" namespace="/">
            <default-interceptor-ref name="defaultStack" />
            <action name="imageAction" class="imageAction">
                <result type="stream">
                    <param name="contentType">image/png,image/gif,image/jpeg</param>
                    <param name="inputName">imageFile</param>
                    <param name="bufferSize">4096</param>
                </result>
            </action>
            <action name="main" class="operatorAction" method="login"></action>
            <action name="login" class="operatorAction" method="logout"></action>
            <action name="language" class="languageAction" method="changeLang"></action>
            <action name="portal_*" class="portalAction" method="{1}">
                <result type="json">
                    <param name="root">JSON</param>
                </result>
            </action>
        </package>
    
        <!-- 页面跳转请求 -->
        <package name="action" extends="mop" namespace="/">
            <global-results>
                <result name="input">${input}</result>
                <result name="success">${success}</result>
            </global-results>
            <!-- 上传,下载文件请求 -->
            <action name="*ExcelAction*" class="{1}ExcelAction">
                <result type="stream">
                    <param name="contentType">application/x-download</param>
                    <param name="inputName">exportFile</param>
                    <!-- 根据Action中fileName参数设置下载文件名 -->
                    <param name="contentDisposition">attachment;filename="${exportFileName}"</param>
                    <param name="bufferSize">4096</param>
                </result>
            </action>
            <action name="*Action_*" class="{1}Action" method="{2}">
                <interceptor-ref name="uploadStack" />
            </action>
            <action name="*Pages_*?*">
                <result>/pages/{1}/{2}.jsp?{3}</result>
            </action>
            <action name="*Pages_*">
                <result>/pages/{1}/{2}.jsp</result>
            </action>
        </package>
    
        <!-- ajax请求 -->
        <package name="json" extends="mop" namespace="/json">
            <!-- 广告图片上传请求,限制图片上传大小 -->
            <action name="ad_uploadImg" class="adAction" method="uploadImg">
                <interceptor-ref name="uploadStack" />
                <result type="json">
                    <param name="root">ResultJson</param>
                </result>
            </action>
            <action name="*_*" class="{1}Action" method="{2}">
                <result type="json">
                    <param name="root">ResultJson</param>
                </result>
                <result type="json" name="list">
                    <param name="root">ResultList</param>
                </result>
            </action>
        </package>
    </struts>    

     3、在web.xml中加入struts2框架启动配置,具体的方法是在web.xml中加入如下的代码:

    <filter>  
        <filter-name>struts2</filter-name>  
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>  
    </filter>  
          
    <filter-mapping>  
        <filter-name>struts2</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping> 

    从上面可以看出,struts2框架是通过filter启动的。在StrutsPrepareAndExecuteFilter的init()方法中读取类路径下默认的配置文件struts.xml完成初始化操作。

    注意:struts2读取到struts.xml中的内容后,以javabean的形式保存在内存中,以后struts2对用户的每次请求处理将使用内存中的数据,而不是每次都读取struts.xml文件。

    Struts2的包package的介绍

    <package name="upload" namespace="/upload" extends="struts-default">  
        <action name="upload" class="antony.action.HelloWorldAction" method="upload">  
            <result name="success">/success.jsp</result>  
        </action>  
    </package> 

    struts2中使用包来管理action。包的作用和java中类包是非常类似的,它主要用于管理一组业务功能相关的action。在实际应用中,应该把一组业务功能相关的action放在同一个包下面。

    配置包是必须指定name属性,该name可以随意取名,但是必须唯一,它不对应java的类包,如果其他类要继承该包,必须使用该属性进行使用。包的namespace属性用于定义包的命名空间,命名空间作为该action路径的一部分,如访问上面的例子中的action的路径为:/upload/upload.action。namespace属性可以不用配置,如果不指定该属性,默认的命名空间为“”(空字符串)。

    通常每个包都必须继承struts-default包,因为struts很多核心的功能都是在这个包中定义的拦截器实现的,如:从请求中把请求参数封装到action、文件上传和数据验证等功能搜是通过拦截器实现的。struts-default包中定义了这些拦截器和result类型。换句话说,当包继承了strtus-default包才能使用struts提供的核心功能。struts-default包是在struts2-core-2.x.x.x.jar文件中的struts-default.xml中定义的。struts-default.xml是struts2的默认配置文件,struts2每次都会自动加载struts-default.xml文件。

    包还可以通过abstract=“true”定义为抽象包,抽象包中不能包含action。

    注意,在配置文件struts.xml中没有提示的解决办法:window->preference->xml catalog中添加struts-2.0.dtd文件,key type为URI,key为http://struts.apache.org/dtds/struts-2.0.dtd。

    action的搜索顺序

    1.获得请求的URI,例如uri是:http://server/struts2/path1/path2/path3/test.action

    2.首先寻找namesp为/path1/path2/path3的package,如果不存在这个package,就转第三步,如果存在这个package,则在这个package中寻找名字为test的action,当在该package中找不到action时就到默认namespace的package中寻找(默认package的命名空间为空字符串“”),如果在默认的package中还找不到该action,页面提示找不到action。

    3.寻找namespace为/path1/path2的package,如果不存在这个package,则转第四步,如果存在这个package,则在这个package中寻找名字为test的action,当在该package中找不到action时就到默认namespace的package中寻找(默认package的命名空间为空字符串“”),如果在默认的package中还找不到该action,页面提示找不到action。

    4.寻找namespace为/path1的package,如果不存在这个package,则转第五步,如果存在这个package,则在这个package中寻找名字为test的action,当在该package中找不到action时就到默认namespace的package中寻找(默认package的命名空间为空字符串“”),如果在默认的package中还找不到该action,页面提示找不到action。

    5.寻找namespace为/的package,如果存在这个package,则在这个package中寻找名字为test的action,当在该package中找不到action或不存在这个package时就到默认namespace的package中寻找(默认package的命名空间为空字符串“”),如果在默认的package中还找不到该action,页面提示找不到action。

    action配置中的默认值

    <package name="department" namespace="/department" extends="struts-default">  
        <action name="helloworld" class="antony.action.HelloWorldAction" method="execute">  
            <param name="savepath">department</param>  
            <result name="success">/employeeAdd.jsp?username=${username}</result>  
        </action>  
    </package>  
    

    1.如果没有为action指定class,默认的class是ActionSupport。

    2.如果没有为action指定method,默认执行action中的execute方法。

    3.如果没有为result指定name属性,默认值为success。

    action配置中result的各种转发类型

    result配置类似于struts1中的forward,但struts2提供了多种结果类型,常用的类型有dispatcher(默认值)、redirect、redirectAction、plainText。

    result中还可以使用${属性名}表达式来访问action中的属性,表达式中的属性名为action中的属性名,如下:

    <result type="redirect">/employeeAdd.jsp?id=4{id}</result> 
    

    下面是结果类型为redirectAction的例子:

    重定向到同一个包中的action:

    <result type="redirectAction">add</result> 
    

    重定向到别的namespace中的action:

    <result type="redirectAction">  
        <param name="actionName">xxx</param>  
        <param name="namespace">/redirectAction</param>  
    </result>  

    plainText:显示原始文件内容,例如:当需要原样显示jsp文件源代码的时候,就可以使用此类型。(如点击某个超链接获取源码的应用)

    <action name="source">  
        <result type="plainText">  
            <param name="location">/index.jsp</param>  
            <param name="charSet">UTF-8</param> <!--指定读取文件的编码方式-->   
        </result>  
    </action>  

    action的属性注入

    Struts2的属性注入一般是有三种方法的。

    1、在Struts2的action中编写属性,然后编写set/get方法,采用基本类型接收参数(get/post)

    在action类中定义与请求参数同名的属性,并且该属性有set和get方法,struts2就能自动接收请求参数并赋予同名属性。

    请求路径:http://localhost:8080/struts2/product/view.action?id=1

    public class ProductAction {  
        private int id;  
        public int getId() {  
            return id;  
        }  
        public void setId(int id) {//通过反射技术调用与与请求参数同名的属性的setter方法来为该属性设置值  
            this.id=id;  
        }  
    } 

    2、在Struts2的action中对象的自动注入,编写对象属性,然后编写set/get方法,在页面上用到对象的属性时用 对象.属性名

    请求路径:http://localhost:8080/struts2/product/view.action?product.id=1

    public class ProductAction {  
        private Product product;  
        public Product getProduct() {  
            return product;  
        }  
        public void setProduct(Product product) {  
            this.product=product;  
        }  
    }  

    3、在Struts2的action中的对象自动注入,编写对象属性,然后编写set/get方法,然后该action在继承actionsupport类的同时实现ModelDriven<Object>接口,并给出get方法 

    public class LoginAction extends ActionSupport implements ModelDriven<Account> {
        private static final long serialVersionUID = 1L;
        // 依赖与service层
        private Account acc = new Account();// 用户 对象自动注入
        // get和set方法
        public Account getAcc() {
            return acc;
        }
        public void setAcc(Account acc) {
            this.acc = acc;
        }
        // 实现对象自动注入的接口必须实现的方法
        public Account getModel() {
            return acc;
        }

    struts2首先通过反射技术调用Product的默认构造器创建product对象,然后再通过反射技术调用product中与请求参数同名的属性的setter方法来设置请求参数值。

    struts2.1.6接收中文请求参数乱码的问题是个bug,原因是在获取并使用了请求参数后才调用HttpServletRequest的setCharacterEncoding()方法进行编码设置,导致应用使用的就是乱码参数。这个bug在struts2.1.8中已经被解决,如果使用的是struts2.1.6,要解决这个问题,可以采用下面的方法:

    新建一个filter,把这个filter放在struts2的filter前面,然后在doFilter()方法里添加如下的代码:

    public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws java.io.IOException,ServletException {  
        HttpServletRequest req = (HttpServletRequest)request;  
        req.setCharacterEncoding("UTF-8");//根据实际使用的编码替换  
        chain.doFilter(request,response);  
    }  

    常用的一些常量

    default.properties文件中定义了很多常量,下面就说说通常使用到的几个。

    struts.i18n.encoding 指定默认编码集

    struts.action.extension struts2处理的默认后缀,如果需要定义多个后缀,则用逗号“,"隔开

    struts.serve.static.browserCache 设置浏览器是否缓存静态内容,默认为true(生产环境下使用),开发阶段最好关闭

    struts.configuration.xml.reload 当struts的配置文件修改后,系统是否自动重新加载该文件,默认值为false(生产环境下),开发阶段最好打开。

    struts.devMode 是否为开发模式,默认为false,(生产环境下),开发阶段最好打开,以便打印出更详细的信息

    struts.ui.theme 视图主题,一般用simple

    struts.objectFactory  于spring集成时,指定由spring负责action对象的创建

    struts.enable.DynamicMethodInvocation 是否支持动态方法调用,action!methodname,默认为true

    struts.multipart.maxSize 上传文件的大小

    Struts2处理的流程

    StrutsPrepareAndExecuteFilter是struts2框架的核心控制器,它负责拦截由<url-pattern>/*</url-pattern>指定的所有用户请求,当用户请求到达时,该Filter会过滤用户请求。默认情况下,如果用户请求的路径不带后缀或是后缀是action,这时请求将被struts2框架处理,否则struts2将略过该请求。当请求转入struts2处理时会先经过一些列的拦截器,然后到action。与struts1不同,struts2对用户的每一个请求都会创建一个action,所以struts2是线程安全的。

    为应用指定多个Struts配置文件

    在大部分应用里,随着应用规模的增加,系统中action的数量也会大量增加,导致struts.xml配置文件变的非常臃肿。为避免struts.xml文件过于庞大、臃肿,提高struts.xml文件的可读性,可以将一个struts.xml文件分解成过个配置文件,然后在struts.xml文件中包含其他的配置文件。下面的struts.xml文件通过include元素指定多个配置文件:

    <?xml version="1.0" encoding="UTF-8"?>  
    <!DOCTYPE struts PUBLIC  
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"  
        "http://struts.apache.org/dtds/struts-2.0.dtd">  
    <struts>  
        <include file="department.xml"></include>  
        <include file="employee.xml"></include>  
        <include file="upload.xml"></include>  
        <include file="interceptor.xml"></include>  
    </struts>  

    通过这种方式,可以将struts中的action按模块添加在多个配置文件中。

    动态方法调用

    在配置文件的action中不指定method,而是在访问的时候通过actionname!methodname这种方式来访问,前提是struts.enable.DynamicMethodInvocation要设置为true。

    例如:

    <package name="employee" namespace="/employee" extends="struts-default">  
        <action name="helloworld" class="antony.action.HelloWorldAction">  
            <result name="success">/employeeAdd.jsp?username=${username}</result>  
        </action>  
    </package>  

    假设antony.action.HelloWorldAction类中有add()和update()两个方法,可以使用"/employee/helloworld!add"来访问add()方法。

    使用通配符的方式配置action

    使用通配符*定义action,可以有效的减小配置文件的规模。下面就是一个简单的例子,和上面动态方法调用中的配置文件类似:

    <package name="employee" namespace="/employee" extends="struts-default">  
        <action name="helloworld_*" class="antony.action.HelloWorldAction" method="{1}">  
            <param name="savepath">employee</param>  
            <result name="success">/employeeAdd.jsp?username=${username}</result>  
        </action>  
    </package>  

    假设antony.action.HelloWorldAction类中有add()和update()两个方法,这样定义后,就可以使用”/employee/helloworld_add“来访问add()方法。

    自定义转行类型转换器

    1、定义类

    public class DateTypeConverter extends DefaultTypeConverter {  
        @Override  
        public Object convertValue(Map<String, Object> context, Object value,Class toType) {  
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");  
            try {  
                if(toType == Date.class) {//字符串向date类型转换  
                    String[] params = (String[])value;  
                    return sdf.parse(params[0]);  
                } else if(toType == String.class) {//date转换成string类型  
                    Date date = (Date)value;  
                    return sdf.format(date);  
                }  
            } catch (ParseException e) {  
                e.printStackTrace();  
            }  
            return null;  
        }     
    }  

    2.将上面的类注册为局部类型转换器

    在action所在的包下放置ActionClassName-conversion.properties文件,ActionClassName是action的类名,后面的-conversion.properties是固定写法,在本例中,文件名为HelloWorldAction-conversion.properties。该文件的内容为:

    属性名称=类型转换器的全类名

    就本例而言,HelloWorldAction-conversion.properties文件中的内容为:

    birthday=antony.type.converter.DateTypeConverter  

    3.将上面的类注册为全局类型转换器

    在WEB-INF/classes下放置xwork-conversion.properties文件。该文件中的内容为:

    待转换的类型=类型转换器的类全名

    对于本例而言,xwork-conversion.properties文件中的内容为

    java.util.Date=antony.type.converter.DateTypeConverter  

    访问request、session、application域中的属性

    1.如果只是添加或访问request/session/application中的属性,就可以用ActionContext类来实现。如下代码所示:

    ActionContext ctx = ActionContext.getContext();  
    ctx.getApplication().put("app", "应用范围");//相当于往ServletContext中放入app  
    ctx.getSession().put("ses", "session应用范围");//相当于往session中加入ses  
    ctx.put("req", "request应用范围");//相当于往request中加入req   
    ctx.put("names", Arrays.asList("刘明","茫茫大海"));  

    2.获取HttpServletRequest/HttpSession/ServletContext/HttpServletResponse对象,可以有两种方法实现:
    (1)通过ServletActionContext直接获取:

    HttpServletRequest request = ServletActionContext.getRequest();  
    ServletContext context = ServletActionContext.getServletContext();  
    request.setAttribute("req", "请求范围属性");  
    request.getSession().setAttribute("ses", "会话范围属性");  
    context.setAttribute("app", "应用范围属性");  

    (2)实现指定接口,由struts2框架运行时注入:

    public class TestAction implements ServletRequestAware, ServletResponseAware,   ServletContextAware {  
        private HttpServletRequest request;  
        private HttpServletResponse response;  
        private ServletContext servletContext;  
          
        public void setServletRequest(HttpServletRequest request) {  
            this.request = request;  
        }  
      
        public void setServletResponse(HttpServletResponse response) {  
            this.response = response;  
        }  
      
        public void setServletContext(ServletContext context) {  
            this.servletContext = context;  
        }  
      
    }  

    文件上传

    文件上传在项目中经常会用到,下面就来说说struts2中怎么上传文件的:
    1.引入相应的jar包(commons-fileupload-1.2.1.jar和commons-io-1.3.2.jar)
    2.把form的enctype设置为"multipart/form-data",如下所示:

    <form action="<%=basePath%>upload/upload.action" method="post" name="form" enctype="multipart/form-data">  
        文件1:<input type="file" name="upload"/><br/>  
        <input type="submit" value="上传" />  
    </form>  

    3.在action类中添加如下代码中注释的几个属性。

    public class HelloWorldAction {  
        private File upload;//得到上传的文件  
        private String uploadContentType;//得到上传文件的扩展名  
        private String uploadFileName;//得到上传文件的名称  
                  
        public File getUpload() {  
            return upload;  
        }  
      
        public void setUpload(File upload) {  
            this.upload = upload;  
        }  
      
        public String getUploadContentType() {  
            return uploadContentType;  
        }  
      
        public void setUploadContentType(String uploadContentType) {  
            this.uploadContentType = uploadContentType;  
        }  
      
        public String getUploadFileName() {  
            return uploadFileName;  
        }  
      
        public void setUploadFileName(String uploadFileName) {  
            this.uploadFileName = uploadFileName;  
        }  
          
        public String upload() throws IOException {  
            String realpath = ServletActionContext.getServletContext().getRealPath("/upload");  
            if(upload != null) {  
                File savefile = new File(realpath,uploadFileName);  
                if(!savefile.getParentFile().exists()) {  
                    savefile.getParentFile().mkdirs();  
                }  
                FileUtils.copyFile(upload, savefile);  
                ActionContext.getContext().put("msg", "文件上传成功!");  
            }  
            return "success";  
        }  
    }  

    注意,如果在上传的过程中文件的大小超过了struts2默认的文件大小的话,就会上传失败,这时候,可以根据具体的情况设置struts.multipart.maxSize的值来满足上传的需求。

    多文件上传

    在实际的项目中,有时候可能会要求上传多个文件的情况,下面就来说说上传多个文件的情况。
    1.同上。
    2.form如下所示:

    <form action="<%=basePath%>upload/upload" method="post" name="form" enctype="multipart/form-data">  
        文件1:<input type="file" name="upload"/><br/>  
        文件2:<input type="file" name="upload"/><br/>  
        文件3:<input type="file" name="upload"/><br/>  
        <input type="submit" value="上传" />  
    </form>  

    3.action中添加的几个属性都是数组形式的。

    public class HelloWorldAction {  
        private File[] upload;//得到上传的文件  
        private String[] uploadContentType;//得到上传文件的扩展名  
        private String[] uploadFileName;//得到上传文件的名称  
                  
        public File[] getUpload() {  
            return upload;  
        }  
      
        public void setUpload(File[] upload) {  
            this.upload = upload;  
        }  
      
        public String[] getUploadContentType() {  
            return uploadContentType;  
        }  
      
        public void setUploadContentType(String[] uploadContentType) {  
            this.uploadContentType = uploadContentType;  
        }  
      
        public String[] getUploadFileName() {  
            return uploadFileName;  
        }  
      
        public void setUploadFileName(String[] uploadFileName) {  
            this.uploadFileName = uploadFileName;  
        }  
          
        public String upload() throws IOException {  
            String realpath = ServletActionContext.getServletContext().getRealPath("/upload");  
            if(upload != null) {  
                for(int i=0; i<upload.length; i++) {  
                    File savefile = new File(realpath,uploadFileName[i]);  
                    if(!savefile.getParentFile().exists()) {  
                        savefile.getParentFile().mkdirs();  
                    }  
                    FileUtils.copyFile(upload[i], savefile);  
                }  
                ActionContext.getContext().put("msg", "文件上传成功!");  
            }  
            return "success";  
        }  
    } 

    定义拦截器

    自定义拦截器要实现com.opensymphony.xwork2.interceptor.Interceptor接口。下面是一个自定义拦截器的例子:

    public class PermissionInterceptor implements Interceptor {  
      
        public void destroy() {  
              
        }  
        public void init() {  
      
        }  
        public String intercept(ActionInvocation invocation) throws Exception {  
            Object user = ActionContext.getContext().getSession().get("user");  
            if(user != null) {  
                return invocation.invoke();  
            } else {  
                ActionContext.getContext().put("message", "你没有执行权限!");  
            }  
            return "success";  
        }  
      
    } 

    接下来,就要在配置文件中注册拦截器,具体的做法是:

    <interceptors>  
        <interceptor name="permission" class="antony.interceptor.PermissionInterceptor"></interceptor>  
    </interceptors> 

    为action指定拦截器,具体的做法是:

    <action name="interceptor" class="antony.action.HelloWorldAction" method="interceptor">  
        <interceptor-ref name="permission"></interceptor-ref>  
    </action>  

    但是这样做了以后,就会出现一个问题,struts2中为一个action指定拦截器后,默认的defaultStack中的拦截器就不起作用了,也就是说struts2的众多核心功能都使用不了了(struts2的许多核心功能都是通过拦截器实现的),为了解决这个问题,引入拦截器栈,先使用系统默认的拦截器,然后再来使用自定义的拦截器,具体的做法是:

    <interceptors>  
        <interceptor name="permission" class="antony.interceptor.PermissionInterceptor"></interceptor>  
        <interceptor-stack name="permissionStack">  
            <interceptor-ref name="defaultStack"></interceptor-ref>  
            <interceptor-ref name="permission"></interceptor-ref>  
        </interceptor-stack>  
    </interceptors>  

    如果希望包下的所有action都使用自定义的拦截器,可以把拦截器设置为默认拦截器,具体的实现方式是:

    <default-interceptor-ref name="permissionStack"></default-interceptor-ref>  

    注意:每个包中只能有一个默认的拦截器;一旦为包中的某个action指定了拦截器,则默认的拦截器就不起作用了。

    输入校验

    struts2中可以实现对action中的所有方法进行校验,也可以实现对指定的方法进行校验。可以用如下两种方式来实现输入校验。

    1.采用手工编写代码实现:
    <1>手工编写代码实现对action中的所有方法进行校验:通过编写validate()方法实现,validate()会校验action中所有与execute方法签名相同的方法。当某个数据校验失败时,应该采用addFieldError()方法往系统的filedErrors添加校验失败信息(为了使用该方法,action可以继承ActionSupport),如果系统的filedErrors包含失败信息,struts2会将请求发到名为input的result,在input视图中可以通过<s:fielderror/>显示失败信息。具体的参见下面的例子:

    <s:fielderror></s:fielderror>  
    <form method="post" action="<%=basePath%>person/manage_save.action">  
           用户名:<input type="text" name="username"/>不能为空<br/>  
           手机号:<input type="text" name="mobile"/>不能为空,并且要符合手机号的格式,1,3/5/8,后面是9个数字<br/>  
           <input type="submit" value="提交"/>  
    </form>  

    页面中使用<s:fielderror></s:fielderror>来显示失败信息。

    public void validate() {//会对action中的所有方法进行校验  
        if(this.username == null || this.username.trim().equals("")) {  
            this.addFieldError("username", "用户名不能为空!");  
        }  
              
        if(this.mobile == null || this.mobile.trim().equals("")) {  
            this.addFieldError("mobile", "手机号不能为空!");  
        } else {  
            if(!Pattern.compile("^1[358]\d{9}{1}quot;).matcher(this.mobile).matches()) {  
                this.addFieldError("mobile", "手机号格式不正确!");  
            }  
        }  
    }  

    <2>手工编写代码实现对action中的指定方法进行校验:通过编写validateXxx()方法实现,validateXxx()会校验action中方法名为xxx的方法。当某个数据校验失败时,应该采用addFieldError()方法往系统的filedErrors添加校验失败信息(为了使用该方法,action可以继承ActionSupport),如果系统的filedErrors包含失败信息,struts2会将请求发到名为input的result,在input视图中可以通过<s:fielderror/>显示失败信息。该校验方法示例如下:

    public void validateUpdate() {//会对action中的update方法进行校验  
        if(this.username == null || this.username.trim().equals("")) {  
            this.addFieldError("username", "用户名不能为空!");  
        }  
              
        if(this.mobile == null || this.mobile.trim().equals("")) {  
            this.addFieldError("mobile", "手机号不能为空!");  
        } else {  
            if(!Pattern.compile("^1[358]\d{9}{1}quot;).matcher(this.mobile).matches()) {  
                this.addFieldError("mobile", "手机号格式不正确!");  
            }  
        }  
    }  

    *.输入校验的流程:
    (1)类型转换器对请求参数进行执行类型转换,并把转换后的值赋给action中的属性。
    (2)如果在执行转换的过程中出现异常,系统会将异常信息保存到ActionContext中,conversionError拦截器将异常信息封装到fieldErrors中。不管类型转换是否异常,都会转入第3步。
    (3)系统通过放射技术先调用action中的validateXxx()方法,xxx为方法名。
    (4)再调用action的validate()方法。
    (5)经过上面四步,如果系统中的fieldErrors存在错误信息(即存放错误信息的集合的size大于0),系统会将请求转发至名为input的视图。如果系统中的filedErrors中没有错误信息,系统将执行action中的处理方法。

    2.基于xml配置方式来实现:
    <1>基于xml配置方式实现对action中的所有方法进行校验:Action要继承ActionSupport,并且提供校验文件,校验文件和action类在同一个包下,文件的命名规则是:ActionClassName-validation.xml,其中的ActionClassName为action的简单类名,-validation为固定写法。下面是一个校验文件(PersonAction-validation.xml)的例子:

    <?xml version="1.0" encoding="UTF-8"?>  
    <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.3//EN"  
           "http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd">  
    <validators>  
        <field name="username">  
            <field-validator type="requiredstring">  
                <param name="trim">true</param><!-- 默认为true -->  
                <message>用户名不能为空!</message>  
            </field-validator>  
        </field>  
        <field name="mobile">  
            <field-validator type="requiredstring">  
                <param name="trim">true</param><!-- 默认为true -->  
                <message>手机号不能为空!</message>  
            </field-validator>  
            <field-validator type="regex">  
                 <param name="expression"><![CDATA[^1[358]d{9}$]]></param>  
                <message>手机格式不正确!</message>  
            </field-validator>  
        </field>  
    </validators>  

    说明:<field>指定action中要校验的属性,<field-validator>指定校验器,上面指定的requiredstring校验器是由系统提供的,系统提供了能满足大部分验证需求的校验器,这些校验器可以在文件xwork-core-2.X.X.jar中的com.opensymphony.xwork2.validator.validators下的default.xml中找到。<message>为校验失败后的提示信息,如果需要国际化,可以为message指定key属性,key的值为资源文件中的key。在这个配置文件中,对action中字符串类型的username属性进行验证,首先要求调用trim()去掉空格,然后判断用户名是否为空。对string类型的mobile字段的校验使用了regex这个校验器。

    <2>基于xml配置方式实现对action中指定的方法进行校验:当校验文件名为ActionClassName-validation.xml时,会对action中的所有方法进行校验。如果要对action中的某一个方法进行校验,那么校验文件的名称为:ActionClassName-ActionName-validation.xml。其中ActionName为struts.xml中的action的名称。配置文件如下:

    <package name="person" namespace="/person" extends="struts-default">  
        <action name="manage_*" class="antony.action.PersonAction" method="{1}">  
            <result name="message">/WEB-INF/page/message.jsp</result>  
            <result name="input">/index.jsp</result>  
        </action>  
    </package>  

    假设PersonAction中有save()和update()两个方法。对save方法进行校验的文件名为PersonAction-manage_save-validation.xml,对update方法进行校验的文件名位PersonAction-manage_update-validation.xml。

    *.基于xml校验的一些特点:
    当为某个action提供了ActionClassName-validation.xml和ActionClassName-ActionName-validation.xml两种规则的校验文件时,系统按下面的顺序寻找校验文件:
    (1)ActionClassName-validation.xml
    (2)ActionclassName-ActionName-validation.xml
    系统搜索到第一个校验文件时,还会继续搜索后面的校验文件,当搜索到所有校验文件时,会把校验文件里的所有校验规则汇总,然后全部应用于action方法的校验。如果两个文件指定的校验规则冲突,则只使用后面文件中的校验规则。

    当action继承了另一个action时,父类action的校验文件会被先搜索到。还是按照上面的顺序,校验规则为四个文件的总和。

    国际化

    1.准备资源文件,资源文件的命名格式是:
    baseName_language_country.properties
    其中baseName为资源文件的基本名,可以自定义。但language和country必须是java支持的语言和国家。如:
    中国大陆:baseName_zh_CN.properties
    美国:baseName_en_US.properties

    现在为应用添加两个资源文件:
    第一个存放中文:welcome_zh_CN.properties
    内容为:welcome=欢迎来到中国
    第二个存放英文:welcome_en_US.properties
    内容为:welcome=welcome to china

    对于中文的属性文件,编写好后,应该使用jdk自带的native2ascii命令把文件转成unicode编码的文件,命令的使用方式如下:
    native2ascii src.properties dest.properties

    2.配置全局资源文件
    准备好资源文件后, 可以在struts.xml中通过struts.custom.i18n.resources把资源文件定义为全局资源文件,如下所示:

    <constant name="struts.custom.i18n.resources" value="welcome"></constant>  

    3.输出国际化信息
    (1)在jsp页面中使用<s:text name=""/>标签输出国际化信息,如:<s:text name="welcome"/>,name为资源文件中的key
    (2)在action类中,可以继承ActionSupport,使用getText()方法得到国际化信息,该方法的第一个参数用于指定资源文件中的key
    (3)在表单标签中,通过key属性指定资源文件中的key,如:<s:textfield name="realname" key="welcome"/>

    *.输出带占位符的国际化信息
    资源文件中的内容为:welcome=package:{0},欢迎来到传智播客{1}

    在jsp页面输出带占位符的国际化信息

    <s:text name="welcome">  
        <s:param>liuming</s:param>  
        <s:param>study</s:param>  
    </s:text> 

    在action类中获取带占位符的国际化信息,可以使用getText(String key,String[] args)或getText(String key,List args)。

    4.包范围的资源文件
    在一个大型应用中,整个应用有大量的内容需要实现国际化,如果把国际化的内容都放置在全局资源文件属性中,会导致资源文件过于庞大、臃肿,不便于维护,这个时候可以针对不同模块,使用包范围来组织国际化文件。具体的做法如下:

    在java的包下放置package_language_country.properties资源文件,package为固定写法,处于该包及子包下的action都可以访问该资源。当查找指定key的消息时,系统会先从package资源文件中查找,当找不到对应的key时,才会从全局资源文件中找。

    5.action范围的资源文件
    可以为某个action单独指定资源文件,方法如下:
    在action类所在的路径,放置ActionClassName_language_country.properties资源文件,ActionClassName为action类的简单名称。

    当查找指定key的消息时,系统会先从ActionClassName_language_country.properties资源文件中查找,如果没有找到对应的key,然后沿着当前包向上查找基本名为package的资源文件,一直找到最顶层包。如果还没有找到指定的key,最后会从全局资源文件中查找。

    6.jsp中直接访问某个资源文件
    struts2提供了<s:i18n>标签,使用该标签可以在类路径下直接从某个资源文件中获取国际化数据,而无需任何配置。

    <s:i18n name="welcome">  
        <s:text name="welcome"/>  
    </s:i18n>  

    如果要访问的资源在类路径的某个包下,可以这样访问:

    <s:i18n name="antony/action/package">  
        <s:text name="welcome"/>  
    </s:i18n>  
    

    访问包antony.action包下基本名为package的资源文件。

    还可以访问action范围的资源文件:

    <s:i18n name="antony/action/PersonManageAction">  
        <s:text name="welcome">  
           <s:param>liuming</s:param>  
           <s:param>study</s:param>  
        </s:text>  
    </s:i18n>  

    OGNL表达式语言

    GNL是Object Graphic Navigation Language(对象导航图语言)的缩写,它是一个开源项目。Struts2框架使用OGNL作为默认的表达式语言。

    相对于EL表达式,它提供了一些平时需要的功能。
    (1)支持对象方法调用,如xxx.save()
    (2)支持类静态方法调用和值访问,表达式的格式为@全类名(包括包路径)@方法名(参数),如:@java.lang.String@format('foo%s','bar')或@java.util.Math@PI
    (3)操作集合对象

    Ognl有一个上下文(Context)概念,说白了上下文就是一个Map结构,它实现了java.util.Map接口,在struts2中context的实现为ActionContext,下面是它的结构示意图:

    当struts2接受一个请求时,会迅速创建出ActionContext,ValueStack,action,然后把action存放进ValueStack,所以action的实例变量可以被ognl访问。

    访问上下文中的对象需要使用#标注命名空间,如#application,#session等。

    ognl有一个根对象(root对象),在struts2中根对象就是ValueStack,如果要访问根对象中对象的属性,则可以省略#命名空间,直接访问该对象的属性即可。

    在struts2中,根对象ValueStack的实现为OgnlValueStack,该对象不是只存放单个值,而是存放一组对象。在OgnlValueStack中有一个List类型的root变量,就是使用它存放一组对象。

    在root变量中处于第一位的对象叫做栈顶对象。通常在OGNL表达式里面直接写上属性的名称即可访问root变量里对象的属性,搜索顺序是从栈顶对象开始,依次往下搜索,直到找到为止。

    需要注意的一点是,struts2中ognl表达式要配合struts标签才可以使用。如:<s:property value="name"/>

    由于ValueStack是Struts2中的ognl的根对象,如果用户需要访问ValueStack中的对象,在jsp页面可以通过下面的EL表达式访问ValueStack中对象的属性。

    ${foo}//获得ValueStack中某个对象的foo属性

    如果访问Context中的别的对象,由于它们不是根对象,所以在访问是需要添加#前缀。
    (1)application对象:用于访问ServletContext,例如#application.userName或#application['userName'],相当于调用session的getAttribute("userName")
    (2)session对象:用于访问HttpSession,例如#session.userName或#session['userName'],相当于调用ServletContext的getAttribute("userName")
    (3)request对象:用于访问HttpServletRequest对象,例如#request.userName或#request['userName'],相当于调用request的getAttribute("userName")
    (4)parameters对象:用于访问Http的请求参数,例如#parameters.userName或#parameters['userName'],相当于调用request的getParameter("userName")
    (5)attr对象:用于按page->request->session->application的顺序访问其属性。

    采用ognl表达式创建List/Map集合对象:如果需要一个集合元素的时候(例如list或map),可以使用ognl中通集合相关的表达式,使用如下代码可以直接生成一个list对象:

    <s:set var="list" value="{'第一个','第二个','第三个'}"></s:set><!-- 默认放在OGNL context中 -->  
    <!--s:iterator中有一个特点,会把当前迭代的对象放在栈顶中  -->  
    <s:iterator value="#list">  
        <s:property/><br/>  
    </s:iterator>  

    set标签用于将某个值放入某个范围。
    scope指定变量被放置的范围,该属性可以接受application、session、request、page和action。如果没有该属性,则默认放在OGNL context中。
    value赋给变量的值,如果没有该属性,则将ValueStack栈顶的值放入赋给变量。

    生成一个map对象:

    <s:set var="maps" value="#{'key1':90,'key2':35,'key3':12}"></s:set>  
    <s:iterator value="#maps">  
        <s:property value="key"/>=<s:property value="value"/><br/>  
    </s:iterator> 

    采用ognl表达式判断对象是否在集合中:对于集合类型,ognl表达式可以使用in和not in两个符号,其中in表示指定的元素是否在集合中,而not in表示指定的元素是否不再集合中。如下所示:

    <s:if test="'foo' not in {'xxx','foo1','foo2'}">  
        不在  
    </s:if>  
    <s:else></s:else> 

    OGNL表达式的投影功能:
    除了in和not in外,ognl还允许使用某个规则获得集合对象的子集,常用的有以下三个操作符:
    ?:获得所有符合逻辑的元素
    ^:获得符合逻辑的第一个元素
    $:获得符合逻辑的最后一个元素
    例如:

    <s:iterator value="books.{?#this.price>60}">  
        <s:property value="name"/>,价格:<s:property value="price"/><br/>  
    </s:iterator>  

    在上面的代码中,直接在集合后跟.{}运算符表明用于取出该集合的子集,{}内的表达式用于获取符合条件的元素,this表示为了从大集合books中筛选数据到小集合,需要对大集合books进行迭代,this代表当前迭代的元素。本例中用与获取集合中价格大于60的书的集合。

    Struts2中标签

    struts2中标签分为通用标签和UI标签,通用标签包含控制标签和数据标签,如下图所示:

                                 

    UI标签包含form标签,非form标签和Ajax标签,如下图所示:

              

    这里有很多的标签,掌握一些常用的,然后在用到的时候查struts2帮助文档。

    例使用<s:token/>标签防止重复提交

    使用<s:token/>标签可以防止重复提交,具体的用户如下:

    1.在表单中加入<s:token/>,例如:

    <s:form action="token" namespace="/test" method="post">  
        姓名:<s:textfield name="name" label="姓名"></s:textfield>  
        <s:token></s:token>  
        <s:submit label="提交"></s:submit>  
    </s:form> 

    2.在action中配置token拦截器,如下所示:

    <action name="token" class="antony.action.PersonAction">  
        <interceptor-ref name="defaultStack"></interceptor-ref>  
        <interceptor-ref name="token"></interceptor-ref>  
        <result name="success">/WEB-INF/page/message.jsp</result>  
        <result name="invalid.token">/index.jsp</result>  
    </action>  

    上面加入了token拦截器和invalid.token result,因为token拦截器在会话状态的token与请求状态的token不一致时,直接返回invalid.token result。

  • 相关阅读:
    CodeForces 173B Chamber of Secrets spfa
    CodeForces 173A Rock-Paper-Scissors 数学
    Codeforces Gym 100803G Flipping Parentheses 线段树+二分
    Codeforces Gym 100803D Space Golf 物理题
    Codeforces Gym 100803F There is No Alternative 暴力Kruskal
    Codeforces Gym 100803C Shopping 贪心
    《白日梦想家》观后感
    select sum也会返回null值
    mysql update更新带子查询的实现方式
    mysql 添加索引 mysql 如何创建索引
  • 原文地址:https://www.cnblogs.com/antonyhubei/p/5515204.html
Copyright © 2020-2023  润新知