本文包括以下五个部分:
一、值栈
采用servlet和JSP开发时,servlet通过域对象保存数据,在JSP页面通过jstl标签+el表达式获取数据。
采用struts2和JSP框架进行开发时,Action通过值栈对象保存数据,在JSP页面通过struts标签+ognl表达式获取数据。
1.1 值栈(ValueStack)的概念
值栈就是存放action的堆栈,用来存储请求响应数据,是Struts2存取数据的核心。值栈将数据统一管理起来,方便Action、Result、Interceptor的使用。值栈有一个标准接口ValueStack,而在实际的项目开发中,是通过一个实现类OgnlValueStack来存储数据的。
1.2 值栈的结构
值栈分为值栈分为两个逻辑结构(数据结构):
- Object Stack(对象栈):ArrayList (CompoundRoot),底层数据结构为ArrayList集合+栈的结构(先进后出)。
对象栈主要存储Action对象和Provider代理对象。
- Context Map(映射栈): HashMap (OgnlContext),底层的数据结构为Map集合的结构。
a. 映射栈主要存放各个域存放的数据和用户的参数信息。
b. 对象栈主要有五个对象:
Key Value request RequestMap session SessionMap application ApplicationMap parameters ParaemterMap attr AttributeMap (封装了三个Map(request,session,application))
1.3 操作值栈对象
1.3.1 操作Object Stack
//得到ActionContext对象 ActionContext ac = ActionContext.getContext(); //得到值栈对象 ValueStack vs = ac.getValueStack();
push(Object o):压入栈顶
Object pop():推出栈顶元素
1.3.2 操作Context Map
Map<String,Object> getContext(): 得到Context Map对象
ActionContxt.get("request").put("name",Object): 操作Context Map中的reqest元素
ActionContxt.getSession().put("name",Object): 操作Context Map中的session元素
ActionContxt.getApplication().put("name",Object): 操作Context Map中的application元素
package edu.scut.d_valuestack; import java.util.Date; import java.util.Map; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.util.ValueStack; //演示值栈 public class BookAction extends ActionSupport { public String list(){ //值栈是由struts2框架在每次访问Action的方法时创建的,然后将其存入ActionContext //1 得到ActionContext对象 ActionContext ac = ActionContext.getContext(); //2 得到值栈对象 ValueStack vs = ac.getValueStack(); System.out.println(vs); //3 操作值栈 //Object Stack(对象栈) Book book = new Book(); book.setName("java编程思想"); book.setPrice(58); book.setPublishtime(new Date()); //3.1 压入元素(栈定) vs.push(book); System.out.println(vs); //3.2 推出元素 vs.pop(); System.out.println(vs); //4 映射栈(Context Map) //4.1 操作request属性 Map requestMap = (Map) ac.get("request"); requestMap.put("r_book", book); //4.2 操作session属性 ac.getSession().put("s_book", book); //4.3 操作application属性 ac.getApplication().put("c_book", book); System.out.println(vs); return SUCCESS; } }
二、Ognl表达式
2.1 Ognl表达式简介
OGNL是Object GraphicNavigation Language(对象图导航语言)的缩写,它是一个开源项目。 Struts2框架使用OGNL作为默认的表达式语言。在struts2项目中导入ognl.jar 包来实现支持ognl表达式。
2.2 Ognl表达式和EL表达式的比较
Ognl表达式 EL表达式 获取域对象的数据。可以存放数据,可以调用方法。
获取域对象的数据。不能存放数据,不能调用方法
2.3 Ognl表达式的优势
- 支持对象方法调用,如xxx.doSomeSpecial();
支持类静态的方法调用和值访问,表达式的格式:
@[类全名(包括包路径)]@[方法名 | 值名],例如:
@java.lang.String@format('foo %s', 'bar')或者
@tutorial.MyConstant@APP_NAME。
支持赋值操作和表达式串联,如(price=10,discount=0.6,calculatePrice()),这个表达式会返回6.0;
- 可以访问OGNL上下文(OGNL context)和ActionContext;
- 可以操作集合对象。
2.4 Ognl表达式的核心对象(OgnlContext)
2.4.1 Ognl表达式的核心对象OgnlContext对象的使用
package edu.scut.a_ognl; import ognl.Ognl; import ognl.OgnlContext; import ognl.OgnlException; import org.junit.Test; //演示Ognl public class OgnlDemo { //1 学习了解Ognl表达式的核心对象OgnlContext对象的使用 @Test public void test1(){ User user = new User(); user.setId(1); user.setUserName("乔峰"); user.setUserPsw("666666"); //1 创建一个OgnlContext对象 OgnlContext context = new OgnlContext(); //2 把user对象存入OgnlContext对象 context.put("user", user); //3 从OgnlContext对象取出数据 User user2 = (User) context.get("user"); System.out.println(user2.getId()+"\t"+user2.getUserName()+"\t"+user2.getUserPsw()); } }
2.4.2 使用Ognl表达式取出OgnlContext的数据,根对象不需要有key,取值不需要#package edu.scut.a_ognl; import ognl.Ognl; import ognl.OgnlContext; import ognl.OgnlException; import org.junit.Test; //演示Ognl public class OgnlDemo { //2 使用Ognl表达式取出OgnlContext的数据,根对象不需要有key,取值不需要# @Test public void test2() throws Exception{ User user = new User(); user.setId(3); user.setUserName("段誉"); user.setUserPsw("88888888"); //1 创建一个OgnlContext对象 OgnlContext context = new OgnlContext(); //2 把user对象存入OgnlContext对象 //根对象要存储数据需要key context.setRoot(user); //3 从OgnlContext对象取出数据,使用Ognl表达式取数据 Object ognlObj = Ognl.parseExpression("userName"); Object result = Ognl.getValue(ognlObj, context, context.getRoot()); System.out.println(result); } }
2.4.3 使用Ognl表达式取出OgnlContext的数据,非根对象需要有key,取值需要#
package edu.scut.a_ognl; import ognl.Ognl; import ognl.OgnlContext; import ognl.OgnlException; import org.junit.Test; //演示Ognl public class OgnlDemo { <pre name="code" class="java"> //3 使用Ognl表达式取出OgnlContext的数据,非根对象需要有key,取值需要# @Test public void test3() throws Exception{ User user = new User(); user.setId(2); user.setUserName("虚竹"); user.setUserPsw("7777777"); //1 创建一个OgnlContext对象 OgnlContext context = new OgnlContext(); //2 把user对象存入OgnlContext对象 //非根对象要存储数据需要key context.put("user", user); //3 从OgnlContext对象取出数据,使用Ognl表达式取数据 Object ognlObj = Ognl.parseExpression("#user.userName"); Object result = Ognl.getValue(ognlObj, context, context.getRoot()); System.out.println(result); }}
2.4.4 Ognl表达式调用静态方法
package edu.scut.a_ognl; import ognl.Ognl; import ognl.OgnlContext; import ognl.OgnlException; import org.junit.Test; //演示Ognl public class OgnlDemo { //4 Ognl表达式调用静态方法 @Test public void test4() throws Exception{ System.out.println(Math.round(12.55)); //1 创建一个OgnlContext对象 OgnlContext context = new OgnlContext(); //使用Ognl表达式取数据 Object ognlObject = Ognl.parseExpression("@Math@round(10.5)"); Object result = Ognl.getValue(ognlObject, context, context.getRoot()); System.out.println(result); } }
结论:
a. 从OgnlContext对象的根对象取出数据,不需要#号。
b. 从OgnlContext对象的非根对象取出数据,需要#号。
2.5 值栈如何共享和获取数据
2.5.1 值栈如何传递到jsp页面
struts2框架在Action中最后把值栈对象传递到jsp页面,是通过request域对象的struts.valueStack名称的属性传递到jsp页面的。
class Action{ 1.创建OgnlValueStack对象 public String list(){ 2.得到OgnlValueStack对象,然后操作OgnlValueStack对象 } 3.把OgnlValueStack对象放入request域对象 request.setAttribute("struts.valueStack",OgnlValueStack对象); }
2.5.2 查看值栈对象所有内容
<s:debug/>
2.5.3 获取值栈数据2.5.3.1 获取值栈数据的条件
a. 必须使用ognl表达式获取,需要学习ognl表达式语法。
b. ognl表达式必须依赖struts2的标签。
struts2获取数据的标签: <s:property value="ognl表达式"/>2.5.3.1 获取值栈数据的规则
a. 获取Object Stack: 不带#号直接获取
(a) 获取对象(对象栈): [下标] 下标从0开始 。
规则:从对象栈的指定下标的元素开始查找,直到最后一个元素为止!
[0] 从第一个元素开始查找
[1] 从第二个元素开始查找
<s:property value="[1]"/>(b) 获取对象属性: [下标].属性 注意:下标可以不写,默认从第一个元素开始查找。
规则:从对象栈的指定下标的元素开始查找指定的属性,如果找到了就直接返回了,如果没有继续查找,直到最后一个元素为止!<s:property value="[1].name"/>b. 获取Context Map(映射栈):带#号获取
<s:property value="#name"/> <s:property value="#request.r_book.price"/><br/> <s:property value='#session.s_book.price'/> <s:property value='#application.c_book.price'/> <s:property value="#parameters.email"/> <s:property value="#attr.request.r_book.price"/>
三、struts2标签库
3.1 逻辑类标签: 类似于jstl里面的的条件判断,循环等,用于处理jsp页面的逻辑
<s:set/> 保存数据
<s:property/> 获取数据
<s:if/>+<s:elseif/>+<s:else> 条件判断
<s:iterator/> 循环
<body> <%-- <s:set> 设置数据, <s:property> 获取数据--%> <s:set var="message" value="'itcast'" scope="request"/> <s:set var="message" value="'itcast'" scope="session"/> <s:set var="message" value="'itcast'" scope="application"/> <s:property value="#request.message"/> <hr> <%--条件判断 <s:if> <s:elseif> <s:else> --%> <%--默认登录数据放在ContextMapd的session中 --%> <s:set var="loginInfo" value="'rose'" scope="session" ></s:set> <s:if test="#session.loginInfo==null"> 请先登录 </s:if> <s:else> 欢迎你,用户名:<s:property value="#session.loginInfo"/> </s:else> <hr> <%--<S:iterator>循环迭代 --%> <s:iterator value="#request.books" status="vs" id="book"> 序号:<s:property value="#vs.count"/> 书名:<s:property value="#book.name"/> 价格:<s:property value="#book.price"/><br> </s:iterator> </body>
3.2 UI标签:用于简化html标签(表单)使用
<s:form>
<s:textfield>
<s:password>
<s:submit>
<s:reset>
<s:checkbox>
*<s:checkboxlist>
*<s:radio>
*<s:select>
<body> <s:form action="book_list" namespace="/ognl"> <%--Struts2的UI部分不是代表ognl表达式,如果要让他们成为OGNL表达式,要加上%{} %{ognl表达式} --%> <s:textfield label="用户名" name="curUser.userName" cssClass="style2"></s:textfield> <s:password label="密码" name="curUser.userPsw" ></s:password> <s:textarea label="简介" name="info"></s:textarea> <s:submit value="注册" align="left"></s:submit> <s:checkbox name="hobby" label="篮球" value="打篮球"></s:checkbox> <s:checkbox name="hobby" label="足球" value="足球"></s:checkbox> <s:checkbox name="hobby" label="网球" value="网球"></s:checkbox> <%-- 可以用ognllist进行数据的展示 ognl表达式创建List集合 ognl表达式创建Map集合 --%> <%--label和value一致的情况 --%> <s:checkboxlist name="hobby" list="{'篮球','足球','网球'}"></s:checkboxlist> <s:checkboxlist name="types" list="#request.types"></s:checkboxlist> <%--lable和value不一致的情况 --%> <s:checkboxlist name="hobby" list="#{'eat':'吃','drink':'喝','play':'玩'}"></s:checkboxlist> <%--list:所以checkbox的值 value:默认选中的checkbox的值 --%> <s:checkboxlist name="types" list="#request.typesMap" value="curTypes" ></s:checkboxlist> <s:select name="types" list="#request.typesMap" value="curTypes"></s:select> <s:radio list="#request.typesMap" value="%{curTypes}"></s:radio> <s:reset value="重置" align="left" theme="simple"></s:reset> </s:form> </body>
四、国际化
国际化步骤:
1. 在src目录下创建resource包,在resource包下创建国际化的properties文件:
message_en_US.properties:
user=USERNAME password=PASSWORD login=LOGINmessage_zh_CN.properties:
user=\u7528\u6237\u540D password=\u5BC6\u7801 login=\u767B\u5F552. 统一绑定资源包。
在struts.xml配置文件中添加国际化的常量配置。
<!-- 修改国际化资源包的路径 --> <constant name="struts.custom.i18n.resources" value="resource.message"></constant>3. 使用国际化
a. Action:注意action类要继承ActionSupport类,用getText()方法获取需要国际化的属性。
package edu.scut.a_ognl; import com.opensymphony.xwork2.ActionSupport; public class BookAction extends ActionSupport{ public String i18n(){ //获取国际化内容 String user = getText("user"); String password = getText("password"); String login = getText("login"); System.out.println(user); System.out.println(password); System.out.println(login); return "i18n"; } }b. JSP页面:注意用key接收需要国际化的属性。<body> <%--在标签中使用国际化内容 --%> <s:form> <s:textfield key="user"/> <s:password key="password"/> <s:submit key="login"/> </s:form> </body>
五、表单数据校验
采用javascript页面前端进行验证安全性不够,容易被绕过去,为了提高安全性,一般要进行后台验证。
5.1 使用代码验证
使用代码进行验证的方式灵活太差,一般比较少用。
5.1.1 全局验证(所有的方法都验证)
a. 编写一个Action类,继承ActionSupport(为了实现Valiateable接口)。b. 覆盖validate()方法。
b. 覆盖validate()方法。
下面的代码中对reg()和list()两个方均进行了验证。
package edu.scut.b_validate; import org.hibernate.validator.constraints.Email; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; //采用方式进行验证,必须继承actionSupport public class UserAction extends ActionSupport implements ModelDriven<User> { //接受页面数据 private User user = new User(); @Override public User getModel() { // TODO Auto-generated method stub return user; } /** * 1)用户名不能为空,必须为数字或字母,长度4-16位 * 2)密码不能为空,必须为数字,长度为6-16位 */ @Override public void validate() { System.out.println("执行数据校验!"); System.out.println(user); if(user!=null){ //进行数据校验 //用户名 if(user.getName()==null || user.getName().equals("")){ //用户名不能为空 //将错误信息带入注册页面 addFieldError("username", "用户名不能为空!"); }else{ //必须是字母或者数字。并且长度为4-16位 if(!user.getName().matches("[0-9a-zA-Z]{4,16}")){ addFieldError("username", "用户名格式错误!"); }; } //密码 if(user.getPassword()==null || user.getPassword().equals("")){ //密码不能为空 addFieldError("password", "密码不能为空!"); }else{ //必须为数字,4-16位 if(!user.getPassword().matches("[0-9]{6,16}")){ addFieldError("password", "密码格式有误!"); } } } public String reg(){ System.out.println(user); return SUCCESS; } <span style="color:#FF0000;"><span style="color:#000000;"></span></span><pre name="code" class="java"> public String list(){ System.out.println(user); return "list"; }}
c. 在struts.xml文件中对应的action配置加上input视图,然后struts2就会自动把错误信息转发到input视图的页面上去。
<result name="input">/reg.jsp</result>d. 在input视图页面上,打印出错误信息。
<s:fielderror></s:fielderror>5.1.2 局部验证(对一个方法验证)
局部校验时,只需要把需要验证的方法名改成固定格式(validate+需要验证的方法名称)即可。
例如,要对只对reg()方法验证,而不对其他方法验证,只需将reg()方法名称改成validateReg()即可。
//代码方式局部验证,编写validate+需要验证的方法名称!!! public void validateReg(){ //有错误信息,struts2会自动跳转到input页面 if(user!=null){ //用户名验证 if(user.getName()==null || user.getName().equals("")){ addFieldError("name", "用户名不能为空!"); }else{ if(!user.getName().matches("[0-9a-zA-Z]{4,16}")){ addFieldError("name", "用户名格式错误!"); } } } }
5.2 使用配置文件验证
为了提高数据校验的灵活性,可以使用配置文件的方式完成校验。
5.2.1 全局验证(所有的方法都验证)
这种配置方式对action的所有方法都生效。
a. 编写一个xml文件,名称: Action文件名-validation.xml。
b. 该xml文件必须放在Action文件的同一目录。
例如:UserAction-validation.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd"> <validators> <!-- 验证一个属性 name是属性名 --> <field name="name"> <!-- 属性验证器 --> <field-validator type="requiredstring"> <!-- 错误信息 --> <message key="name.requried"></message> </field-validator> <field-validator type="regex"> <!-- 给验证器注入一个参数 --> <param name="expression">[0-9a-zA-Z]{4,16}</param> <!-- 错误信息 --> <message key="name.formaterror"></message> </field-validator> </field> <field name="email"> <field-validator type="requiredstring"> <message key="email.requried"></message> </field-validator> <field-validator type="email"> <message key="email.formaterror"></message> </field-validator> </field> </validators>5.2.2 局部验证(对一个方法验证)
这种配置方式对action的指定方法都生效。
a. 编写一个xml文件,名称: Action文件名-访问方法路径-validation.xml。
b. 该xml文件必须放在Action文件的同一目录。
例如:UserAction-user_reg-validation.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd"> <validators> <!-- 验证一个属性 name是属性名 --> <field name="name"> <!-- 属性验证器 --> <field-validator type="requiredstring"> <!-- 错误信息 --> <message key="name.requried"></message> </field-validator> <field-validator type="regex"> <!-- 给验证器注入一个参数 --> <param name="expression">[0-9a-zA-Z]{4,16}</param> <!-- 错误信息 --> <message key="name.formaterror"></message> </field-validator> </field> <field name="email"> <field-validator type="requiredstring"> <message key="email.requried"></message> </field-validator> <field-validator type="email"> <message key="email.formaterror"></message> </field-validator> </field> </validators>
六、拦截器
6.1 拦截器简介
拦截器的功能类似于过滤器,但是过滤器可以过滤项目的任何请求(servlet/jsp/html/img),拦截器只能拦截Action资源。拦截器是struts2框架的核心,因为struts2的核心功能都是通过拦截器来实现的。
例如:
参数的接收拦截器:com.opensymphony.xwork2.interceptor.ParametersInterceptor
文件上传拦截器:org.apache.struts2.interceptor.FileUploadInterceptor
国际化拦截器:com.opensymphony.xwork2.interceptor.I18nInterceptor
6.2 拦截器和过滤器的比较
拦截器 | 过滤器 |
可以拦截代码 | 可以拦截代码 |
struts2的组件之一,主要是拦截的action(方法) | servlet的三大组件之一,主要是拦截请求(静态内容和动态内容)和响应 |
6.3 自定义拦截器的开发步骤
struts2框架有很多拦截器,来支持其核心功能。但是,有时候不能满足实际开发的需求,用户需要自定义具有特殊功能的拦截器。struts2支持自定义拦截器。 自定义拦 截器的开发步骤如下:
Example
编写两个拦截器:
MyInterceptor1
package edu.scut.d_Interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.Interceptor; public class MyInterceptor1 implements Interceptor { @Override public void destroy() { } @Override public void init() { } @Override public String intercept(ActionInvocation invocation) throws Exception { System.out.println("1 执行interceptor1的action前面的代码!"); String result = invocation.invoke(); System.out.println("5 执行interceptor1的action后面的代码!"); return result; } }MyInterceptor2
package edu.scut.d_Interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.Interceptor; public class MyInterceptor2 implements Interceptor { @Override public void destroy() { } @Override public void init() { } @Override public String intercept(ActionInvocation invocation) throws Exception { System.out.println("2 执行interceptor2的action前面的代码!"); String result = invocation.invoke(); System.out.println("4 执行interceptor2的action后面的代码!"); return result; } }
action类
package edu.scut.d_Interceptor; import com.opensymphony.xwork2.ActionSupport; //图书操作类 public class BookAction extends ActionSupport { //接收页面参数 private String name; public void setName(String name) { this.name = name; } private String password; public void setPassword(String password) { this.password = password; } public String list(){ System.out.println("3 执行了图书的list方法!"); System.out.println(name); System.out.println(password); return SUCCESS; } }b. 在struts.xml文件中配置拦截器,并定义拦截器栈。。
注意:在配置action时,引用了自定义的拦截器栈后,默认的拦截器栈(defaultStack)也要引用,并且放在配置的第一位置。否则,struts2的很多核心功能将实效。例如不能接收页面传过来的参数。
<?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> <package name="interceptor" extends="struts-default" namespace="/interceptor"> <!-- 配置自定义拦截器 --> <interceptors> <!-- 定义拦截器 --> <interceptor name="interceptor1" class="edu.scut.d_Interceptor.MyInterceptor1"/> <interceptor name="interceptor2" class="edu.scut.d_Interceptor.MyInterceptor2"/> <!-- 定义拦截器栈 --> <interceptor-stack name="myStack"> <!-- 一个拦截器栈包含多个拦截器 --> <!-- 注意:struts2的默认拦截器一定要配置在第一位!否则会被覆盖! --> <interceptor-ref name="defaultStack"></interceptor-ref> <interceptor-ref name="interceptor1"></interceptor-ref> <interceptor-ref name="interceptor2"></interceptor-ref> </interceptor-stack> </interceptors> <action name="book_*" class="edu.scut.d_Interceptor.BookAction" method="{1}" > <!-- 引入拦截器 --> <interceptor-ref name="myStack"></interceptor-ref> <result >/succ.jsp</result> </action> </package> </struts>
以上Example的运行结果:1 执行interceptor1的action前面的代码!2 执行interceptor2的action前面的代码!3 执行了图书的list方法!4 执行interceptor2的action后面的代码!5 执行interceptor1的action后面的代码!
可以看出,页面发送的请求,必须经过拦截器才能访问action类,而且通过拦截器的顺序是根据struts2里面的配置顺序进行的。执行了第一个拦截器的(String result = invocation.invoke();)才能放行,依次执行后面的拦截器或者方法。
七、struts2的执行过程
下面这个图不是我自己画的,但是为了说明问题,我引用一下。跟着步骤仔细看一遍,会加深理解。