Struts2【开发Action】

AUTOSAR AP——SM模块 对AUTOSAR AP中状态管理模块SM,从模块定义、总结等多方面进行了介绍。 阅读详情

前言

前面Struts博文基本把Struts的配置信息讲解完了…..本博文主要讲解Struts对数据的处理

Action开发的三种方式

在第一次我们写开发步骤的时候,我们写的Action是继承着ActionSupport类的…为啥我们继承了ActionSupport类呢?下面我就会讲解到

继承ActionSupport类

我们来看一下ActionSupport干了什么:

640?wx_fmt=png
这里写图片描述

也就是说,如果我们在Action类中需要用到Struts为我们提供的数据校验等Struts已经帮我们实现的功能,我们就继承着ActionSupport类..


实现Action接口

我们再来看看Action接口干了什么:

640?wx_fmt=png
这里写图片描述

当然啦,ActionSuppot也继承着Action接口,所以ActionSuppot拥有Action接口的全部功能….因此,这种开发方式我们是比较少用的…


不继承任何类、不实现任何接口

开发此类的Action,它是不继承任何类、不实现任何接口的…也就是说,它就是一个普通的Java类….

public class PrivilegeAction  {    public String login() {        System.out.println("我是普通的javaAction,不继承任何的类、不实现任何的接口");        return "success";    }}class PrivilegeAction  {


   public String login() {
       System.out.println("我是普通的javaAction,不继承任何的类、不实现任何的接口");

       return "success";
   }
}
<struts><package name="privilige" extends="struts-default">    <action name="login" class="privilegeaction.PrivilegeAction" method="login">        <result name="success">/index.jsp</result>    </action></package></struts>
<package name="privilige" extends="struts-default">
   <action name="login" class="privilegeaction.PrivilegeAction" method="login">
       <result name="success">/index.jsp</result>
   </action>
</package>
</struts>
640?wx_fmt=png
这里写图片描述

小总结

请求数据封装

一般地,我们使用Servlet的时候都是分为几个步骤的:

  1. 得到web层的数据、封装数据

  2. 调用service层的逻辑业务代码

  3. 将数据保存在域对象中,跳转到对应的JSP页面

现在问题来了,我们自己编写的Action类是没有request、response、Session、application之类的对象的….我们是怎么得到web层的数据、再将数据存到域对象中的呢??

前面已经说过了,Struts预先帮我们完成了对数据封装的功能,它是通过params拦截器来实现数据封装的

            <interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/>

register.jsp

首先,我们填写表单页面的数据,请求Action处理数据

<form action="${pageContext.request.contextPath}/date01" method="post">    用户名:<input type="text" name="username"><br>    密码:<input type="text" name="psd"><br>    年龄:<input type="text" name="age"><br>    生日:<input type="text" name="birthday"><br>    <input type="submit" value="注册"><br></form>
   用户名:<input type="text" name="username"><br>
   密码:<input type="text" name="psd"><br>
   年龄:<input type="text" name="age"><br>
   生日:<input type="text" name="birthday"><br>
   <input type="submit" value="注册"><br>
</form>

Action封装基本信息

在Action设置与JSP页面相同的属性,并为它们编写setter方法

    private String username;    private String psd;    private int  age;    private Date birthday;    public void setUsername(String username) {        this.username = username;    }    public void setPsd(String psd) {        this.psd = psd;    }    public void setAge(int age) {        this.age = age;    }    public void setBirthday(Date birthday) {        this.birthday = birthday;    }
   private String psd;
   private int  age;
   private Date birthday;

   public void setUsername(String username) {
       this.username = username;
   }

   public void setPsd(String psd) {
       this.psd = psd;
   }

   public void setAge(int age) {
       this.age = age;
   }

   public void setBirthday(Date birthday) {
       this.birthday = birthday;
   }

我们直接在业务方法中访问这些变量,看是否能得到表单的值。

640?wx_fmt=png
这里写图片描述

Action封装对象

一般地,我们注册的时候,都是在Servlet上把基本信息封装到对象上…那么在Struts怎么做呢?

package qwer;import java.util.Date;/** * Created by ozc on 2017/4/27. */public class User {    private String username;    private String psd;    private int  age;    private Date birthday;    public String getUsername() {        return username;    }    public void setUsername(String username) {        this.username = username;    }    public String getPsd() {        return psd;    }    public void setPsd(String psd) {        this.psd = psd;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public Date getBirthday() {        return birthday;    }    public void setBirthday(Date birthday) {        this.birthday = birthday;    }}

import java.util.Date;

/**
* Created by ozc on 2017/4/27.
*/

public class User {

   private String username;
   private String psd;
   private int  age;
   private Date birthday;

   public String getUsername() {
       return username;
   }

   public void setUsername(String username) {
       this.username = username;
   }

   public String getPsd() {
       return psd;
   }

   public void setPsd(String psd) {
       this.psd = psd;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

   public Date getBirthday() {
       return birthday;
   }

   public void setBirthday(Date birthday) {
       this.birthday = birthday;
   }
}
public class ccAction extends ActionSupport {    private User user;    public User getUser() {        return user;    }    public void setUser(User user) {        this.user = user;    }    public String register() {        System.out.println(user.getUsername());        System.out.println(user.getPsd());        System.out.println(user.getAge());        System.out.println(user.getBirthday());        return "success";    }}class ccAction extends ActionSupport {

   private User user;

   public User getUser() {
       return user;
   }

   public void setUser(User user) {
       this.user = user;
   }

   public String register() {

       System.out.println(user.getUsername());
       System.out.println(user.getPsd());
       System.out.println(user.getAge());
       System.out.println(user.getBirthday());

       return "success";
   }


}
<form action="${pageContext.request.contextPath}/register" method="post">    用户名:<input type="text" name="user.username"><br>    密码:<input type="text" name="user.psd"><br>    年龄:<input type="text" name="user.age"><br>    生日:<input type="text" name="user.birthday"><br>    <input type="submit" value="注册"><br></form>
   用户名:<input type="text" name="user.username"><br>
   密码:<input type="text" name="user.psd"><br>
   年龄:<input type="text" name="user.age"><br>
   生日:<input type="text" name="user.birthday"><br>
   <input type="submit" value="注册"><br>
</form>
640?wx_fmt=png
这里写图片描述

得到域对象

Struts怎么把数据保存在域对象中呢???Struts提供了三种方式

一、得到Servlet API

我们可以通过ServletActionContext得到Servlet API

由于每个用户拥有一个Action对象,那么底层为了维护用户拿到的是当前线程的request等对象,使用ThreadLocal来维护当前线程下的request、response等对象…

        //通过ServletActionContext得到Servlet API        javax.servlet.ServletContext context = ServletActionContext.getServletContext();        HttpServletRequest request = ServletActionContext.getRequest();        HttpSession session = request.getSession();        HttpServletResponse response = ServletActionContext.getResponse();
       javax.servlet.ServletContext context = ServletActionContext.getServletContext();
       HttpServletRequest request = ServletActionContext.getRequest();
       HttpSession session = request.getSession();
       HttpServletResponse response = ServletActionContext.getResponse();

二、ActionContext类

我们还可以通过ActionContext类来得到request、response、session、application被Struts封装的Map集合

        //得到ActionContext 对象        ActionContext context = ActionContext.getContext();        Map<String, Object> session = context.getSession();        Map<String, Object> application = context.getApplication();        //这是request的Map        Map<String, Object> request = context.getContextMap();
       ActionContext context = ActionContext.getContext();
       Map<String, Object> session = context.getSession();
       Map<String, Object> application = context.getApplication();

       //这是request的Map
       Map<String, Object> request = context.getContextMap();

三、实现接口

当web容器发现该Action实现了Aware接口,会把相对应的资源通过Aware接口注射进去,实际上就是一种IOC。

Aware实际就是一种拦截器,拦截代码在执行Action之前执行、将资源注射到Action中

实现SessionAware, RequestAware, ApplicationAware接口,它就要在程序中实现三个方法:

    private Map<String, Object> request;    private Map<String, Object> session;    private Map<String, Object> application;    @Override    public void setApplication(Map<String, Object> map) {        this.application = map;    }    @Override    public void setRequest(Map<String, Object> map) {        this.request = map;    }    @Override    public void setSession(Map<String, Object> map) {        this.session = map;    }
   private Map<String, Object> session;
   private Map<String, Object> application;


   @Override
   public void setApplication(Map<String, Object> map) {
       this.application = map;
   }

   @Override
   public void setRequest(Map<String, Object> map) {

       this.request = map;
   }

   @Override
   public void setSession(Map<String, Object> map) {
       this.session = map;
   }

通过这些方法,我们就可以得到对应的Map对象…..

小总结

那么,我们有三种方法可以得到Servlet对应的对象,那么该使用哪一种呢???

分析:

如果我们需要使用到对象的其他方法,类似getContextPath()之类的,那么只能使用第一种

如果我们就按照平常的开发,我们就使用第二种【获取简单,没有耦合】

至于第三种,当我们将来可能开发BaseAction的时候,就使用它!


日期转换问题

前面博文已经讲解了,Struts2为我们实现了数据自动封装…由上篇的例子我们可以看出,表单提交过去的数据全都是String类型的,但是经过Struts自动封装,就改成是JavaBean对应成员变量的类型了。

但是呢,日期类型只支持是yyyy-MM-dd这种格式的,因为我们在上个例子中直接使用的是Struts支持的格式,因此没有报错…本篇博文就是讲解Struts如何对日期类型的格式更好地支持

当我们使用的是yyyyMMdd这种格式的时候,我们看看Struts的自动封装能不能解析出相对应的日期

640?wx_fmt=png
这里写图片描述

直接抛出了异常

640?wx_fmt=png
这里写图片描述
640?wx_fmt=png
这里写图片描述

分析

那么,我们怎么让Struts能够支持更多的日期格式呢??比如,我想Struts在自动封装数据的时候支持yyyyMMdd,yyyy年MM月dd日这样的日期格式…..

Struts提供了转换器给我们使用,也就是,我们可以自定义转换器,我们定义了什么格式,Struts就可以根据对应的格式进行自动封装…

当我们写完自定义转换器,是需要向Struts说明我们写了,不然的话,Struts是不知道我们自定义了转换器类的…

也就是说,我们要想实现类型转换,需要两步:

自定义转换器类

一般地,我们想要编写自定义转换器类,都是实现StrutsTypeConverter类的….

/** * Created by ozc on 2017/5/1. * 自定义异常转换器类 * * 我们要实现的就是:在Struts转换的时候, * */public class MyConvter extends StrutsTypeConverter {    //需求,当Struts自动封装数据时,也支持yyyyMMdd,yyyy年MM月dd日等格式的支持\    SimpleDateFormat[] format = {new SimpleDateFormat("yyyy-MM-dd"), new SimpleDateFormat("yyyyMMdd"), new SimpleDateFormat("yyyy年MM月dd日")};    /**     * 把String转换为指定的类型 【String To Date】     *     *     * @param map     *            当前上下文环境     * @param strings     *            jsp表单提交的字符串的值     * @param aClass     *            要转换为的目标类型     */    @Override    public Object convertFromString(Map map, String[] strings, Class aClass) {        //判断是否有值        if (strings == null) {            return null;        }        //判断是否是日期类型的        if (Date.class != aClass) {            return null;        }        //遍历循环        for (SimpleDateFormat dateFormat : format) {            try {                //解析传递进来的第一个就行啦                dateFormat.parse(strings[0]);            } catch (ParseException e) {                //如果格式不对,那么就跳出当前的循环                continue;            }        }        return null;    }    @Override    public String convertToString(Map map, Object o) {        return null;    }}
public class MyConvter extends StrutsTypeConverter {


   //需求,当Struts自动封装数据时,也支持yyyyMMdd,yyyy年MM月dd日等格式的支持\
   SimpleDateFormat[] format = {new SimpleDateFormat("yyyy-MM-dd"), new SimpleDateFormat("yyyyMMdd"), new SimpleDateFormat("yyyy年MM月dd日")};



   /**
    * 把String转换为指定的类型 【String To Date】
    *
    *
    * @param map
    *            当前上下文环境
    * @param strings
    *            jsp表单提交的字符串的值
    * @param aClass
    *            要转换为的目标类型
    */

   @Override
   public Object convertFromString(Map map, String[] strings, Class aClass) {

       //判断是否有值
       if (strings == null) {
           return null;
       }
       //判断是否是日期类型的
       if (Date.class != aClass) {
           return null;
       }

       //遍历循环
       for (SimpleDateFormat dateFormat : format) {
           try {

               //解析传递进来的第一个就行啦
               dateFormat.parse(strings[0]);
           } catch (ParseException e) {
               //如果格式不对,那么就跳出当前的循环
               continue;
           }
       }
       return null;
   }
   @Override
   public String convertToString(Map map, Object o) {
       return null;
   }
}

告诉Struts,我写了转换器类

告诉Struts我写了一个转换器类,也分两种方式

全局转换器

步骤:


局部转换器类

步骤:


效果

640?wx_fmt=png
这里写图片描述
640?wx_fmt=png
这里写图片描述

错误提示页面

当发生了日期转换的异常时,Struts给出的页面是这样子的:

640?wx_fmt=png
这里写图片描述

这个我们称之为input视图,我们要做的就是给出用户更友好的提示,于是在struts.xml文件中配置:如果返回的是input视图,那么跳转到我们相对应的页面上

   <result name="input">/error.jsp</result></result>
640?wx_fmt=png
这里写图片描述

文件上传和下载

在讲解开山篇的时候就已经说了,Struts2框架封装了文件上传的功能……..本博文主要讲解怎么使用Struts框架来完成文件上传和下载

回顾以前的文件上传

首先,我们先来回顾一下以前,我们在web中上传文件是怎么做的….http://blog.youkuaiyun.com/hon_3y/article/details/66975268

可以使用FileUpload或者SmartUpload组件来完成文件上传的功能。但是呢,FileUpload组件使用起来是比较麻烦的…而SmartUPload解决中文的问题也非常麻烦

使用Struts进行文件上传

从要导入的jar包我们就可以知道:Struts内部还是使用fileUpload上传组件….但是它极大的简化地我们的具体操作

那我们怎么用它呢??看下面的图

640?wx_fmt=png
这里写图片描述

JSP页面

在注册页面上拥有两个上传文件控件

<form action="${pageContext.request.contextPath}/register" method="post" enctype="multipart/form-data">    <input type="file" name="photo"><br>    <input type="file" name="photo1"><br>    <input type="submit" value="注册"><br></form>
   <input type="file" name="photo"><br>
   <input type="file" name="photo1"><br>
   <input type="submit" value="注册"><br>
</form>

Action

得到相对应的File对象、上传文件名称、上传文件的类型

package fileupload;import java.io.File;/** * Created by ozc on 2017/5/2. */public class FileUploadAction {    //上传文件对应的File对象    private File photo;    private File photo1;    //得到上传文件的名称    private String photoFileName;    private String photo1FileName;    //得到上传文件的类型    private String photoContentType;    private String photo1ContentType;    //给出相对应的setter    public void setPhoto(File photo) {        this.photo = photo;    }    public void setPhoto1(File photo1) {        this.photo1 = photo1;    }    public void setPhotoFileName(String photoFileName) {        this.photoFileName = photoFileName;    }    public void setPhoto1FileName(String photo1FileName) {        this.photo1FileName = photo1FileName;    }    public void setPhotoContentType(String photoContentType) {        this.photoContentType = photoContentType;    }    public void setPhoto1ContentType(String photo1ContentType) {        this.photo1ContentType = photo1ContentType;    }    public String register() {        System.out.println(photo1FileName);        System.out.println(photoFileName);        return "success";    }}

import java.io.File;

/**
* Created by ozc on 2017/5/2.
*/

public class FileUploadAction {

   //上传文件对应的File对象
   private File photo;
   private File photo1;

   //得到上传文件的名称
   private String photoFileName;
   private String photo1FileName;

   //得到上传文件的类型
   private String photoContentType;
   private String photo1ContentType;

   //给出相对应的setter
   public void setPhoto(File photo) {
       this.photo = photo;
   }

   public void setPhoto1(File photo1) {
       this.photo1 = photo1;
   }

   public void setPhotoFileName(String photoFileName) {
       this.photoFileName = photoFileName;
   }

   public void setPhoto1FileName(String photo1FileName) {
       this.photo1FileName = photo1FileName;
   }

   public void setPhotoContentType(String photoContentType) {
       this.photoContentType = photoContentType;
   }

   public void setPhoto1ContentType(String photo1ContentType) {
       this.photo1ContentType = photo1ContentType;
   }


   public String register() {

       System.out.println(photo1FileName);
       System.out.println(photoFileName);


       return "success";
   }



}

成功得到数据:

640?wx_fmt=png
这里写图片描述
640?wx_fmt=png
这里写图片描述

Action业务代码:

    public String register() throws IOException {        //得到上传的路径        String path = ServletActionContext.getServletContext().getRealPath("upload");        System.out.println(path);        //创建文件对象        File destFile = new File(path,photoFileName);        //调用工具类方法,将文件拷贝过去        FileUtils.copyFile(photo, destFile);        return "success";    }

       //得到上传的路径
       String path = ServletActionContext.getServletContext().getRealPath("upload");
       System.out.println(path);

       //创建文件对象
       File destFile = new File(path,photoFileName);

       //调用工具类方法,将文件拷贝过去
       FileUtils.copyFile(photo, destFile);

       return "success";
   }
640?wx_fmt=png
这里写图片描述

文件下载

我们以前是通过设置request消息头来实现文件下载的…..那么在Struts又如何实现文件下载呢??

我们请求服务器处理都是通过Action类来完成的,但是呢,Action类的业务方法都是返回字符串。因此,Struts在<result>节点中提供了类型为stream的type值。通过stream来配置相对应的信息,从而实现下载!

列出所有可以下载的文件

public class downLoadAction {    //列出所有可以下载的文件    public String list() {        //得到upload文件夹        String path = ServletActionContext.getServletContext().getRealPath("/upload");        //创建file对象        File file = new File(path);        //列出文件下所有的文件        File[] files = file.listFiles();        //将这些文件存到request域中        HttpServletRequest request = ServletActionContext.getRequest();        request.setAttribute("files", files);        return "list";    }}class downLoadAction {

   //列出所有可以下载的文件
   public String list() {

       //得到upload文件夹
       String path = ServletActionContext.getServletContext().getRealPath("/upload");

       //创建file对象
       File file = new File(path);

       //列出文件下所有的文件
       File[] files = file.listFiles();

       //将这些文件存到request域中
       HttpServletRequest request = ServletActionContext.getRequest();
       request.setAttribute("files", files);
       return "list";
   }
}
        <action name="down_*" class="fileupload.downLoadAction" method="{1}">            <result name="{1}">/list.jsp</result>           <!-- <result name="{1}" type="stream">/index.jsp</result>-->        </action>
           <result name="{1}">/list.jsp</result>
          <!-- <result name="{1}" type="stream">/index.jsp</result>-->
       </action>
<c:if test="${files==null}">    对不起,没有下载的页面</c:if><c:if test="${files!=null}">    <table border="1px">        <tr>            <td>编号</td>            <td>文件名称</td>            <td>操作</td>        </tr>        <c:forEach items="${files}" varStatus="file" var="fileName">            <tr>                <td>${file.count}</td>                    <%--如果直接写fileName,输出的名字带有路径,使用EL方法库来截取--%>                <td>${fn:substringAfter(fileName, "upload\\")}</td>                <td>                        <%--使用url标签来构建url,不然超链接带有中文,会出现乱码--%>                    <c:url var="url" value="down_downLoad">                        <c:param name="fileName">${fn:substringAfter(fileName, "upload\\")}</c:param>                    </c:url>                    <a href="${url}">下载</a>                </td>            </tr>        </c:forEach>    </table></c:if>"${files==null}">

   对不起,没有下载的页面

</c:if>

<c:if test="${files!=null}">

   <table border="1px">
       <tr>
           <td>编号</td>
           <td>文件名称</td>
           <td>操作</td>
       </tr>
       <c:forEach items="${files}" varStatus="file" var="fileName">
           <tr>

               <td>${file.count}</td>

                   <%--如果直接写fileName,输出的名字带有路径,使用EL方法库来截取--%>
               <td>${fn:substringAfter(fileName, "upload\\")}</td>
               <td>

                       <%--使用url标签来构建url,不然超链接带有中文,会出现乱码--%>
                   <c:url var="url" value="down_downLoad">
                       <c:param name="fileName">${fn:substringAfter(fileName, "upload\\")}</c:param>
                   </c:url>

                   <a href="${url}">下载</a>

               </td>
           </tr>
       </c:forEach>

   </table>
</c:if>
    /**     * 访问Action的业务方法仅仅返回的是字符串。因此Struts在result节点提供了stream类型的type,     * 指定了stream就代表着我这是要下载的...     * <p>     * 既然要下载文件,那么肯定需要几样东西:     * 1、文件名     * 2、代表文件的流     */    public String downLoad() {        return "downLoad";    }    //得到要下载的文件名,Struts提供了自动封装的功能    private String fileName;    //如果文件名是中文的,那么需要手动转换,因为超链接是get方法提交    public void setFileName(String fileName) throws UnsupportedEncodingException {        fileName = new String(fileName.getBytes("ISO8859-1"), "UTF-8");        this.fileName = fileName;        System.out.println(fileName);    }    //得到代表下载文件流,该方法由Struts调用    public InputStream getAttrInputStream() {        return ServletActionContext.getServletContext().getResourceAsStream("/upload/" + fileName);    }    //下载时,显示的名称【如果是中文,可能会乱码,因此要URLencode】---->在Struts.xml文件中通过${}可获取    public String getDownFileName() throws UnsupportedEncodingException {        fileName = URLEncoder.encode(fileName, "UTF-8");        return fileName;    }
   public String downLoad() {

       return "downLoad";
   }

   //得到要下载的文件名,Struts提供了自动封装的功能
   private String fileName;


   //如果文件名是中文的,那么需要手动转换,因为超链接是get方法提交
   public void setFileName(String fileName) throws UnsupportedEncodingException {
       fileName = new String(fileName.getBytes("ISO8859-1"), "UTF-8");
       this.fileName = fileName;
       System.out.println(fileName);
   }

   //得到代表下载文件流,该方法由Struts调用
   public InputStream getAttrInputStream() {
       return ServletActionContext.getServletContext().getResourceAsStream("/upload/" + fileName);
   }

   //下载时,显示的名称【如果是中文,可能会乱码,因此要URLencode】---->在Struts.xml文件中通过${}可获取
   public String getDownFileName() throws UnsupportedEncodingException {

       fileName = URLEncoder.encode(fileName, "UTF-8");
       return fileName;
   }
        <action name="down_*" class="fileupload.downLoadAction" method="{1}">            <result name="{1}">/list.jsp</result>            <result name="downLoad" type="stream">                <!--运行下载的类型,指定为所有的二进制文件-->                <param name="contentType">application/octet-stream</param>                <!-- 对应的是Action中属性: 返回流的属性【其实就是getAttrInputStream()】 -->                <param name="inputName">attrInputStream</param>                <!-- 下载头,包括:浏览器显示的文件名 -->               <!--${}这里不是EL表达式-->                <param name="contentDisposition">attachment;filename=${downFileName}</param>                <!-- 缓冲区大小设置 -->                <param name="bufferSize">1024</param>            </result>        </action>
           <result name="{1}">/list.jsp</result>
           <result name="downLoad" type="stream">

               <!--运行下载的类型,指定为所有的二进制文件-->
               <param name="contentType">application/octet-stream</param>

               <!-- 对应的是Action中属性: 返回流的属性【其实就是getAttrInputStream()】 -->
               <param name="inputName">attrInputStream</param>

               <!-- 下载头,包括:浏览器显示的文件名 -->               <!--${}这里不是EL表达式-->
               <param name="contentDisposition">attachment;filename=${downFileName}</param>

               <!-- 缓冲区大小设置 -->
               <param name="bufferSize">1024</param>

           </result>
       </action>


模型驱动

什么是模型驱动

在Struts2中模型驱动就是用来封装数据的..完成数据的自动封装.

为什么要使用模型驱动?

我们之前就使用过Sturts2的数据自动封装功能,是用params拦截器完成的…既然有了params拦截器,为啥还要模型驱动??

当我们使用params拦截器完成数据自动封装的时候,如果要封装的是JavaBean对象,那么在web表单中就必须的name写上javaBean.属性名….

这样的话,web层和Action层就耦合了…因为在web层必须要知道封装的JavaBean对象是什么才能够实现自动封装!

而模型驱动就解决了这个问题!即时不知道Action层的JavaBean对象是什么,也能够完成数据自动封装!

模型驱动的实现原理

实现模型驱动功能也是由拦截器完成的,我们来看看拦截器到底做了什么吧….

         <interceptor name="modelDriven" class="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"/>

拦截方法的源码是这样的:

    public String intercept(ActionInvocation invocation) throws Exception {        //得到当前要执行的Action对象        Object action = invocation.getAction();        //判断该Action对象是否实现了ModelDriven接口        if(action instanceof ModelDriven) {            ModelDriven modelDriven = (ModelDriven)action;            //获取值栈对象            ValueStack stack = invocation.getStack();            //得到model的对象            Object model = modelDriven.getModel();            //把对象存到值栈对象中            if(model != null) {                stack.push(model);            }            if(this.refreshModelBeforeResult) {                invocation.addPreResultListener(new ModelDrivenInterceptor.RefreshModelBeforeResult(modelDriven, model));            }        }        return invocation.invoke();    }

       //得到当前要执行的Action对象
       Object action = invocation.getAction();

       //判断该Action对象是否实现了ModelDriven接口
       if(action instanceof ModelDriven) {
           ModelDriven modelDriven = (ModelDriven)action;

           //获取值栈对象
           ValueStack stack = invocation.getStack();

           //得到model的对象
           Object model = modelDriven.getModel();

           //把对象存到值栈对象中
           if(model != null) {
               stack.push(model);
           }
           if(this.refreshModelBeforeResult) {
               invocation.addPreResultListener(new ModelDrivenInterceptor.RefreshModelBeforeResult(modelDriven, model));
           }
       }

       return invocation.invoke();
   }

把model对象放到值栈对象之后,**Parameters 拦截器将把表单字段映射到 ValueStack 栈的栈顶对象的各个属性中. **

也就是说,使用模型驱动是需要配合Params拦截器完成的!

使用数据模型驱动

实现ModelDriven接口

public class UserAction extends ActionSupport implements ModelDriven<User> {    public String login() {        return SUCCESS;    }    @Override    public User getModel() {        return null;    }}class UserAction extends ActionSupport implements ModelDriven<User> {



   public String login() {

       return SUCCESS;
   }


   @Override
   public User getModel() {
       return null;
   }
}

对象实例化

public class UserAction extends ActionSupport implements ModelDriven<User> {    //这里一定要实例化    User user = new User();    public User getUser() {        return user;    }    public void setUser(User user) {        this.user = user;    }    @Override    public User getModel() {        return user;    }}class UserAction extends ActionSupport implements ModelDriven<User> {


   //这里一定要实例化
   User user = new User();

   public User getUser() {
       return user;
   }

   public void setUser(User user) {
       this.user = user;
   }

   @Override
   public User getModel() {
       return user;
   }
}

测试

JSP提交页面,直接写上JavaBean对象的属性就行了..不需要写上JavaBean对象的名称!

<form action="${pageContext.request.contextPath}/user_execute">    <table border="1">        <tr>            <td>用户名:<input type="text" name="username"></td>        </tr>        <tr>            <td> 密码:<input type="password" name="password"></td>        </tr>        <tr>            <td>电话:<input type="text" name="cellphone"></td>        </tr>        <tr>            <td> 邮箱:<input type="text" name="email"></td>        </tr>        <tr>            <td><input type="submit" value="提交"></td>        </tr>    </table></form>
   <table border="1">

       <tr>
           <td>用户名:<input type="text" name="username"></td>
       </tr>
       <tr>
           <td> 密码:<input type="password" name="password"></td>
       </tr>
       <tr>
           <td>电话:<input type="text" name="cellphone"></td>
       </tr>
       <tr>
           <td> 邮箱:<input type="text" name="email"></td>
       </tr>

       <tr>
           <td><input type="submit" value="提交"></td>
       </tr>

   </table>


</form>
    @Override    public String execute() throws Exception {        System.out.println(user);        return SUCCESS;    }
   public String execute() throws Exception {

       System.out.println(user);
       return SUCCESS;
   }
640?wx_fmt=png
这里写图片描述

如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y


【置顶】TEE从入门到精通-[目录] 此专栏讲解包含不限于optee、商用TEE、常规的安全业务设计。但主要基于optee最新代码,深度讲解和剖析各项原理和机制... 阅读详情

相关推荐

Edge浏览器IE模式全攻略:从企业应用到老旧网站兼容(含常见问题解答)

本文详细解析了微软Edge浏览器的IE模式,这是解决企业遗留系统与老旧网站兼容性问题的关键技术。文章从核心原理出发,提供了从个人用户临时启用到企业级组策略部署的完整配置指南,并深入探讨了文档模式调优、ActiveX控件处理等高级技巧,最后汇总了常见问题排查与安全实践,帮助用户高效平衡兼容性与现代化浏览体验。

weixin_29227425的博客 609

Struts2----无法接收类属性的成员变量

定义一个实体类,然后jsp中 类.类成员变量 ,但是无法获取 以下为错误的列子: package entity; /** * @author Ming * @date 2017/12/22 16:35 * POJO */ public class Company { private String pDeptName; private String pEmpN

Tomorrow 670

Unity AR开发实战:从Vuforia图片识别到实体识别全流程解析

增强现实(AR)技术通过计算机视觉算法将虚拟信息叠加到现实世界,其核心原理在于对现实环境的感知与理解。在移动端AR开发中,图像识别与物体识别是实现虚实融合的关键技术,它们决定了虚拟内容能否准确、稳定地与现实对象对齐。Vuforia作为成熟的商业AR SDK,为开发者提供了从平面图像识别(Image Target)到三维实体识别(Model Target)的完整解决方案,极大降低了计算机视觉技术的应用门槛。在工程实践中,Vuforia通过封装复杂的特征提取与匹配算法,使开发者能够专注于交互设计与内容制作,广泛

weixin_30972263的博客 301

绝对路径${pageContext.request.contextPath}用法及其与web.xml中Servlet的url-pattern匹配过程

绝对路径${pageContext.request.contextPath}用法及其与web.xml中Servlet的url-pattern匹配过程

Sun9528的博客 9万+

如何在NVIDIA CX6 网卡和华为CE交换机上为RDMA协议使能DCQCN和PFC

【代码】如何在NVIDIA CX6 网卡和华为CE交换机上使能DCQCN和PFC。

Markchen7788的博客 5090

Ansys Zemax与Speos协同设计:从HUD光学优化到驾驶环境可视化全流程解析

本文深入解析了如何利用Ansys Zemax与Speos进行协同设计,实现从HUD光学系统核心优化到驾驶环境可视化验证的全流程。文章强调了HUD设计是光学性能与视觉感知的深度耦合,通过Zemax进行精密光学设计,再利用Speos进行系统级仿真与感知模拟,最终完成从虚拟到现实的闭环验证,有效提升HUD产品的开发效率与可靠性。

weixin_29199873的博客 243

路径问题 解决 ${pageContext.request.contextPath}

今天在写一个struts的时候遇到路径问题最后用了这条语句解决 这里要注意的是如果用了上面的 ${pageContext.request.contextPath} 表达式的の时候那么你的web.xml要保证头是这样的http://java.sun.com/xml/ns/j2ee"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instan

畅所宇言 9557

Ollama最新快速上手指南:从安装到精通本地AI模型部署

想让Ollama跑你的"私房模型"?三种主流格式全支持,总有一款适合你!GGUF格式(推荐新手首选)创建Modelfile文件:执行创建命令:运行你的专属模型:PyTorch/Safetensors格式(适合进阶玩家)先用库测试模型能否正常加载确保模型结构完整后再导入Ollama官方文档有详细转换指南专业小贴士GGUF格式最轻量,PyTorch最灵活,Safetensors最安全遇到问题?试试用llama.cpp进行格式转换7B参数模型推荐使用q4_0量化级别想让AI用马里奥。

全世界的博客 2309

Action类为何要 extends ActionSupport以及实现ModelDriven

 图如题注意继承ActionSupporty引入的包是com.opensymphony.xwork2.ActionSupport;实现​ ModelDriven引入的包也是com.opensymphony.xwork2.ModelDriven;首先:1、Action接口有: public static final java.lang.String SUCCESS = "success"; pub...

qq_38136705的博客 1625

嵌入式开发避坑指南:用DWT做延时函数,这几个细节不注意代码会跑飞

本文深入探讨了在Cortex-M嵌入式开发中使用DWT实现精确延时函数的关键技巧与常见陷阱。针对DWT_CYCCNT计数器应用中的内核兼容性、溢出处理、编译器优化等五大核心问题,提供了工业级稳健解决方案,帮助开发者避免代码跑飞风险,实现微秒级高精度延时控制。

weixin_42653691的博客 158

Python 爬虫实战:社交媒体(Facebook)评论数据抓取与情感分析

在当今数字化时代,社交媒体平台如 Facebook 已成为人们日常生活中不可或缺的一部分。它不仅是人们分享生活点滴、交流思想的场所,也为企业和研究人员提供了丰富的数据资源。通过分析社交媒体上的评论数据,我们可以了解用户的情感倾向、产品反馈以及社会热点话题等。本文将详细介绍如何使用 Python 爬虫技术抓取 Facebook 上的评论数据,并进行情感分析。这不仅是一次技术实战,更是一次数据探索之旅。

yansideyucsdn的博客 671

struts2通配符及Action开发的三种方式

一,通配符与动态方法调用 1)准备一个UserAction.java,这个Action中有两个方法login()和register() package com.bighuan.a_config; import com.opensymphony.xwork2.ActionSupport; /** * @author bighuan */ public class UserAction ex

bighuan的博客 5214

RTDETR最强结构图 | 包含模型整体结构图 | 全模块展开图 | AiFi模块展开图

RT-DETR 最全结构图

Coding... 9597

Struts2——开发Action

文章目录一、Action开发的三种方式1.1 继承ActionSupport类1.2 实现Action接口1.3 不继承任何类、不实现任何接口二、请求数据的封装2.1 提出问题2.2 解决问题2.2.1 向后端传递数据2.2.2 后端接收数据 一、Action开发的三种方式 参考:https://mp.weixin.qq.com/s/6YLd8CwsieXOht8cf_R-LQ 1.1 继承Act...

且行且珍惜 1017

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

struts2 使用注解形式开发Action步骤

servlet3.0以后就有了 注解。 有好处也有坏处,所以有必要了解一下如何用注解的形式在Struts2中开发action 使用注解开发Action步骤: 1.   引入struts2-convention-plugin-2.3.31.jar 包 2.   配置文件web.xml 文件 Struts2 org.apache.struts2.d

man_tou_22 1133

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

六、Struts2 的Action开发 -- Action类(一)

一、Struts2配置 1.1 Action的开发的几种方式 1.1.1 继承ActionSupport类 public class HelloAction extends ActionSupport { /** * 执行方法 */ public String hello() { return "success"; } } 1.1.2 实现Action接口 public c...

weixin_45602227的博客 224

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

java中的action是指什么_Struts2【开发Action】知识要点

前言前面Struts博文基本把Struts的配置信息讲解完了.....本博文主要讲解Struts对数据的处理Action开发的三种方式在第一次我们写开发步骤的时候,我们写的Action是继承着ActionSupport类的...为啥我们继承了ActionSupport类呢?下面我就会讲解到继承ActionSupport类我们来看一下ActionSupport干了什么:也就是说,如果我们在Actio...

weixin_29370183的博客 4467

Struts2 学习笔记——Action开发详解

对于开发Struts2应用的开发者来说,Action才是应用的核心,我们通过开发不同的业务控制器—Action来处理不同的用户请求逻辑。 实现Action类 Struts2采用了低入侵式的、松耦合设计,开发一个Action类,不需要继承或实现任何Struts2的类或接口。 我们先来看一下标准的Action类的格式:   public class 类名 {

1447

Struts2 Action开发

Struts2配置1. Action开发的三种方式1.1 继承ActionSupportimport com.opensymphony.xwork2.ActionSupport;public class UserAction2 extends ActionSupport{ public String login() { System.out.println("UserActi

guigui_oy的博客 262

Struts2【开发Action】知识要点

前言 前面Struts博文基本把Struts的配置信息讲解完了.....本博文主要讲解Struts对数据的处理 Action开发的三种方式 在第一次我们写开发步骤的时候,我们写的Action是继承着ActionSupport类的...为啥我们继承了ActionSupport类呢?下面我就会讲解到 继承ActionSupport类 我们来看一下ActionSupport干了什么: 也就是...

awxj68104的博客 123

java struts2 作用_Java Web开发——Struts2 中.action和.do的奥秘

为什么Struts中.action的时候可以访问,是.do的时候不能访问呢?遇到这个问题的时候有点晕乎,在网上查了些资料,终于恍然大悟!下面把自己的经验说来给大家,希望对大家有所帮助。其实问题发生在struts.properties配置文件里,解决方法是将struts.action.extension改为如下形式struts.action.extension=do,action这样.action和...

weixin_39698217的博客 173

Struts 1.2 Action 开发流程

struts 1.2 版本开发 1.新建web project项目;2.导入Struts 1.2插件包;3.修改配置文件: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Config...

diandu2747的专栏 104

struts2 jsp跳转action 404_复习搭建struts2的开发环境

struts2可以应答哪些请求?1).org.apache.struts2 包下的default.properties 中配置了Struts2应用的一些常量 文件中有些常量配置 struts.action.extension=action,, 表示不写扩展名也可以2), struts.action.extension 定义了当前Struts2 应用可以接受的请求的...

weixin_39998859的博客 157

JAVAWEB开发之Struts2详解(一)——Struts2框架介绍与快速入门、流程分析与工具配置以及Struts2的配置以及Action和Result的详细使用

Struts2框架介绍 Struts2开发入门 Struts2开发入门示例 模仿Struts2流程完成入门程序 Struts2流程分析与工具配置 Struts2处理流程 关于手动配置strutsxml文件中提示操作 关联strutsxml源文件 Config Brower插件使用 Struts2配置重点 Struts2配置文件加载顺序 关于Action的配置 1关于action配置其他细节 3关于常量配置 1修改struts2 的配置常量可以通过以下三种方式配置 2常用常量介绍 关于Action类的创建

8258

2、struts2开发action 的三种方法以及通配符、路径匹配原则、常量

struct2 开发action 的三种方法1、继承ActionSupportpublic class UserAction extends ActionSupport { // Action中业务处理方法 public String login() { System.out.println("UserAction.login()"); // return "

yungcs的专栏 5942

struts2开发中在jsp页面顶用js取得action中传过来的值

 struts2开发中在jsp页面中用js取得action中传过来的值 使用struts2开发中,在jsp页面的js中用EL表达式取得action中传过来的值。 $(document).ready(function(){ / var employeeSN = "${employeeSN}"; EimUtil.RunEim(employeeSN); });

薛秋艳的博客 3836
上一篇: Struts2【拦截器】
下一篇: Struts2【配置】
Java3y
Java3y 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄9年 1万+粉丝 509原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值