首页 > 代码库 > struts2知识汇总
struts2知识汇总
一、Struts 2基本概念及环境搭建
1.什么是Struts2
Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。
Struts 2是Struts1的下一代产品,是在 struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构差别巨大。
Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以Struts 2可以理解为WebWork的更新产品。
Struts2只是在WebWork的基础上做了适当的简化、加强和封装,并少量保留Struts1.x中的习惯。
Struts2是构建在WebWork基础之上的一个运行稳定、性能优异、设计成熟的WEB框架。
WebWork是建立在XWork的command模式框架之上的基于web的MVC框架。所以,无论是Struts2还是WebWork底层都是XWork。
2.Struts2与Struts1的区别
struts2相对于struts1来说简单了很多,并且功能强大了很多,我们可以从几个方面来看:
从体系结构来看:struts2大量使用拦截器来处理请求,从而允许业务逻辑控制器与servlet-api分离,避免了侵入性;而struts1在action中明显的侵入了servlet-api.
从线程安全分析:struts2.x是线程安全的,每一个对象产生一个实例,避免了线程安全问题;而struts1.x在action中属于单线程。
性能方面:struts2.x测试可以脱离web容器,而struts1.x依赖servlet-api,测试需要依赖web容器。
请求参数封装对比:struts2.x使用ModelDriven模式,这样我们直接封装model对象,无需要继承任何struts2的基类,避免了侵入性。
标签的优势:标签库几乎可以完全替代JSTL的标签库,并且 struts2支持强大的ognl表达式。
当然,struts2和struts1相比,在文件上传,数据校验等方面也方便了好多。
3.Struts2体系结构图
一个请求在Struts2框架中的处理大概分为以下几个步骤:
1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;
2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);
3、接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action;
4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;
5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;
6、ActionProxy创建一个ActionInvocation的实例。
7、ActionInvocation实例使用命令模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2框架中继承的标签。在这个过程中需要涉及到ActionMapper。
4.搭建Struts2开发环境
从官网下载struts2压缩包,将struts-2.3.16.1\apps\struts2-blank\WEB-INF\lib下的jar包导入到项目中去,这样准备工作就完成了。
创建struts.xml,可直接拷贝struts2-blank项目下的。
在web.xml文件中配置struts2核心控制器
<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>
PS:StrutsPrepareAndExecuteFilter是核心控制器,xxxAction是业务控制器。
Http请求的流转过程:
1) Http请求首先会被web.xml所处理
2) 在web.xml中,通过Struts2所设置的默认过滤器StrutsPrepareAndExecuteFilter将请求转发到struts.xml中去处理
3) 在struts.xml中配置了相关的前置拦截器,那么前置拦截器便会对所设置的请求进行处理(例如我们所设置的.do请求以及.action请求)
4) 然后根据设置的url将请求转到所对应的Action类中去,在Action类中进行逻辑处理并返回一个字符串标志
5) 假如配置了对应的后置拦截器则会转到后置拦截器的业务处理逻辑中去。
6) 接下来struts.xml便会根据返回的字符串从而跳转到相应的结果界面。
7) 进而显示给用户所需的界面。
5.配置文件的加载
(1) Struts2框架的核心是StrutsPrepareAndExecuteFilter过滤器,该过滤器有两个功能:
Prepare–预处理,加载核心的配置文件
Execute–执行,让部分拦截器执行
(2) StrutsPrepareAndExecuteFilter过滤器会加载哪些配置文件呢?
通过源代码可以看到具体加载的配置文件和加载配置文件的顺序:
init_DefaultProperties(); – 加载org/apache/struts2/default.properties
init_TraditionalXmlConfigurations(); – 加载struts-default.xml,struts-plugin.xml,struts.xml
init_LegacyStrutsProperties(); – 加载自定义的struts.properties
init_CustomConfigurationProviders(); – 加载用户自定义配置提供者
init_FilterInitParameters() ; – 加载web.xml
了解配置文件:
default.properties – 在org/apache/struts2/目录下,代表的是配置的是Struts2的常量的值,默认值,咱们是不能修改的。
struts-default.xml–在Struts2的核心包下,代表的是Struts2核心功能的配置(Bean、拦截器、结果类型等)
struts.xml – 重点中的重点配置,代表WEB应用的默认配置,在工作中,基本就配置它就可以了!!(可以配置常量)
struts.properties – 可以配置常量,但基本不会在该配置文件中配置
web.xml – 配置前端控制器(可以配置常量,但是基本不这样做)
通常建议在struts.xml中定义struts2属性,而不是在struts.properties中,之所以保留struts.properties,是为了保持与WebWork的向后兼容性。
6.Action类的三种写法
1、Action类就是一个POJO类
2、Action类可以实现Action接口
3、Action类可以去继承ActionSupport类(开发中这种方式使用最多)
ActionSupport是一个默认的Action实现类,当配置action没有指定class属性是,系统自动使用ActionSupport作为Action处理类。
7.结果页面的跳转
结果页面存在两种方式
1、全局结果页面
条件:如果包中的一些action都返回success,并且返回的页面都是同一个JSP页面,这样就可以配置全局的结果页面。
全局结果页面针对的当前的包中的所有的Action,但是如果局部还有结果页面,会优先局部的。使用的标签是
<global-results> <result>/demo3/suc.jsp</result> </global-results>
2、局部结果页面
<result name="success">/demo/success.jsp</result>
3、结果页面使用标签进行配置,包含两个属性:
name – 逻辑视图的名称
type – 跳转的类型,值一些,需要掌握一些常用的类型。
常见的结果类型去struts-default.xml中查找。
dispatcher – 转发,type的默认值。Action—>JSP
redirect – 重定向。 Action—>JSP
chain – 多个action之间跳转,从一个Action转发到另一个Action。 Action—Action
redirectAction – 多个action之间跳转。从一个Action重定向到另一个Action, Action—Action
stream – 文件下载时候使用的
8.Model1和Model2
传统Model:JSP + JavaBean (JSP充当控制层和视图层,有大量的JSP页面,适合小型网站开发)
Model2:JSP + Servlet + JavaBean (MVC)
二、Struts2中数据封装
为什么要使用数据的封装呢?
(1)作为MVC框架,必须要负责解析HTTP请求参数,并将其封装到Model对象中
(2)封装数据为开发提供了很多方便
(3)Struts2框架提供了很强大的数据封装的功能,不再需要使用Servlet的API完成手动封装了!!
在Servlet中是使用Web中的request对象来获取其中的Parameter元素的值来获得对应的值,然后将其封装到对应的对象中去。
但是在Struts2中并没有servlet类而是使用过滤器配合Action对象实现的。
1.属性驱动
在action中创建属性,该属性值与表单中的name值一致,并且必须得有对应的set和get方法。
属性封装机制:
1、编写一个父类BasicServlet(继承HttpServlet,重写doPost方法),
2、编写一个子类,子类却不复写父类中的方法
3、当表单数据传送过来的时候就回去执行父类中的doPost方法,利用反射来获取子类中的属性列表和对应的实体对象,
4、使用request.getParameterNames()方法来获取到表单中的name列表,
5、当属性名称与表单name相同时就把表单中的内容通过反射设置到属性中去,
6、然后利用反射调用execute方法,获取得到的url地址,
7、根据url地址来进行对应的请求转发。
package com.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; /** * 模拟实现属性封装和的实现原理 */ public class BasicServlet extends HttpServlet{ /** * 子类在运行中执行到这里 * 所以这里的当前对象还是子类对象 * 利用反射来获取子类中的属性列表和对应的实体对象 * 使用request.getParameterNames()方法来获取到表单中的name列表 * 当属性名称与表单name相同时就把表单中的内容通过反射设置到属性中去 * 然后利用反射调用execute方法,获取得到的url地址 * 根据url地址来进行对应的请求转发 */ @Override protected void doPost(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(); } } }
父类:继承HttpServlet,在doPost中利用反射来获取子类中的属性列表和对应的实体对象......
子类:继承父类servlet,定义表单中对应的属性,并设置get、set方法,重写execute().
这种方式不是特别好:因为属性特别多,提供特别多的set方法,而且还需要手动将数据存入到对象中。
这种情况下,Action类就相当于一个JavaBean,就没有体现出MVC的思想,Action类又封装数据,又接收请求处理,耦合性较高。
2.模型驱动
使用模型驱动的方式,也可以把表单中的数据直接封装到一个JavaBean对象中,并且表单的写法和之前的写法没有区别!编写的页面不需要任何变化,正常编写name属性的值。
模型驱动的编写步骤:
* 在Action中手动实例化JavaBean,即:private User user = new User();
* 必须实现ModelDriven接口,实现getModel()的方法,在getModel()方法中返回user即可!
package cn.com.action; import cn.com.pojo.User; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; /** * 登录活动类-模型驱动演示 * 注意:这个类必须实现ModelDriven<>泛型中必须填对应的封装模型 * 并设置一个与所封装模型的实例化对象(注意必须实例化,否则会报空指针异常) * ------------------------------------------------------------------ * 思路介绍: * 当请求发送到action之前, * 调用MLoginAction类中的getModel方法获取将要把表单数据封装到的实例化的类对象 * 获得该对象之后,我们便可以获得对应的类类型 * 利用反射可以获取到类中的属性列表 * 通过request.getParameterNames()可以获取表单中的name列表 * 判断name值和属性名称,一致的情况下 * 反射调用属性的set方法来给对应的属性值设置参数 * 从而完成数据的封装 */ public class MLoginAction extends ActionSupport implements 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(),通过反射调用这个方面便能获取到属性的对象。剩下来的操作便和属性驱动的一致了。
注意:这个类必须实现ModelDriven<>泛型中必须填对应的封装模型,并设置一个所封装模型的实例化对象(注意必须实例化,否则会报空指针异常)。
模拟实现模型驱动的数据自动封装原理:
- 当子类执行到doPost方法时
- 就可以获取到子类中的getModel方法,
- 执行后便能得到封装的模型类对象
- 获取模型类对象中的属性列表
- 利用request.getParameterNames()来获得表单中的name列表
- 判断name和属性名称,相同的话便将name中的值设置到对象的属性中去
- 然后获取子类中的execute方法,执行后获得到对应的url
- 对url进行请转发
package com.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; /** * 模拟实现模型驱动的数据自动封装原理 * 当子类执行到doPost方法时 * 就可以获取到子类中的getModel方法, * 执行后便能得到封装的模型类对象 * 获取模型类对象中的属性列表 * 利用request.getParameterNames()来获得表单中的name列表 * 判断naem和属性名称,相同的话便将name中的值设置到对象的属性中去 * 然后获取子类中的execute方法,执行后获得到对应的url * 对url进行请转发 */ public class BasicModelServlet extends HttpServlet{ @Override protected void doPost(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(); } } }
父类:继承HttpServlet,重写doPost,在doPost中获取子类getModel,得到模型类对象...封装数据
子类:继承父类servlet并实现ModelDriven<T>接口,重写getModel()返回T,重写execute()
3.标签驱动
普通form表单:
<%--注意name属性,必须是我们action中的数据名称.属性名--%> <input type="text" name="user.name"> <input type="text" name="user.passwd">
使用struts2的标签:
首先引入标签库,<%@taglibprefix="s"uri="/struts-tags" %>
<s:textfield name="user.username"/> <s:password name="user.password"/>
在action中定义数据,并设置get、set方法。
三、Web资源
Struts2获取Web资源的四种方式
1.使用Aware拦截器
使用Struts2 Aware拦截器来获取web资源,首先必须是在Action中进行,然后还得实现ServletRequestAware, ServletResponseAware, ServletContextAware接口来获取对应的ServletRequest、ServletResponse、ServletContext对象。要实现setServletRequest、setServletResponse、setServletContext方法。
2.RequestAware拦截器
使用RequestAware拦截器来获取web资源,只要实现RequestAware接口就可以了,然后实现其中的setRequest方法。不同的是,这里将ServletRequest、ServletResponse、ServletContext对象放置在了一个Map集合当中,需要使用Struts2当中的key值来获取对应的对象。
3.使用Struts2内置静态对象ActionContext
不需要实现任何的接口
ActionContext ac=ActionContext.getContext();
ServletRequest request=(ServletRequest)ac.get(ServletActionContext.HTTP_REQUEST)
4.使用Struts2内置静态对象ServletActionContext
这是四种方法中最好的方式,不需要实现任何接口,也可以按照需求获取所需的对象
ServletRequest request = ServletActionContext.getRequest(); ServletResponse response = ServletActionContext.getResponse(); ServletContext context = ServletActionContext.getServletContext();
5.在Struts2框架中使用Servlet的API
Web应用中通常需要访问的ServletAPI就是HttpServletRequest、HttpSession和ServletContext,这3个接口分别代表JSP内置对象中的request、session和application。
Struts2框架中提供了一个类 ActionContext,该类提供一些方法,通过方法获取Servlet的API:
- static ActionContext getContext() – 获取ActionContext对象实例
- java.util.Map<java.lang.String,java.lang.Object> getParameters() – 获取请求参数,相当于request.getParameterMap();
- java.util.Map<java.lang.String,java.lang.Object> getSession() – 获取的代表session域的Map集合,就相当于操作session域
- java.util.Map<java.lang.String,java.lang.Object> getApplication() – 获取代表application域的Map集合
- void put(java.lang.String key, java.lang.Object value) – 注意:向request域中存入值。
Struts2框架提供了一个类,ServletActionContext,该类中提供了一些静态的方法:
static PageContext getPageContext() static HttpServletRequest getRequest() static HttpServletResponse getResponse() static ServletContext getServletContext()
不要尝试直接在Action生成对客户端生成响应,没有任何意义。
使用EL表达式获取值:
${ sessionScope.msg }
${ requestScope.msg }
${ applicationScope.msg }
ServletContext,也就是Application,服务器对象,只要服务器不关闭,这个对象就永远存在。该信息是存储于服务器中的,一般数据我们是严禁存储到application对象中去的,因为这样很容易导致服务器内存溢出,程序崩溃。
它的实际应用场景是:在驾校考试系统中,用户只需要注册就可以免费答题,用户量庞大。而在页面中每次只出现一道题,答完之后显示下一道。使用Application对象来存储对应的题目信息,这样省去了我们每次都去数据库中获取下一道题目的步骤。在这种情况下,系统一启动就从数据库中获取全部的题目信息保存到Application对象中去,用户只需访问ServletContext,然后直接在界面中显示对应的数据就行。
四、Struts2标签
1.Struts2标签库的作用
Struts2标签库提供了主题、模板支持,极大地简化了视图页面的编写,而且struts2的主题、模板都提供了很好的扩展性。实现了更好的代码复用。Struts2允许在页面中使用自定义组件,这完全能满足项目中页面显示复杂,多变的需求。
Struts2的标签库有一个巨大的改进之处,struts2标签库的标签不依赖于任何表现层技术,也就是说strtus2提供了大部分标签,可以在各种表现技术中使用。包括最常用的jsp,Velocity和FreeMarker等模板技术。
2.Struts2标签使用前的准备:
(1)在要使用标签的jsp页面引入标签库:
<%@ taglib uri="/struts-tags" prefix="s"%>
(2)在web.xml中声明struts2核心控制器StrutsPrepareAndExecuteFilter。
3.OGNL
OGNL是Object-Graph Navigation Language的缩写,全称为对象图导航语言。
所谓对象图,即以任意一个对象为根,通过OGNL可以访问与这个对象关联的其它对象。
OGNL是一种功能强大的表达式语言,它通过简单一致的语法,可以任意存取对象的属性或者调用对象的方法,能够遍历整个对象的结构图,实现对象属性类型的转换等功能。
从语言角度来说:它是一个功能强大的表达式语言,用来获取和设置Java 对象的属性 , 它旨在提供一个更高抽象度语法来对 java 对象图进行导航。
Struts2框架使用OGNL作为默认的表达式语言。
OGNL 提供五大类功能:
1、支持对象方法调用
2、支持类静态的方法调用和值访问
3、访问OGNL上下文(OGNL context)和ActionContext
4、支持赋值操作和表达式串联
5、操作集合对象
4.OGNL用在什么地方
OGNL 在许多的地方都有应用,例如:
1) 作为 GUI元素(textfield,combobox,等)到模型对象的绑定语言。
2) 数据库表到 Swing的 TableModel的数据源语言。
3) web组件和后台 Model对象的绑定语言 (WebOGNL,Tapestry,WebWork,WebObjects)。
4) 作为 Jakarata Commons BeanUtils或者 JSTL的表达式语言的一个更具表达力的替代语言。
另外,java中很多可以做的事情,也可以使用 OGNL 来完成,例如:列表映射和选择。对于开发者来说,使用 OGNL,可以用简洁的语法来完成对 java对象的导航。通常来说:通过一个“路径”来完成对象信息的导航,这个“路径”可以是到 Java bean的某个属性,或者集合中的某个索引的对象,等等,而不是直接使用 get或者 set 方法来完成。
表达式语言(EL)本质上被设计为:帮助你使用简单的表达式来完成一些“常用”的工作。它被用来简化我们的工作,消除了重复代码的书写。
5.补充OGNL
1.OGNL表达式的计算是围绕OGNL上下文进行的。
OGNL上下文实际上就是一个Map对象,由ognl.OgnlContext类表示。它里面可以存放很多个JavaBean对象。它有一个上下文根对象。
上下文中的根对象可以直接使用名来访问或直接使用它的属性名访问它的属性值。否则要加前缀“#key”。
2.Struts2的标签库都是使用OGNL表达式来访问ActionContext中的对象数据的。
如:<s:propertyvalue="http://www.mamicode.com/xxx"/>。
3.Struts2将ActionContext设置为OGNL上下文,并将值栈作为OGNL的根对象放置到ActionContext中。
4.值栈(ValueStack):可以在值栈中放入、删除、查询对象。访问值栈中的对象不用“#”。Struts2总是把当前Action实例放置在栈顶。所以在OGNL中引用Action中的属性也可以省略“#”。
5.调用ActionContext的put(key,value)放入的数据,需要使用#访问。
6.OGNL中重要的3个符号:#、%、$:
#、%和$符号在OGNL表达式中经常出现,而这三种符号也是开发者不容易掌握和理解的部分,需要时间的积累才渐渐弄清楚……
1.#符号
#符号的用途一般有三种。
1) 访问非根对象属性,例如#session.msg表达式,由于Struts 2中值栈被视为根对象,所以访问其他非根对象时,需要加#前缀。实际上,#相当于ActionContext. getContext();#session.msg表达式相当于ActionContext.getContext().getSession(). getAttribute("msg") 。
2)用于过滤和投影(projecting)集合,如persons.{?#this.age>25},persons.{?#this.name==‘pla1‘}.{age}[0]。
3) 用来构造Map,例如示例中的#{‘foo1‘:‘bar1‘, ‘foo2‘:‘bar2‘}。
2.%符号
%符号的用途是在标志的属性为字符串类型时,计算OGNL表达式的值,这个类似js中的eval。
3.$符号
$符号主要有两个方面的用途。
1)在国际化资源文件中,引用OGNL表达式,例如国际化资源文件中的代码:reg.agerange=国际化资源信息:年龄必须在${min}同${max}之间。
2)在Struts 2框架的配置文件中引用OGNL表达式
7.值栈的概述
什么是值栈?
值栈就相当于Struts2框架的数据的中转站,向值栈存入一些数据。从值栈中获取到数据。
ValueStack是struts2提供的一个接口,实现类OgnlValueStack—值栈对象(OGNL是从值栈中获取数据的)
Action是多例的,有一起请求,创建Action实例,创建一个ActionContext对象,代表的是Action的上下文对象,还会创建一个ValueStack对象。
每个Action实例都有一个ValueStack对象 (一个请求对应一个ValueStack对象 )
在其中保存当前Action 对象和其他相关对象
Struts框架把ValueStack对象保存在名为 “struts.valueStack” 的请求属性中,request中 (值栈对象是 request一个属性)
ValueStack vs = (ValueStack)request.getAttribute("struts.valueStack");
8.值栈的创建和ActionContext对象的关系
1、值栈对象的创建,ValueStack 和 ActionContext 是什么关系?
值栈对象是请求时创建的。
ActionContext是绑定到当前的线程上,那么在每个拦截器或者Action中获取到的ActionContext是同一个。
ActionContext中存在一个Map集合,该Map集合和ValueStack的context是同一个地址。
ActionContext中可以获取到ValueStack的引用,以后再开发,使用ActionContext来获取到值栈对象
技术分析之获取到值栈的对象
2、如何获得值栈对象
获得值栈对象有三种方法 :
ValueStack v1 = (ValueStack) ServletActionContext.getRequest().getAttribute("struts.valueStack"); ValueStack v2 = (ValueStack)ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); ValueStack vs3 = ActionContext.getContext().getValueStack();
3、向值栈保存数据 (主要针对root栈)
valueStack.push(Object obj); //栈顶
push方法的底层调用root对象的push方法(把元素添加到0位置栈顶)
valueStack.set(String key, Object obj);
源码获取map集合(map有可能是已经存在的,有可能是新创建的),把map集合push到栈顶,再把数据存入到map集合中。
4、在JSP中获取值栈的数据
(1)总结几个小问题:
访问root中数据 不需要#
访问context其它对象数据 加 #
如果向root中存入对象的话,优先使用push方法。
如果向root中存入集合的话,优先要使用set方法。
(2)在OgnlContext中获取数据
在Action中向域对象中存入值:request、session、Application、attr、Parameters。
<s:property value="#request.msg"/> <s:property value="#session.msg"/> <s:property value="#parameters.id"/> <s:property value="#attr.msg"/>
采用pageContext对象往page范围内存入值来 验证#attr搜索顺序是从page开始的,搜索的顺序为:page,request,session,application。
<!-- 在JSP页面上,查看值栈的内部结构 --> <s:debug></s:debug>
五、拦截器
1.什么是拦截器
拦截器(Interceptor)是Struts 2的一个强有力的工具,有许多功能都是构建于它之上,如国际化、转换器,校验等。
拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。
说到拦截器有一个东西不能落下——拦截器链(Interceptor Chain,在Struts 2中称为拦截器栈Interceptor Stack)。
拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
2.实现原理
Struts 2的拦截器实现相对简单。当请求到达Struts 2的ServletDispatcher时,Struts 2会查找配置文件,并根据其配置实例化相对的拦截器对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器。
3.拦截器配置
拦截器必须配置在package包下的第一个位置,使用<interceptors>标签来进行配置,可以配置多个拦截器,使用<interceptor>标签来进行配置。还可以在拦截器中设置其属性的,使用<param>标签来对其属性进行赋值。
在配置好拦截器之后,想要在某个Action中使用拦截器,则使用<interceptor-ref>标签来进行指定。
注意:其实系统在执行Action之前都会执行默认的拦截器,也就是说每一个Action标签当中其实都有一句:<interceptor-ref name="defaultStack"/>
但是这种情况在你配置了一个自定义的拦截器之后便会改变。自定义的拦截器会将默认的拦截器覆盖掉,这时系统中只有一个自定义拦截器起作用,但是我们知道Struts2当中的许多功能都是基于默认的拦截器实现的,如国际化、转换器,校验等。所以我们在引入了自定义的拦截器之后一定要引入默认的拦截器到Action当中。
4.拦截器栈
在Struts.xml文件当中也可以自定义拦截器栈
<interceptor-stack name="myInterceptors" > <interceptor-ref name="myInterceptor1"/> <interceptor-ref name="myInterceptor2"/> <!--必须设置默认拦截器--> <interceptor-ref name="defaultStack"/> </interceptor-stack>
5.全局拦截器和全局result
<!--系统默认拦截器,对这个包中的所有action都适用--> <default-interceptor-ref name="myInterceptors"/> <!--全局的返回result,当包中任何一个action方法返回error都会去error界面--> <global-results> <result name="error">/error.jsp</result> </global-results>
6.Action的三种访问方式
调用Action中的execute方法,一个类只能写一个方法,浪费类资源,我们可以在Action类中定义自己的方法,只需在配置文件中配置即可。
1、通过action标签中的method属性,访问到Action中的具体的方法。
2、通配符的访问方式:访问的路径和方法的名称必须要有某种联系。
通配符就是 * 代表任意的字符,使用通配符的方式可以简化配置文件的代码编写,而且扩展和维护比较容易。
<!--实际项目开发中所使用的通配符配置,第一个*是类名,第二个*是类中对应的方法 在表单的action中也要按照这种方式来编写。其实,使用通配符来配置同时也是一种编码规范--> <action name="*_*" class="com.lhy.demo.action.{1}Action" method="{2}"> <result name="{1}_{2}_success">/demo/{1}{2}index.jsp</result> </action>
3、动态方法访问的方式
如果想完成动态方法访问的方式,需要开启一个常量,struts.enable.DynamicMethodInvocation = false,把值设置成true。
注意:不同的Struts2框架的版本,该常量的默认值不一定是true或者false,需要自己来看一下。如果是false,需要自己开启。在struts.xml中开启该常量:
<constant name="struts.enable.DynamicMethodInvocation" value="true"></constant>
页面代码:
<a href="${pageContext.request.contextPath}/product!add.action">添加商</a>
路径的写法:[actionName]![methodName]
7.方法拦截器
对方法的拦截,应该使用方法拦截器。方法拦截器和普通的拦截器有所不同,普通的拦截器是实现Interceptor接口而方法拦截器是继承自MethodFilterInterceptor。
8.拦截器和过滤器
拦截器和过滤器的区别:
1、拦截器是基于Java反射机制的,而过滤器是基于函数回调的。
2、过滤器依赖于servlet容器,而拦截器不依赖于servlet容器。
3、拦截器只能对Action请求起作用(Action中的方法),而过滤器可以对几乎所有的请求起作用(CSS JSP js)。
4、拦截器可以访问Action上下文、值栈里的对象,而过滤器不能。
5、在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。
拦截器采用责任链模式
在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。
责任链每一个节点,都可以继续调用下一个节点,也可以阻止流程继续执行。
在struts2 中可以定义很多个拦截器,将多个拦截器按照特定顺序 组成拦截器栈 (顺序调用 栈中的每一个拦截器 )。
六、国际化
1.国际化i18n
国际化可以使我们的网站切换为中英或其他的各国语言
Struts2中的国际化就是 i18n —— Internationalization(其实就是字母i和n之间有18个字母)
国际化的加入使得软件具备切换界面语言的功能同时也极大地简化国际化功能的实现。
2.国际化的实现
只需要通过一个简单的配置文件和一个Struts2的标签以及一个默认的i18n拦截器就可以实现了。实现步骤:
1、在配置文件struts.xml中加入国际化支持
<!--配置国际化支持--> <constant name="struts.custom.i18n.resources" value="xxx"/>
2、在与struts.xml文件同目录下创建以下两个文件。
xxx_en_US. properties (配置英文信息,xxx部分和在struts.xml中配置的value相同)
xxx_zh_CN. properties(配置中文信息,xxx部分和在struts.xml中配置的value相同)
xxx_en_US. properties 内容:
login.username=username login.password=password login.submitt=submit
xxx_zh_CN. properties 内容:
login.username=\u8d26\u53f7 login.password=\u5bc6\u7801 login.submit=\u767b\u5f55
注意:properties文件里的中文信息配置得使用ASCII码来配置,配置的中英文配置文件中的key值必须相同,其value值便使用其不同的语言编写。
3、在jsp中使用struts的标签完成界面所有内容
注意:在Struts2国际化的时候,在submit提交表单标签当中不能使用key来指定对应的值,而必须使用value,\,%是表达式语言当中对字符串计算值的引用。 这是struts2国际化的一个漏洞。
<s:submit value="%{getText(login.submit)}"/> <s:a href="chLanguageAction.action?request_locale=zh_CN">中文</s:a> <s:a href="chLanguageAction.action?request_locale=en_US">English</s:a>
按照这样,将网站中所有的文字都在配置文件中配置成不同种的语言,就可以实现不同种语言的切换,也可以利用拦截器来获取cookies中的lang值来自动设置网站的语言。
七、令牌
1.令牌机制和原理
令牌(Token)可以防止表单被重复提交。
当用户在提交表单的过程中如果遇到网络拥塞便会回退或者重复点击提交按钮,假如我们不对其做任何处理的话那么运行在服务器上的程序便会崩溃。所以Struts2为我们提供了一套解决表单重复提交的方法-----令牌机制。
该方法的基本原理是:
服务器端在处理到达的request之前,会将request中的Token值与保存在当前用户session中的令牌值进行比较,看是否匹配。在处理完该request后,且在response发送给客户端之前,将会产生一个新的Token,该Token除传给客户端以外,也会将用户session中保存的旧的Token进行替换。这样,如果用户会退到刚才的提交页面并再次提交的话,客户端传过来的Token值和服务器端的不一致,从而有效地防止了重复提交地发生。
2.实现令牌验证步骤
1、jsp页面加入标签支持
<%@ taglib prefix="s" uri="/struts-tags" %>
2、表单中加入
<s:token></s:token>
3、struts.xml中在需要验证重复提交的action中加入验证拦截器
<interceptor-ref name="token" /> <result name="invalid.token">repeatsubmit.jsp</result>
八、数据校验
1.数据校验的意义
我们的网站是要与用户进行交互的,其中最重要的交互方式便是数据的输入输出。但对于用户来说,并不是会按照我们所期望的形式来进行输入,这是就需要对用户输入的数据的格式进行校验,也就是所谓的数据校验。
其实数据校验意义就是保证进入后台的数据都是安全的!
在大多数情况下我们使用的是JavaScript来进行输入数据的校验,但是这种方式具有局限性,有些数据需要到后台才能校验。
目前主流的web层框架都具备校验的相关功能,我们的Struts2提供了两种较为简易的校验方式:
硬编码方式---易理解、不易维护
xml配置方式---易维护、易管理、不侵入源代码(推荐)
2.硬编码方式校验
硬编码方式实现步骤:
第一步:创建Struts2项目
第二步:编写一个普通表单
第三步:在jsp中加入标签库支持
第四步:jsp中加入struts2校验框架提供的两种校验级别错误:
属性级错误:<s:fielderror cssStyle="color:red;"/>
Action级错误:<s:actionerror cssStyle="color:red;"/>
通常属性校验失败我们将错误信息放入fielderror对象中,action级别错误信息放入actionerror 对象中。
第五步:创建Action类,配置到struts.xml中
注意一定要配置一个result,name值为input,用于验证失败跳转页面
第六步:Action类中创建校验方法,方法命名规则:
validate+要验证的方法名(首字母大写)
(例如:execute()方法,校验方法validateExecute())
假如action中有很多方法,也可以分开验证。
第七步:完善校验方法中具体的判断
错误信息共分为两种:FieldError和ActionError
将错误信息放入Field域中:this.addFieldError("username","用户名不能为空");
将错误信息放入Action域中:this.addActionError("请两次密码必须一致");
实际上会将错误信息放入Struts2默认栈队map集合中
页面可以使用 ${errors.username[0]}来单独展示属性错误信息
复杂验证例如邮箱、电话等判断需要用到正则表达式!
3.xml配置方式校验
Xml配置方式实现步骤:
第一步:创建Struts2项目,创建实体类Users
第二步:编写一个普通表单
第三步:在jsp中加入Struts2标签库支持
第四步:jsp中加入Struts2校验框架提供了两种校验级别错误:
属性级错误:<s:fielderror cssStyle="color:red;"/>
Action级错误:<s:actionerror cssStyle="color:red;"/>
单属性方式页面错误信息:${errors.username[0]}
对象方式页面错误信息:${errors["user.username"][0]}
第五步:创建Action类,配置到struts.xml中
注意一定要配置一个result,name值为input,用于验证失败跳转页面
第六步:在action类同包下创建一个Xml配置文件,该文件用于写校验信息
命名规则:Action名-validation.xml(例:UserAction-validation.xml)
第七步:编写UserAction-validation.xml校验信息
内容参考网站:http://blog.csdn.net/icarus_wang/article/details/52015756 struts2学习
http://blog.csdn.net/shijing_0214/article/details/51024147 struts2请求过程源码分析
http://0001111.iteye.com/blog/1582939 标签
struts2知识汇总