2020-12-16

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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • 详谈 Struts2 的核心概念 本文将深入探讨Struts2 的核心概念,首先介绍的是Struts2 的体系结构...
    可爱傻妞是我的爱阅读 1,077评论 0 2
  • 标签 如果要配置的标签,那么必须要先配置标签,代表的包的概念。 包含的属性 name包的名称,要求是唯一的,管理a...
    偷偷得路过阅读 1,171评论 0 0
  • ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■↓↓↓↓↓↓↓↓↓ Struts2...
    _Levi__阅读 543评论 1 0
  • 一、struts2的执行流程 先来了解一下struts1的执行流程: Struts1运行原理:(了解) Strut...
    聂叼叼阅读 556评论 1 3
  • 非本人总结的笔记,抄点笔记复习复习。感谢传智博客和黑马程序猿记笔记啊记笔记 结果页面的设置 在action标签里面...
    键盘瞎阅读 526评论 2 4