课程概要:
讲解Struts2中数据封装的三种方式以及具体实现原理
一、Struts2数据封装机制之属性驱动
我们先来看一下传统的servlet是如何处理从页面传递过来的数据的。
首先我们在表单发送了对应的数据到servlet中去
<form action="<%=path%>/loginservlet"method="post"> 账号:<inputtype="text"name="username"/><br> 密码:<inputtype="password"name="password"/><br> <inputtype="submit"value="提交"/> </form>
protected voiddoPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { String username=request.getParameter("username"); String password=request.getParameter("password"); User user=newUser(); user.setUsername(username); user.setPassword(password); }
可以看到在Servlet中是使用Web中的request对象来获取其中的Parameter元素的值来获得对应的值,然后将其封装到对应的对象中去。
但是在Struts2中并没有servlet类而是使用过滤器配合Action对象实现的。
那么在Struts2中是如何完成上面的工作的呢?
public class LoginActionextends ActionSupport{ //此处的属性值应与表单中的name值一致 //并且必须得有对应的set和get方法 private String username; private String password; @Override public Stringexecute()throws Exception{ System.out.println("username="+username); System.out.println("password="+password); return"success"; } public StringgetUsername() { return username; } publicvoidsetUsername(String username) { this.username= username; } public StringgetPassword() { return password; } publicvoidsetPassword(String password) { this.password= password; } }
上面的就是我们编写的登录验证的Action类,在编写完成后我们需要将其配置到struts.xml文件当中去,使其可以被框架所使用。配置信息如下:
<action name="userLogin"class="cn.lovepi.chapter02.action.LoginAction"> <resultname="success">/chapter02/index.jsp</result> </action>
在配置完成后便可以将表单中的action改为当前的action的name(注意后缀是.action,也可自行设置,使用constant属性)
然后启动项目转到相应页面中输入用户名和密码后可以看到在控制台中已经打印出来了相关信息。
但是我们却并没有在代码中做过任何相关的数据封装操作,
但是对应的数据却已经被封装到属性当中了。
这便是struts2的数据封装中的属性封装机制,只需在Action中设置当前表单中要提交的数据的属性(注意这里可能会存在属性类型的转换问题,这个以后的课程中讲到。现在的演示中只涉及到String类型的属性,同时表单中提交的数据也是String类型的。所有不牵扯到类型转换问题)待表单提交过来数据时便直接会被封装到对应的属性中去。
那么这个机制到底是如何实现的呢?
在讨论这个问题之前首先先来看一下在这个过程中的具体步骤:
- 首先表单要提交数据,那么接收数据的必定是一个Servlet。
- 既然是Servlet那么其中执行相应操作的必须是doPost方法。
- 但是执行相关操作的类却不是一个Servlet!
- 在这种情况下还想要具有Servlet的方法的话就只有一个办法
- 继承父类,让父类成为一个Servlet
- 而子类却不复写父类中的方法
- 这样当表单数据传送过来的时候就回去执行父类中的doPost方法
- 在父类执行时想要对子类中的数据进行操作,这时便需要使用到java中的核心机制----反射机制
那么接下来我们便使用代码来实现对应的操作:
首先我们先编写父类BaseServlet,并且继承HttpServlet
package cn.lovepi.chapter02.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Enumeration; /** * Created by icarus on 2016/7/8. * 模拟实现属性封装和的实现原理 */ public class BasicServletextends HttpServlet{ /** * 子类在运行中执行到这里 * 所以这里的当前对象还是子类对象 * 利用反射来获取子类中的属性列表和对应的实体对象 * 使用request.getParameterNames()方法来获取到表单中的name列表 * 当属性名称与表单name相同时就把表单中的内容通过反射设置到属性中去 * 然后利用反射调用execute方法,获取得到的url地址 * 根据url地址来进行对应的请求转发 */ @Override protectedvoiddoPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{ //获取当前对象的属性列表 Field[] fields=this.getClass().getDeclaredFields(); //获取表单中的name列表,返回值是一个Enumeration枚举类型 Enumeration<String> names=req.getParameterNames(); //开始对表单的name值和属性名称进行比较 while(names.hasMoreElements()){ for(Field f:fields){ //当属性名称和name相同 if(names.nextElement().equals(f.getName())){ //*这一句非常重要* f.setAccessible(true); //将表单中的值设置到对应的属性中去 try{ f.set(this,req.getParameter(f.getName())); }catch(IllegalAccessException e) { e.printStackTrace(); } } } } //获取子类中的execute并执行,获取他的返回值即为请求的url try{ Method execute=this.getClass().getMethod("execute",null); Object url=execute.invoke(this,null); //进行请求转发 req.getRequestDispatcher(String.valueOf(url)).forward(req,resp); }catch(Exception e) { e.printStackTrace(); } } }
对子类LoginServlet进行编写:
/* *步骤:此类继承父类BasicServlet,BasicServlet继承HttpServlet *也就是说此类还是一个Servlet *当表单数据发送到此类中后会首先执行doPost方法, *子类中没有便会去执行父类中的对应方法 *接下来执行父类中doPost方法 */ public class LoginServletextends BasicModelServlet{ private String username; private String password; public Stringexecute()throws Exception{ System.out.println("username="+username); System.out.println("password="+password); return"/chapter02/index.jsp"; } public StringgetUsername() { return username; } publicvoidsetUsername(String username) { this.username= username; } public StringgetPassword() { return password; } publicvoidsetPassword(String password) { this.password= password; } }
这便是Struts2中的数据封装机制-----属性驱动。
二、Struts2数据封装机制之模型驱动
接下来介绍Struts2中的另外一种数据封装机制-----模型驱动
同样是刚才的表单,我们在编写一个新的Action来处理提交的数据,在将其配置到Struts系统中去。
package cn.lovepi.chapter02.action; import cn.lovepi.chapter02.pojo.User; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; /** * Created by icarus on 2016/7/6. * 登录活动类-模型驱动演示 * 注意:这个类必须实现ModelDriven<>泛型中必须填对应的封装模型 * 并设置一个与所封装模型的实例化对象(注意必须实例化,否则会报空指针异常) * ------------------------------------------------------------------ * 思路介绍: * 当请求发送到action之前, * 调用MLoginAction类中的getModel方法获取将要把表单数据封装到的实例化的类对象 * 获得该对象之后,我们便可以获得对应的类类型 * 利用反射可以获取到类中的属性列表 * 通过request.getParameterNames()可以获取表单中的name列表 * 判断name值和属性名称,一致的情况下 * 反射调用属性的set方法来给对应的属性值设置参数 * 从而完成数据的封装 */ public class MLoginActionextends ActionSupportimplements ModelDriven<User>{ //实例化所需封装的模型类 private User user=new User(); /** * 模型驱动方法 * @return 封装好的模型类 */ @Override public User getModel() { return user; } /** * 活动执行方法 * @return "success"与父类默认返回的一致 * @throws Exception */ @Override public String execute()throws Exception { System.out.println("username:"+user.getUsername()); System.out.println("password:"+user.getPassword()); return "success"; } }
那么我们也来看一看模型驱动的实现原理吧
其实具体的实现步骤和属性驱动的实现机制相同,只是由原先的字符类型的属性变成了一个对象属性,但是使用模型驱动必须实现ModelDrive<>而且泛型之中存放的是属性对象,实现接口的方法是getModel(),通过反射调用这个方面便能获取到属性的对象。剩下来的操作便和属性驱动的一致了。
接下来我们使用代码来实现。
首先那么我们将子类LoginServlet中的属性改为和Action中相同的
然后编写其的父类BasicModelServlet:
package cn.lovepi.chapter02.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Enumeration; /** * Created by icarus on 2016/7/8. * 模拟实现模型驱动的数据自动封装原理 * 当子类执行到doPost方法时 * 就可以获取到子类中的getModel方法, * 执行后便能得到封装的模型类对象 * 获取模型类对象中的属性列表 * 利用request.getParameterNames()来获得表单中的name列表 * 判断naem和属性名称,相同的话便将name中的值设置到对象的属性中去 * 然后获取子类中的execute方法,执行后获得到对应的url * 对url进行请转发 */ public class BasicModelServletextends HttpServlet{ @Override protectedvoiddoPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{ try{ //获取子类中的getModel方法 Method getModel=this.getClass().getDeclaredMethod("getModel",null); //利用反射获取到所需封装类对象 Object object=getModel.invoke(this,null); //获取模型类中的所有属性 Field[] fields=object.getClass().getDeclaredFields(); //获取表单中的name列表 Enumeration<String> names=req.getParameterNames(); //开始进行name和属性名称的比较 while(names.hasMoreElements()){ for(Field f:fields){ //当属性名称和表单name相同时 if(names.nextElement().equals(f.getName())){ //非常重要 f.setAccessible(true); f.set(object,req.getParameter(f.getName())); } } } //获取子类中的execute方法并执行,获取得到的url Method execute=this.getClass().getDeclaredMethod("execute",null); Object url=execute.invoke(this,null); //根据对应的url进行请求转发 req.getRequestDispatcher(String.valueOf(url)).forward(req,resp); }catch(Exception e) { e.printStackTrace(); } } }
三、Struts2数据封装机制之标签驱动
想要通过Struts2中的标签来实现表单数据的自动封装的前提是得在表单中加入Struts2标签支持。
<%@taglibprefix="s"uri="/struts-tags" %>
这里需要注意的是prefix是Struts2标签的别称,一般都是“s”
同时在表单之中也得使用Struts2中定义的表单实现方式来定义表单。
<s:form action="sUserLogin"method="POST"> 账号:<s:textfieldname="user.username"/> 密码:<s:passwordname="user.password"/> <s:submitvalue="提交"/> </s:form>
在这里表单标签都得使用s:的形式,而且action不需要后缀。
在这里还存在着表单样式的问题,这个问题后再后面的课程中讲解,这里就不处理了。
接下来是对应Action的编写
package cn.lovepi.chapter02.action; import cn.lovepi.chapter02.pojo.User; import com.opensymphony.xwork2.ActionSupport; /** * Created by icarus on 2016/7/7. * 标签方法实现数据自动封装 */ public class SLoginActionextends ActionSupport{ // 与模型驱动方法不同之处是这里无须实例化所封装对象 private User user; @Override public Stringexecute()throws Exception{ System.out.println("----标签实现----"); System.out.println("username="+user.getUsername()); System.out.println("password="+user.getPassword()); return"success"; } public UsergetUser() { return user; } publicvoidsetUser(User user) { this.user= user; } }
总结:
在这里我们学习了Struts2当中数据封装的三种机制:属性驱动、模型驱动、标签驱动。
三种方式各有优势,我们都应掌握每种方式的使用以及了解其具体的内部实现原理。基于实际使用情况来看我们最多使用的是属性驱动和模型驱动,由于标签驱动使用了Struts2的一些东西,增加了系统的耦合度,所以较少使用。