美文网首页
2020-12-16

2020-12-16

作者: 免费的午餐 | 来源:发表于2020-12-16 23:57 被阅读0次

    Struts2介绍

    Struts2是在WebWork2基础发展而来的。和Struts1一样,Struts2也属于mvc框架。不过有一点大家要注意的是:尽管Struts2和Struts1名字上相差不大,但是在代码编写风格上几乎是不一样的,那么既然有了Struts1,为何还要推出Struts2.主要是因为Struts2有以下优点:

    1. 在软件设计上Struts2没有像Struts1那样跟Servletapi和Strutsapi有紧密的耦合,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开发环境

    搭建Struts2环境时,我们一般需要做以下几个步骤的工作:

    1.找到开发Struts2应用需要使用到的jar包。

    2.编写Struts2的配置文件。

    3.在web.xml中加入Struts2 mvc框架启动配置。

    搭建Struts2开发环境-----开发Struts2应用依赖的jar文件

    可以到Struts官网查看那些包是必须引入的,下面的这写有点版本老了。

    开发struts2应用需要依赖的jar文件在解压目录的lib文件夹下,不同的应用需要的jar包是不同的,下面给出了开发Struts2程序最少需要的jar。

    Struts2-core-2.x.x.jar: Struts2 框架的核心类库

    xwork-2.x.x.jar : XWork类库,Struts2在其上构建

    ognl-2.6.x.jar:对象图导航语言(object graph navigation language),Struts2框架通过其读写对象的属性。

    freemarker-2.3.x.jar:Struts2的UI标签的模板使用FreeMarker编写。

    commons-logging-1.1.x.jar:ASF出品的日志包,Struts2框架使用这个日志包来支持Log4J和JDK1.4+的日志记录。

    commons-fileupload-1.2.1.jar 文件上传组件,2.1.6版本后必须要加入此文件。

    搭建Struts2开发环境—Struts2应用的配置文件

    Struts2默认的配置文件为struts.xml,该文件需要存放在WEB-INF/classes下,该文件的配置模板如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    
    <!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
        "http://struts.apache.org/dtds/struts-2.3.dtd">
    
    <struts>
    
    <constant name="struts.devMode" value="false" />
    
    <package name="myjson" namespace="/" extends="json-default">
        <action name="transfer" class="cn.itcast.action.TransferAction">
            <result type="json"></result>
        </action>
        <action name="queryOrders" class="cn.itcast.action.QueryOrdersAction">
            <result type="json"></result>
        </action>
    </package>
    
    </struts>
    

    搭建Struts2开发环境—Struts2在web中的启动配置

    在Struts1.x中,Struts框架是通过Servlet启动的。在Struts2中,Struts框架是通过Filter启动的。他在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>
    

    在StrutsPrepareAndExecuteFilter的init()方法中将会读取类路径下默认的配置文件struts.xml完成初始化操作。

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

    Struts.xml配置中等包介绍

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">
    
    <struts>
    
        <constant name="struts.devMode" value="true" />
    
        <package name="basicstruts2" namespace="/test" extends="struts-default">
            <action name="index">
                <result>/index.jsp</result>
            </action>
        </package>
    
    </struts>
    

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

    配置包时必须指定name属性,该属性值可以任意取名,但必须唯一,他对应java的类包,如果其他包要继承该报,必须通过该属性进行引用,包的namespace属性用于定义包的命名空间,命名空间的作为访问该报下的action的路径的一部分。如访问上面的action,访问路径为:/test/index。namespace属性可以不设置,如果不指定该属性,默认的命名空间为“”(空字符串)。

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

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

    第一个struts程序:

    struts.xml配置

    <package name="wgp"  extends="struts-default">
        <action name="helloworld" class="com.wgp.action.HelloWorldAction" method="execute" >
            <result name="success">/WEB-INF/page/hello.jsp</result>
        </action>
    
    </package>
    

    action类:

    package com.wgp.action;
    
    public class HelloWorldAction {
    
        private String message;
    
        public String execute(){
    
            message = "我的第一个struts2应用!";
            return "success";
        }
    
        public String getMessage() {
            return message;
        }
    }
    

    jsp页面:hello.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>第一个struts2应用</title>
    </head>
    <body>
        ${message}
    </body>
    </html>
    

    浏览器的访问路径:http://localhost:8080/struts2demo_war_exploded/test/helloworld

    Action名称的搜索顺序

    1.获得请求路径的URI,例如url是:htttp://server/struts2/path1/path2/path3/test.action

    2.首先寻找namespace为/path1/path2/path3的package,如果没找到则执行步骤3;如果存在这个package,则在这个包中寻找对应的action,当在这个package下找不到action时就会直接跑到默认namespace的package中寻找,默认命名空间为空空字符串,如果默认中还是找不到,页面提示找不到action。

    3.寻找namespace为/path1/path2的package,如果不存在,则转至步骤4;如果存在,则在这个package中训中名字为test的action,当该package寻找不到,则会去默认命名空间的package中寻找action,默认空间找不到,页面提示action找不到。

    4.寻找namespace为/path1的package,如果不存在就执行步骤5;存在则在该package下寻找action,如果找不到action,就去默认空间找action,默认空间找不到,页面就提示该action找不到。

    5.寻找namespace为/的package,如果存在这个package,则在该package下寻找action,如果找不到或不存在该package时,去默认空间的package里面寻找action,如果还是找不到,页面提示找不到该action。

    总结:一个方位url路径,会先去找全路径的package,如果找不到就找上一个路径的package,依次类推,如果都不到就会去默认空间的package中寻找action,默认空间还是找不到,那么页面就会提示找不到该action。

    Action配置中的各项默认值

    1. 如果没有为action指定class,默认是ActionSupport。
    2. 如果没有为action指定method,默认执行action中的execute()方法。
    3. 如果没有指定result的name属性,默认值为success。

    action中的子节点result配置的各种试图转发类型

    struts2中提供了多种结果类型,常用的类型有:dispatcher(默认配置)、redirect(重定向到某个路径)、redirectAction(重定向到某个action)、plainText。

    在result中还可以使用${属性名}表达式访问action中的属性,表达式里的属性名对应action中的属性。如下:

    <result type="redirect">/view.jsp?id=${id}</result>

    下面是redirectAction结果类型的例子,如果重定向的Action中同一个包下:

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

    如果重定向的action在别的命名空间下:

    <result type="redirectAction">

    ​ <param name="actionName">hello world</param>

    ​ <param name="namespace">/test</param>

    </result>

    plaintext:显示原始文件内容,例如:当我们需要原样显示jsp文件源代码的时候,我们可以使用此类型。

    <result name="source" type="plainText">

    ​ <param name="location">/xx.jsp</param>

    ​ <param name="charSet">UTF-8</param>

    </result>

    定义全局消息页面的方式:其他package继承这个base就行了。

    <package name="base" extends="struts-default">
        <global-results>
            <result name="message">/WEB-INF/page/message.jsp</result>
        </global-results>
    </package>
    

    为action属性注入值:

    public class HelloWorldAction {
    
        private String message;
    
        public String execute(){
    
            return "success";
        }
    
        public String getMessage() {
            return message;
        }
    
        //为要注入的属性设置set方法
        public void setMessage(String message) {
            this.message = message;
        }
    }
    
    <package name="wgp" namespace="/test" extends="struts-default">
        <action name="helloworld" class="com.wgp.action.HelloWorldAction" method="execute" >
            <!--为action类中的属性 设置值-->
            <param name="message">这是我第一个struts2程序</param>
            <result name="success">/WEB-INF/page/hello.jsp</result>
        </action>
    
    </package>
    

    指定struts2处理的请求后缀

    我们都是默认使用的.action后缀访问action,其实默认后缀是可以通过常量“struts.action.extension”进行修改的,例如我们可以配置struts2只处理以.do为后缀的请求路径:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE struts PUBLIC
            "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
            "http://struts.apache.org/dtds/struts-2.5.dtd">
    
    <struts>
        <!--如果用户需要指定多个请求后缀,则多个后缀直接以英文逗号分隔-->
        <constant name="struts.action.extension" value="do,go"/>
    <struts>
    

    细说常量定义:

    常量可以定义在struts.xml或者struts.properties中配置,建议在struts.xml中配置,两种配置方式如下:

    在struts.xml文件配置:

    <struts>
    <!--如果用户需要指定多个请求后缀,则多个后缀直接以英文逗号分隔-->
    <constant name="struts.action.extension" value="do,go"/>
    <struts>
    

    在struts.properties中配置常量

    struts.action.extension=do

    因为常量可以在下面多个配置文件中进行定义,所以需要了解struts2加载常量的搜索顺序:

    struts-default.xml

    struts-plugin.xml

    struts.xml

    struts.properties

    web.xml

    如果在多个文件中配置了同一个常量,则后一个文件中配置会覆盖前面文件中配置的常量值。

    常用的常量介绍

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE struts PUBLIC
            "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
            "http://struts.apache.org/dtds/struts-2.5.dtd">
    
    <struts>
    
        <!--常用的常量-->
    
        <!--指定默认编码集,作用于HttpServletRequest的setCharacterEncoding方法和freemarker、velocity的输出,post提交过来的数据-->
        <constant name="struts.custom.i18n.resources" value="UTF-8"/>
    
        <!--该属性指定需要struts2处理的请求后缀,该属性值默认是action,及匹配*.action的请求由struts2处理。
           如果用户需要指定多个请求后缀,则多个后缀之间以英文逗号(,)隔开。
        -->
        <constant name="struts.action.extension" value="do"/>
    
        <!--设置浏览器是否缓存静态内容,默认值为true(生产环境下使用),开发阶段最好关闭-->
        <constant name="struts.serve.static.browserCache" value="false"/>
    
        <!--当struts的配置文件修改后,系统是否自动重新加载该文件,默认值为false(生产环境下使用),开发阶段最好打开-->
        <constant name="struts.configuration.xml.reload" value="true"/>
    
        <!--开发模式下使用,这样可以打印出更详细的错误信息-->
        <constant name="struts.devMode" value="true" />
    
        <!--默认的视图主题-->
        <constant name="struts.ui.theme" value="simple"/>
    
        <!--与spring集成时,指定由spring负责action对象的创建-->
        <constant name="struts.objectFactory" value="spring"/>
    
        <!--该属性设置struts2是否支持动态方法调用,该属性的默认值是true。
        如果需要关闭动态方法调用,则可设置属性为false。-->
        <constant name="struts.enable.DynamicMethodInvocation" value="false"/>
    
        <!--上传文件的总大小限制-->
        <constant name="struts.multipart.maxSize" value="10701096"/>
        
    <struts>
    

    struts2的处理流程

    [图片上传失败...(image-d688f5-1608134184287)]

    为应用指定多个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.5//EN"
            "http://struts.apache.org/dtds/struts-2.5.dtd">
    
    <struts>
            <include file="struts-user.xml"/>
            <include file="struts-order.xml"/>
    <struts>
    

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

    动态方法调用和使用通配符定义action

    动态方法调用有两种方式:

    1.如果action中存在多个方法时,我们可以使用!+方法名调用指定方法。官方不推荐!!

    2.使用通配符定义action

    <package name="wgp" namespace="/test" extends="struts-default">
        <action name="helloworld_*" class="com.wgp.action.HelloWorldAction" method="{1}" >
            <result name="success">/WEB-INF/page/hello.jsp</result>
        </action>
    
    </package>
    
    public class HelloWorldAction{
        private String message;
        ...
        public String execute()throws Exception{
            this.message = "我的第一个struts2程序";
        }
        
        public String other()throws Exception{
            this.message = "第二个方法";
            return "success";
        }
    }
    

    要访问other()方法,可以通过这样的url方法:/test/helloworld_other.action或/test/helloworld_other;

    接收请求参数

    采用基本数据类型接收参数(get/post)

    在action类中定义与请求参数同名的属性,struts2便能自动接收请求参数并赋予给同名属性。

    请求路径:http://localhost:8080/test/view.action?id=78
    public class ProductAction{
        private Integer id;
        
        //struts2通过反射技术调用与请求参数同名的属性的setter方法来设置获取到的请求参数。
        public void setId(Integer id){
            this.id = id;
        }
        public Integer getId(){
            return id;
        }
    }
    

    采用复合类型接收请求参数

    请求路径:http://localhost:8080/test/view.action?product.id=78
    
    public class ProductAction{
        private Product product;
        public void setProduct(Product product){
            this.product=product;
        }
        public Product getProduct(){
            return product;
        }
    }
    //struts2首先通过反射接收调用Product的默认构造器创建product对象,然后再通过发射技术调用product中与请求参数同名的属性的setter方法来设置获取到的请求参数。
    

    关于struts2.1.6接收中文请求参数乱码问题

    Struts2.1.6版本中存在一个bug,接收到的中文请求参数为乱码(post方式提交),原因是该版本在获取并使用了请求参数后才调用HttpServletRequest的setCharacterEncoding()方法进行编码设置,导致应用使用的就是乱码请求参数。要在该版本中解决这个问题,我们可以这样做:定义一个Filter过滤器,把我们自定义的过滤器放在struts2的过滤器之前。

    自定义类型转换器

    当我们当action中有个Date类型的属性 ,需要接受一个那么当我们客户端传过来的是“20181221”这样一个字符串就会报错,这时我们就需要自定义类型转换器,把该字符串转换成Date类型。

    类型转换器作用范围分为局部类型转换器(只作用于action)和全局类型转换。

    自定义类型转换器:

    //自定义日期转换器
    public class DateConverter extends DefaultTypeConverter {
        @Override
        public Object convertValue(Map<String, Object> context, Object value, Class toType) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
            try {
                if (toType== Date.class){//当要转换的类型是Date类型
                    //value是Request.getParameterValues();
                    String[] params = (String[]) value;
                    return dateFormat.parse(params[0]);
                }else if (toType==String.class){//当要转换成的类型是字符串时
                    Date date = (Date) value;
                    return dateFormat.format(date);
                }
            }catch (ParseException e){
                e.printStackTrace();
            }
            return null;
    
        }
    }
    

    局部类型转换器的配置

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

    在action类所在的包下创建一个ActionClassName(action的类名)-conversion.properties文件,ActionClassName是action的类名,后面的-conversion.properties是固定写法。

    在properties文件中的内容为:

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

    自定义全集类型转换器的配置

    在WEB-INF/classes下放置xwork-conversion.properties文件,在properties文件中内容为:待转换的类型=类型转换器的全类名

    例如:java.util.Date=com.wgp.conversion.DateConverter

    访问或添加Request、Session、application属性

    ActionContext ctx = ActionContext.getContext();
    ctx.getApplication().put("app","应用范围");//ServletContext域中
    ctx.getSession().put("ses","sesssion范围");//session域中
    ctx.put("req","request范围");//Request域中
    
    <!--页面中获取域中属性,用之前学习javaweb时的正常方式也是可以拿到的-->
    ${applicationScope.app}
    ${sesssionScope.ses}
    ${requestScope.req}
    

    获取HttpServletRequest、HttpSession、ServletContext、HttpServletRequest对象

    两种方式:

    方式一,通过ServletActionContext类直接获取

    //获取Request对象
    HttpServletRequest request = ServletActionContext.getRequest();
    //获取ServletContext对象
    ServletContext servletContext = ServletActionContext.getServletContext();
    //获取Session对象
    request.getSession()
    //获取Response对象
    HttpServletResponse response = ServletActionContext.getResponse();
    
    

    方式二,实现指定接口,有struts框架运行时自动注入

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

    文件上传

    第一步:在WEB-INF/lib下加入commons-fileupload.jar、commons-io.jar。这两个包可以去Apache官网下载。

    第二步:把form表单的enctype设置为:“multipart/form-data”

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>文件上传</title>
    </head>
    <body>
        <form enctype="multipart/form-data" action="${pageContext.request.contextPath}/test/fileUpload" method="post">
            文件:<input type="file" name="uploadImage"><br>
            <input type="submit" value="上传">
    
        </form>
    </body>
    </html>
    

    第三步:在action类中添加以下属性,属性部分对应表单中文件字段的名称

    public class FileUploadAction {
    
        //得到上传的文件
        /**
         * 该字段名称必须和页面上传的文件的参数名称一致,这样struts框架就会自动接收该文件
         * 必须设置该字段的setter方法,
         */
        private File uploadImage;
        //得到文件的类型
        /**
         * 上传文件的类型,struts框架会自动帮我们获取文件的MIME类型,
         * 该字段的命名规则是 "提交的文件字段名+ContentType" 固定写法,必须设置该字段的setter方法
         */
        private String uploadImageContentType;
        //得到文件的名称
        /**
         * 上传文件的文件名,带后缀的文件名。
         * 该字段struts框架会帮我们字段获取并设置上信息,该字段也必须设置setter方法
         * 该字段的命名规则是 "提交的文件字段名+FileName" 固定写法
         */
        private String uploadImageFileName;
    
        public File getUploadImage() {
            return uploadImage;
        }
    
        public void setUploadImage(File uploadImage) {
            this.uploadImage = uploadImage;
        }
    
        public String getUploadImageContentType() {
            return uploadImageContentType;
        }
    
        public void setUploadImageContentType(String uploadImageContentType) {
            System.out.println(uploadImageContentType);
            this.uploadImageContentType = uploadImageContentType;
        }
    
        public String getUploadImageFileName() {
            return uploadImageFileName;
        }
    
        public void setUploadImageFileName(String uploadImageFileName) {
            System.out.println(uploadImageFileName);
            this.uploadImageFileName = uploadImageFileName;
        }
    
        public String upload()throws Exception{
            ServletContext servletContext = ServletActionContext.getServletContext();
            String realPath = servletContext.getRealPath("/images");
            File file = new File(realPath);
            if (!file.exists()){//如果该目录不存在就创建
                file.mkdirs();
            }
            FileUtils.copyFile(uploadImage,new File(file,uploadImageFileName));
            return "success";
        }
    }
    
    

    多文件上传:对应的字段改成数组类型或集合类型就可以自动接收多文件上传

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>多文件上传</title>
    </head>
    <body>
        <form enctype="multipart/form-data" action="${pageContext.request.contextPath}/test/fileUpload" method="post">
            文件1:<input type="file" name="uploadImage"><br>
            文件2:<input type="file" name="uploadImage"><br>
            文件3:<input type="file" name="uploadImage"><br>
            <input type="submit" value="上传">
    
        </form>
    </body>
    </html>
    
    public class FileUploadAction {
    
        //得到上传的文件
        /**
         * 该字段名称必须和页面上传的文件的参数名称一致,这样struts框架就会自动接收该文件
         * 必须设置该字段的setter方法,
         */
        private File[] uploadImage;
        //得到文件的类型
        /**
         * 上传文件的类型,struts框架会自动帮我们获取文件的MIME类型,
         * 该字段的命名规则是 "提交的文件字段名+ContentType" 固定写法,必须设置该字段的setter方法
         */
        private String[] uploadImageContentType;
        //得到文件的名称
        /**
         * 上传文件的文件名,带后缀的文件名。
         * 该字段struts框架会帮我们字段获取并设置上信息,该字段也必须设置setter方法
         * 该字段的命名规则是 "提交的文件字段名+FileName" 固定写法
         */
        private String[] uploadImageFileName;
    
        public File[] getUploadImage() {
            return uploadImage;
        }
    
        public void setUploadImage(File[] uploadImage) {
            this.uploadImage = uploadImage;
        }
    
        public String[] getUploadImageContentType() {
            return uploadImageContentType;
        }
    
        public void setUploadImageContentType(String[] uploadImageContentType) {
            this.uploadImageContentType = uploadImageContentType;
        }
    
        public String[] getUploadImageFileName() {
            return uploadImageFileName;
        }
    
        public void setUploadImageFileName(String[] uploadImageFileName) {
            this.uploadImageFileName = uploadImageFileName;
        }
    
        public String upload()throws Exception{
            ServletContext servletContext = ServletActionContext.getServletContext();
            String realPath = servletContext.getRealPath("/images");
            File file = new File(realPath);
            if (!file.exists()){//如果该目录不存在就创建
                file.mkdirs();
            }
            for (int i = 0; i < uploadImage.length; i++) {
                FileUtils.copyFile(uploadImage[i],new File(file,uploadImageFileName[i]));
            }
            return "success";
        }
    }
    

    自定义拦截器

    我们可以用拦截器去进行权限拦截等功能。

    要自定义拦截器需要实现com.opensymphony.xwork2.interceptor.Interceptor这个接口。

    示例:这个种方式把struts自带的拦截器都给屏蔽了,这样不好,我们还需要使用到struts自带的核心功能

    public class PermissionInterceptor implements Interceptor {
        @Override
        public void destroy() {
    
        }
    
        @Override
        public void init() {
    
        }
    
        @Override
        public String intercept(ActionInvocation actionInvocation) throws Exception {
              //在该方法中做拦截处理
         Map<String, Object> session = ActionContext.getContext().getSession();
            Object user = session.get("user");
            if (user!=null){
                //actionInvocation.invoke() 这个方法就是调用的action中的用户调用的方法,我猜是用动态代理搞的
                String invoke = actionInvocation.invoke();//放行 用户调用的方法
                return invoke;//吧方法返回的字符串返回
            }
    
    
            return "failed";//用户没有权限
        }
    }
    
    <package name="wgp2" namespace="/test"  extends="struts-default">
        <!--拦截器配置-->
        <interceptors>
            <interceptor name="permission" class="com.wgp.action.PermissionInterceptor"/>
        </interceptors>
    
        <action name="fileUpload" class="com.wgp.action.FileUploadAction" method="upload">
            <!--为这个action配置拦截器-->
            <interceptor-ref name="permission"></interceptor-ref>
            <result name="success">/WEB-INF/page/hello.jsp</result>
        </action>
    
    </package>
    

    struts本身的默认的拦截和我们自定义的拦截器都使用上:

    示例:

    只需要更改配置struts配置文件

    <package name="wgp2" namespace="/test"  extends="struts-default">
        <!--拦截器配置-->
        <interceptors>
            <!--配置自定义的拦截器-->
            <interceptor name="permission" class="com.wgp.action.PermissionInterceptor"/>
            
            <!--第定义一个拦截器栈-->
            <interceptor-stack name="permissionStack">
                <!--添加struts的默认拦截器,这行代码要在自己的自定义的拦截器之前-->
                <interceptor-ref name="defaultStack"/>
                <!--添加自己定义的拦截器-->
                <interceptor-ref name="permission"/>
            </interceptor-stack>
        </interceptors>
    
        <action name="fileUpload" class="com.wgp.action.FileUploadAction" method="upload">
            <!--为这个action配置拦截器,引用拦截器栈-->
            <interceptor-ref name="permissionStack"/>
            <result name="success">/WEB-INF/page/hello.jsp</result>
        </action>
    
    </package>
    

    当我自定义拦截器的时候,在struts的配置文件中配置了我们的自定义的拦截器,那么struts框架的默认拦截器就不会再起作用了,我们需要显示的在配置文件添加上struts框架的默认拦截器,因为struts2中如文件上传、数据验证、获取客户端提交的参数、国际化等,都是struts2的默认拦截器帮我们做的,如果我们不引用就不能用这些功能了。系统默认的拦截的名字是defaultStack

    如果希望包下的所有action都使用自定义拦截器,可以通过<default-interceptor-ref name="permissionStack"/>把拦截器定义为默认拦截器。注意:每个包智能指定一个默认拦截器,另外,一旦我们为该包中的某个action显式指定了某个拦截器,则默认拦截器不会起作用了。如果即想让默认拦截器permissionStack起作用 还要给action配置另一个拦截器,那么可以在action标签内配置多个拦截器标签指定。

    输入校验

    在struts2中,我们可以实现对aciton的所有方法进行校验或者action的指定方法进行校验。

    对于输入校验,struts2提供了两种实现方法:

    1.采用手工编写代码实现。

    2.基于xml配置方式实现。

    手工代码实现方式

    通过重新validate()方法实现,validate()方法会校验action中所有与execute方法签名相同的方法。当狗哥数据校验失败时,我们应该调用addFieldError()方法往系统的fieldErrors添加校验失败信息(为了使用addFieldError()方法,action可以继承ActionSupport),如果系统的fieldErrors包含失败信息,struts2会将请求转发到名为input的result。在input试图中可以通过< s:fielderror />显示失败信息。

    示例:该示例中对action中的所有方法都会执行校验

    //继承ActionSupport类
    public class PersonAction extends ActionSupport {
        private String username;
        private String mobile;
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getMobile() {
            return mobile;
        }
    
        public void setMobile(String mobile) {
            this.mobile = mobile;
        }
    
        public String update(){
            ActionContext.getContext().put("message","更新成功");
            return "success";
        }
        public String save(){
            ActionContext.getContext().put("message","保存成功");
            return "success";
        }
    
        /**
        *这个方法进行自动校验的,我们在复写这个方法来进行判断
        */
        @Override
        public void validate() {
            super.validate();
            if (this.username==null || "".equals(username.trim())){
                addFieldError("username","用户名不能空!");
            }
    
            if (this.mobile==null || "".equals(mobile.trim())){
                addFieldError("mobile","手机号不能为空!");
            }else {
                if (!Pattern.compile("^1[358]\\d{9}$").matcher(mobile).matches()){
                    addFieldError("mobile","手机号格式不对!");
    
                }
            }
        }
    }
    
    <package name="wgp" namespace="/test" extends="struts-default">
        <action name="person_*" class="com.wgp.action.PersonAction" method="{1}" >
            <!--验证失败后,请求转发到input试图-->
            <result name="input">/user.jsp</result>
            <result name="success">/WEB-INF/page/hello.jsp</result>
        </action>
    
    </package>
    
    <%@ taglib prefix="s" uri="/struts-tags" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>输入校验</title>
    </head>
    <body>
        <%-- 在该页面中使用<s:fielderror /> 标签来显示失败信息--%>
    <s:fielderror />
     <form action="${pageContext.request.contextPath}/test/person_save" method="post" >
         用户名:<input type="text" name="username"><br>
         手机号:<input type="text" name="mobile"><br>
         <input type="submit" value="提交">
     </form>
    </body>
    </html>
    

    对action指定方法校验

    通过validateXxx()方法实现,validateXxx()方法只会校验action中的方法名为xxx的方法。其中Xxx第一个字母要大写,当某个数据校验失败时,我们应该调用addFieldError()方法往系统的fieldErrors添加校验失败信息,如果系统的fieldError包含了失败信息,struts2会将请求转发到名为input的result标签。在input视图中可以通过< s:fielderror />显示失败信息。

    示例:如上面的validate方法改成validateUpdate(),那么就会只对访问update()方法时才会进行校验。

    输入校验的流程

    1. 类型转换器对请求参数执行类型转换,并把转换后的值赋给action中的属性。
    2. 如果在执行类型转换的过程中出现了异常,系统会将异常信息保存到ActionContext,conversionError拦截器将异常信息添加到fieldErrors里,不管类型转换是否出现异常,都会进入第3步
    3. 系统通过发射技术先调用action中的validateXxx()方法,Xxx为方法名。
    4. 再调用action中的validate()方法。
    5. 经过上面4步,如果系统中的fieldErrors存在错误信息,系统会自定将请求转发至名称为input视图。如果系统中的fieldErros没有任何错误信息,系统将执行action中的处理方法。

    XML配置方式实现action的所有方法进行输入校验

    使用xml配置方式实现输入校验时,Action也需要继承ActionSupport,并且提供校验文件,校验文件和action类放在同一个包下,文件的取名格式为:ActionClassName-validation.xml,其中ActionClassName为action的简单类名,-validation为固定写法。如果action类为com.wgp.UserAction,那么该文件的取名应为:UserAction-validation.xml。

    下面是校验文件的模板:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE validators PUBLIC
            "-//Apache Struts//XWork Validator 1.0.3//EN"
            "http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
     
    <validators>
        <field name="username">
            <field-validator type="requiredstring">
                <!--高版本不用配置该属性 默认就是去除空格的-->
                <param name="trim">true</param>
                <message>用户名不能为空!</message>
            </field-validator>
            
            <field-validator type="regex">
                <!--这个regexExpression 其实就是regex这个自带标签对应的类的属性-->
                <param name="regexExpression"><![CDATA[^1[358]\d{9}$]]></param>
                <message>手机格式不正确!</message>
            </field-validator>
            
        </field>
    </validators>
            
    

    < field>指定action中要校验的属性,<field-validator>指定校验器,上面指定的校验器requiredstring是由系统提供的,系统提供了能满足大部分校验需求的校验器,这些校验器的定义可以在xwork-2.x.jar中com.opensymphony.xwork2.validator.validators下的default.xml中找到。高版本的比如struts2.5版本的中吧xwork2包集成到了struts2-core-2.5.18.jar包中。

    <message>为校验失败后的提示信息,如果需要国际化,可以为message指定key属性,key的值为资源文件中的key。

    在这个校验文件中,对action中字符串类型的username属性进行验证,首先要求调用trim()方法去掉空格,然后判断用户名是否为空。

    [图片上传失败...(image-96e0c6-1608134184287)]

    基于XML配置方式对action类中指定的方法实现输入校验

    如果想要对action中的某个action方法实施校验,那么,校验文件的取名应为:ActionClassName-ActionName-validation.xml,其中ActionName为struts.xml中action的名称。

    示例:

            <action name="person_*" class="com.wgp.action.PersonAction" method="{1}" >
                <result name="input">/user.jsp</result>
                <result name="success">/WEB-INF/page/hello.jsp</result>
            </action>
    

    PersonAction中有以下两个方法:

    public String add(){}

    Public String update(){}

    要对add()方法实施验证,校验文件的取名为:UserAction-person_add-validation.xml

    要对update()方法实施验证,校验文件取名为:UserAction-person_update-validation.xml

    基于XML校验的一些特点

    当为某个action提供了ActionClassName-validation.xml和ActionClassName-ActionName-validation.xml两种规则的校验文件时,系统按下面顺序寻找校验文件:

    先找ActionClassName-validation.xml ,然后再找ActionClassName-ActionName-validation.xml。

    系统找到第一个校验文件时会继续找后面的校验文件,当搜索到所有校验文件时,会吧校验文件里的所有校验规则汇总,然后全部应用于action方法的校验。如果两个校验文件中指定的校验规则冲突,则只使用后面文件的校验规则。

    当action继承了另一个action,父类action的校验文件会先被搜索到。也遵循以上规则,也就是说把父类的校验文件和子类的校验文件汇总,然后应用action的方法的校验。有冲突的后面的覆盖前面的。

    国际化

    Struts2 提供了全局范围的、包范围的、action范围的国际化文件。

    准备资源文件,资源文件的命名格式如下:

    baseName_language_country.properties

    baseName_language.properties

    baseName.properties

    其中baseName是资源文件的基本名,我们可以自定义,但language和country必须是java支持的语言和国家。

    如: 中国大陆:baseName_zh_CN.properties 美国:baseName_en_US.properties

    现在为应用添加两个资源文件:

    第一个存放中文:wgp_zh_CN.properties 内容为:welcome=欢迎

    第二个放英语(美国):wgp_en_US.properties 内容为:welcome=welcome

    对于中文的属性文件,我们编写好后,应该使用jdk提供的native2ascii命名把文件转换为unicode编码的文件。命令的使用方式如下:native2ascii 源文件.properties 目标文件.properties

    这些属性文件放在和struts.xml同目录下。

    配置全局资源与输出国际化信息

    当准备好资源文件后,我们可以在struts.xml中通过<constant name="struts.custom.i18n.resources" value="wgp"/> wgp为资源文件的基本名。

    后面我们就可以在页面或action中访问国际化信息:

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

    国际化:输出带有占位符的国际化信息

    资源文件中的内容如下:welcome={0},欢迎来到中国{1}

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

    <s:text name="welcome">

    ​ < s:param> < s:property value="realname" /> </ s:param>

    ​ < s:param> 学习</ s:param>

    </s:text >

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

    国际化:包范围资源文件

    在一个大型项目中,整个应用有大量的内容需要实现国际化,如果我们把国际化的内容都放在全局的资源属性文件中,显然会导致资源文件过于庞大臃肿,不便于维护,这个时候我们可以只对不同模块,使用包范围来组织国际化文件。

    方法如下:

    在java的包下放置packege_language_country.properties资源文件,package为固定写法,处于该包及子包下的action都可以访问该资源文件。当炒作指定key的消息时,系统会先从package资源文件中查找,当找不到对应的key时,才会从常量<constant name="struts.custom.i18n.resources" value="wgp"/>指定的资源文件中查找。

    国际化:Action范围的资源文件

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

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

    <constant name="struts.custom.i18n.resources" value="wgp"/>指定的资源文件中寻找。

    OGNL表达式语言

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

    对于el表达式,ognl提供了平时我们需要的一些功能更,如:

    • 支持对象方法调用,如 xxx.sayHello();
    • 支持类静态方法调用和值访问,表达式的格式为:@[类全名(包括包路径)] @[方法名 | 值名],例如:@java.lang.String@format('foo %s','bar')或@com.wgp.Constant@APP_NAME;
    • 操作集合对象。

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

    [图片上传失败...(image-3c6ee2-1608134184288)]

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

    另外ognl会设定一个根对象(root对象),在Struts2中根对象就是ValueStack(值栈),如果要访问跟对象也就是值栈中的对象的属性,则可以省略#命名空间,直接访问该对象的属性即可。

    在Struts2中,根对象ValueStack的实现类为OgnlValueStack,该对象不是我们想象的只存放单个值,而是存放一组对象。在OgnlValueStack类里面有一个List类型的root变量,就是使用他存放一组对象。

    在root变量中处于第一位的对象叫栈顶对象。通常我们在ognl表达式里直接写上属性的名称即可访问root变量里对象的属性,搜索顺序是从栈顶对象开始寻找,如果栈顶对象不存在该属性,就会从第二个对象寻找,如果没有找到就从第三个对象寻找,依次往下访问,直到找到为止。

    注意:在Struts2中,ognl表达式需要配合Struts标签才可以使用。如:<s:property value="name" />、<s:property value="#request.user" />,获取request域对象中user属性

    由于ValueStack是Struts2中ognl的根对象,如果用户需要范围值栈中的对象,在jsp页面中可以直接通过下面的el表达式访问ValueStack中的对象的属性:${foo} //获得值栈中某个对象的foo属性

    如果访问其他的Context中的对象,由于他们不是根对象,所以在访问时,需要添加#前缀。

    • application对象:用于访问ServletContext,例如#application.username或者#application['username'],相当于调用了ServletContext的getAttribute("username")。
    • session对象:用来访问HttpSession,例如#session.username 或者#session['username'],相当于调用了session.getAttribute("username")。
    • request对象:用来访问HttpServletRequest属性(attribute)的map,例如#request.username或者#request['username'],相当于调用了request.getAttribute("username")。
    • parameters对象:用于访问Http的请求参数,例如#parameters.username或者#parameters['username'],相当于调用了request.getParameter("username")。
    • attr对象:用于按page->request->session->application顺序访问期属性。

    之前我们写的action类里面的属性生成setter和getter方法后,jsp页面可以用el表达获取到属性值呢?,我们知道el表达式是获取域对象中的属性的${name}就相当于域对象调用getAttribute("name"),那么为什么Struts框架中的action中属性也可以被el表达式获取呢,是因为action是被放到valueStack中的,而Struts框架对HttpServletRequest进行了进一步的封装,

    为什么使用EL表达式能够范围valueStack中的对象属性呢?

    [图片上传失败...(image-e67666-1608134184288)]

    采用ONGL表达式创建List/Map集合对象

    如果需要一个集合元素的时候,我们可以使用ognl中同集合相关的表达式。使用如下代码直接生产一个List对象:

    <s:set name="list" value="{'zhang','wang','li'}" />

    <s:iterator value="#list">

    ​ <s:property />

    </s:iterator >

    Set标签用于将某个值放入指定范围。

    scope:指定变量被放置的范围,该属性可以接受application、session、request、page或action。如果没有指定该属性,则默认放置在OGNLContext中。

    value:赋给变量的,如果没有设置该属性,则将ValueStack栈顶的值赋值给变量。

    生成一个Map对象:

    <s:set name="foobar" value="#{'foo1':'bar1','foo2':'bar2'}" />

    <s:iterator value="#foobar">

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

    </s:iterator >

    property标签

    property标签用于输出指定值:

    <s:set name="name" value="kk" />

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

    default:可选属性,如果需要输出的属性值为null,则显示属性指定的默认值。

    escape:可选属性,指定是否格式化HTML代码。

    value:可选属性,指定需要输出的属性值,如果没有指定该属性,则默认输出ValueStack栈顶的值。

    id: 可选属性,指定该元素的标识。

    采用OGNL表达式判断对象是否存在集合中

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

    in表达式:

    <s:if test=" 'foo' in {'foo','bar'} ">

    ​ 在

    </s:if >

    <s:else >

    ​ 不在

    </ s:else>

    not in 表达式:

    <s:if test=" 'foo' not in {'foo','bar'} ">

    ​ 不在

    </s:if >

    <s:else >

    ​ 在

    </ s:else>

    OGNL表达式的投影功

    除了in和not in之外,ognl还允许使用某个规则获得集合对象的子集,常用的有一下3个相关操作符。

    ?: 获得所有符号逻辑的元素

    ^: 获得符合路径的第一个元素

    $: 获得符合逻辑的最后一个元素

    例如代码:

    <s:iterator value="books.{?#this.price >35}">

    ​ <s:property value="title" /> -$<s:property value="price" />

    </ s:iterator>

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

    public class BookAction extends ActionSupport{
        private List<Book>  books
        //省略 books的setter和getter方法
        public String excute(){
            books = new LinkedList<Book>();
            books.add(new Book("A123","spring"),67)
            books.add(new Book("A456","ejb3.0"),15)
        }
    }
    

    OGNL表达式使用比较少,我们完全可以使用jstl和el表达式想以前学习javaweb一样,

    Struts2常用标签

    iterator标签用于对集合进行迭代,包含list、set和数组。

    value:可选属性,指定被迭代的集合,如果没有设置该属性,则使用ValueStack栈顶的集合。

    id:可选属性,指定集合里元素的id(已过时)

    status:可选属性,该属性指定迭代是的IteratorStatus实例,该实例包含如下几个方法:

    ​ int getCount() :返回当前迭代了几个元素

    ​ int getIndex(): 返回当前迭代元素的索引

    ​ boolean isEven(): 返回当前被迭代元素的索引是否是偶数

    ​ boolean isOdd(): 返回当前被迭代元素的索引是否是奇数

    ​ boolean isFirst(): 返回当前被迭代元素是否是第一个元素

    ​ boolean isLast(): 返回当前被迭代元素是否是最后一个元素

    url标签

    <s:url action="hello_add" namspace="/test" >

    ​ <s:param name="personid" value="23"/>

    </ s:url>

    生成的路径是/struts/test/hello_add.action?personid=23

    当标签的属性值作为字符串类型处理时,”%“符号的用途是计算ognl表达式的值。

    <s:set name="myurl" value=" 'http://www.baidu.com'" />

    <s:url value="#myurl" /> 输出结果是:#myurl

    <s:url value="%{#myurl}" /> 输出结果是:http://www.baidu.com

    表单标签—chackboxlist复选框

    如果集合为list

    <s:checkboxlist name="list" list="{'java','.net','php'}" value="{'java','.net'}" />

    上面这句代码会生成如下HTML代码:

    <input type="checkbox" name="list" value="java" checked="checked" /><label>java</label>

    <input type="checkbox" name="list" value=".net" checked="checked" /><label>.net</label>

    <input type="checkbox" name="list" value="php" /><label>php</label>

    如果集合是Map

    那么<s:checkboxlist name="map" list="#{1:'瑜伽用品', 2:'户外用品', 3:'球类',4:'自行车'}" listKey="key" listValue="value" value="{1,2,3}" />

    生成的html代码如下:

    <input type="checkbox" name="map" value="1" checked="checked" /><label>瑜伽用品</label>

    <input type="checkbox" name="map" value="2" checked="checked" /><label>户外用品</label>

    <input type="checkbox" name="map" value="3" checked="checked" /><label>球类</label>

    <input type="checkbox" name="map" value="4" /><label>自行车</label>

    如果集合里存放的是javabean

    <%

    ​ Person p1 = new Person(1,"第一个");

    ​ Person p2 = new Person(2,"第二个");

    ​ list.add(p1);

    ​ list.add(p2);

    ​ request.setAttribute("persons",list);

    %>

    那么<s:checkboxlist name="beans" list="#request.persons" listKey="personid" listValue="name" />

    personid和name为Person的属性

    会生成如下html代码:

    <input type="checkbox" name="beans" value="1" /><label>第一个</label>

    <input type="checkbox" name="beans" value="2" /><label>第二个</label>

    表单标签—radio单选框

    该标签的使用和CheckBoxlist复选框相同。

    表单标签-select下拉框

    <s:select name="list" list="{'java','.net'}" value="java" />

    生成的html页面代码:

    <select name="list" id ="list">
        <option value="java" selected="selected">java</option>
         <option value=".net" >.net</option>
    </select>
    

    如果集合里是javabean数据或map数据 都是类似的。

    防止表单重复提交<s:token />

    <s:token />标签防止重复提交,用法如下:

    第一步:在表单中加入<s:token />

    <s:form action="hello_add" method="post" namespace="/test">

    ​ <s:textfield name="person.name"/>

    ​ <s:token />

    ​ <s:submit />

    </s:form >

    第二步:

    <action name="hello_*" class="com.wgp.HelloAction" method="{1}">

    ​ <interceptor-ref name="defaultStack" />

    ​ <interceptort-ref name="token" />

    ​ <result name="invalid.token">/WEB-INF/page/message.jsp</result>

    ​ <result>/WEB-INF/page/result.jsp</result>

    </ation>

    以上配置加入了token拦截器和 invalid.token结果,因为token拦截器在会话的token与请求的token不一致时,将会直接返回invalid.token结果。

    在debug状态,控制台出现错误信息,因为Action中并没有Struts.token和Struts.token.name属性,我们不用关心这个错误。

    Struts2+Spring+Hibernate整合

    先集成spring,再集成hibernate,最后Struts。

    相关文章

      网友评论

          本文标题:2020-12-16

          本文链接:https://www.haomeiwen.com/subject/holagktx.html