路线:
- Struts2的概述、Struts2的入门、Struts2常见的配置、Struts2的Action的编写
- Struts2的数据的封装、结果页面配置
- Struts2的值栈和OGNL表达式
- Struts2的标签库
上一篇:Struts2 快速笔记 1
1. OGNL表达式
-
OGNL:Object Graph Navigation Language,功能强大的表达式语言——比EL表达式,对象导航语言表达式。
- EL:只能从域对象中获取数据,11个域对象——request/session/pagecontext等:${name},${request}
- OGNL:可以调用对象的方法,域对象数据,以及Struts2的值栈的数据。OGNL是独立的第三方语言,Struts将其引用到了,单独也可以使用。
-
OGNL的功能:
- 支持运算符操作
- struts2中支持对象方法的调用
- Struts2中支持静态方法和值的引用
- 支持赋值和表达式串联
- 访问OGNL上下文OGNLContext和ActionContext——与值栈有关
- 操作集合对象,或者new新的对象
-
使用3要素:Java中使用,不仅仅在struts2
- 表达式:从哪里获取什么数据
- 根对象:操作目标
- Context对象:在哪里操作,操作环境
示例(Java中):
-
包:
-
Demo
@Test /** * OGNL调用对象方法 */ public void demo1() throws OgnlException{ //3要素 context root source OgnlContext ognlContext = new OgnlContext(); Object root = ognlContext.getRoot(); Object obj = Ognl.getValue("'helloworld'.length()", ognlContext, root); System.out.println(obj); } @Test /** * OGNL调用静态方法和值: * @对象全路径名@方法(参数) * @对象全路径名@变量名 */ public void demo2() throws OgnlException{ //3要素 context root source OgnlContext ognlContext = new OgnlContext(); Object root = ognlContext.getRoot(); Object obj = Ognl.getValue("@java.lang.Math@random()", ognlContext, root); Object obj2 = Ognl.getValue("@java.lang.Math@PI", ognlContext, root); System.out.println(obj); } @Test /** * Context中的数据需要加# * Root对象中的数据不需要 */ public void demo3() throws OgnlException{ User user = new User("Tom", "123"); OgnlContext ognlContext = new OgnlContext(); //1.Context中 /* ognlContext.put("user", user); Object obj = Ognl.getValue("#user", ognlContext, ognlContext.getRoot()); System.out.println(obj);*/ //2.Root中,访问的是对象的属性 ognlContext.setRoot(user); Object obj2 = Ognl.getValue("username", ognlContext, ognlContext.getRoot()); System.out.println(obj2); }
-
OGNL在Struts2中使用:主要是从JSP页面的数据交互
-
页面引入标签:
<%@ taglib uri="/struts-tags" prefix="s" %>
-
获取对象数据
<h1>Struts2的OGNL表达式</h1> <h3>1.访问对象方法</h3> <s:property value="'hello'.length()"/> <br/> <h3>2.访问静态方法-----struts2 默认将其关闭,如@java.lang.Math@random()-------可以在struts的核心包中default.properties中配置常量struts.ognl.allowStaticMethodAccess=true</h3> <s:property value="@java.lang.Math@random()"/> <br/> <h3>3.访问静态成员-----struts2 可以如@java.lang.Math@PI</h3> <s:property value="@java.lang.Math@PI"/>
-
OGNL其最主要的作用——值栈
-
2. 值栈空间
-
概念
ValueStack,是一个容器(栈),有struts框架创建。当前端页面如JSP发送一个请求时,Struts的默认拦截器就会将请求中的数据进行封装,然后将其放入ValueStack的栈顶。
前端的请求将会被封装成对象,放入值栈容器中。在使用Struts2框架的开发中,一般前端数据的中转都由值栈来进行,尽量较少的使用域对象(request,pageContext等),但也要记住struts中也能使用这些对象。有一个Action的实例,就会有一个值栈的对象,Action是多例的。
**作用:**存入ValueStack中的数据,在struts2中任何位置能获取到,如页面、配置文件、Action中;而存入域对象如Request对象的数据,只能在JSP页面中获取。
-
内部结构
值栈中的两个主要区域:root和context。root区继承了一个ArrayList,context区来自OGNL的包,其实现了一个Map接口,包含了一些常见的Web对象的引用。
-
root区一般放置常见的对象,ArrayList——CompoundRoot对象;获取root数据不需要加#
-
context放置对象的引用,Map——OgnlContext对象;获取context数据需要加#
- root引用
- request
- parameters
- session
- application
- attr:小范围的引用,使用它按序依次访问request、session、application中的属性,找到即停。
-
一般而言,操作的值栈是指的操作Root区。
-
也可以在JSP的debug中看到
<h1>OGNL的值栈内部结构debug</h1><s:debug />
-
-
值栈与ActionContext
和ServletContext上下文对象一样,ActionContext对应Action的生命周期。因Struts核心过滤器
StrutsPrepareAndExecuteFilter
在init
方法中会执行加载配置文件(xml中继承自struts-default的,又会执行许多默认的拦截器,实现参数的校验,类型转化和封装等功能)。ActionContext:请求产生------
StrutsPrepareAndExecuteFilter
------执行doFilter方法-----创建ActionContext-----再由其创建ValueStack,并封装到ActionContext中;所有可以使用ActionContext获取值栈对象。ActionContext有值栈的引用,于是能够访问Servlet中的request等域对象,就获得其API的相关数据。ActionContext中的获得的上下文对象,其实是通过当前线程来获得的:
public class ActionContext implements Serializable { static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>(); /** * Constant for the name of the action being executed. */ public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name"; /** * Constant for the {@link com.opensymphony.xwork2.util.ValueStack OGNL value stack}. */ public static final String VALUE_STACK = ValueStack.VALUE_STACK; /** * Constant for the action's session. */ public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session"; ...... /** * Returns the ActionContext specific to the current thread. * * @return the ActionContext for the current thread, is never <tt>null</tt>. */ public static ActionContext getContext() { return actionContext.get(); }
-
获得值栈
- 通过ActionContext获取
- 在Struts2中也在request中存入了一个值栈引用
//方式1. ValueStack valueStack1 = ActionContext.getContext().getValueStack(); // 方式2. ValueStack valueStack2 = (ValueStack) ServletActionContext.getRequest() .getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); // 二者属于同一Action,ValueStack当然是同一个对象 System.out.println(valueStack1 == valueStack2); return NONE;
-
操作值栈:操作数据
-
通过Action提供了Get方法的属性,获取其属性值。
- 原因:默认情况下,ValueStack会将Action对象本身也压入栈(Root区)中,所有有get方法的属性都可获取
public class DoValueStack extends ActionSupport { private User user; //方式1必须要有get属性 public User getUser() { return user; } @Override public String execute() throws Exception { //方式1.在action中提供get方法 user = new User("Helo", "1234"); return SUCCESS; } } -----------------------JSP---------------------------------- <h2>操作值栈方式一,get</h1> <s:property value="user.username"/> <s:property value="user.password"/>
-
通过ValueStack自身的API:更常用,不需要太多的Get方法,代码更可读
@Override public String execute() throws Exception { //方式2.使用ValueStack本身的API ValueStack valueStack = ActionContext.getContext().getValueStack(); //添加数据,push(对象),set(key,value) User user = new User("ooooo", "4444"); valueStack.push(user); //push的对象保存在栈顶位置!!!!!!!!!!!!!!!! valueStack.set("aaa", "123456"); return SUCCESS; } ----------------------------JSP--------------------------------- <s:property value="username"/> ooooo <s:property value="aaa"/> 123456
-
-
获取值栈数据
-
获取Root区中的数据——获取一个对象或者集合
public String execute() throws Exception { // ValueStack valueStack = ActionContext.getContext().getValueStack(); //1个对象 User user = new User("张三", "123456"); valueStack.push(user); List<User> list = new ArrayList<>(); list.add(new User("李四", "123")); list.add(new User("王五", "123")); list.add(new User("赵六", "123")); valueStack.set("list", list); return SUCCESS; } ----------------------------JSP-------------------------- <h2>1.获取Root区的数据 单个对象</h1> <s:property value="username"/> <s:property value="password"/> <h2>2.获取Root区的数据 集合</h1> <s:property value="list[0].username"/> <s:property value="list[0].password"/><br/> <s:property value="list[1].username"/> <s:property value="list[1].password"/><br /> <s:property value="list[2].username"/> <s:property value="list[2].password"/>
-
获取context区中的数据(不常用)
ServletActionContext.getRequest().setAttribute("myname", "孙七"); ServletActionContext.getRequest().getSession().setAttribute("myname", "朱八"); ServletActionContext.getServletContext().setAttribute("myname", "周九"); ------------------------------JSP--------------------------- <h2>3.获取context区的数据 </h1> <s:property value="#request.myname"/> <s:property value="#session.myname"/> <s:property value="#application.myname"/> <s:property value="#attr.myname"/> <h2>4.获取请求字符串参数 </h1> <s:property value="#parameters.id"/>
-
-
EL为何能访问值栈数据
源码:Struts中初始化时对request进行了增强包装(request.getAttribute),框架将其进行了改造。使用EL表达式时,先会访问原request等域对象中数据,没有获取到时则进入ActionContext中,进入值栈进行查找。
于是,EL表达式同样能获取到值栈数据。
3. OGNL 中的特殊字符
-
<% request.setAttribute("name", "张三"); %> <h2>OGNL中#号的使用</h2> <h3>1.获取context域中的数据</h3> <s:property value="#request.name"/> <h3>2.构造集合list</h3> <s:iterator var="i" value="{'aa','bb','bb'}"> <s:property value="i"/>------<s:property value="#i"/><br/> </s:iterator> <h3>2.构造集合map1</h3> <s:iterator value="#{'aa':'11','bb':'22','cc':'33'}"> <s:property value="key"/>------<s:property value="value"/><br/> </s:iterator> <h3>2.构造集合map2, 使用了var就必须加#号,从context区中获取</h3> <s:iterator var="entry" value="#{'aa':'11','bb':'22','cc':'33'}"> <s:property value="#entry.key"/>------<s:property value="#entry.value"/><br/> </s:iterator> <h3>3.#号在一些常用表单中使用,构建集合</h3> 性别:<input type="radio" value="1" name="sex1">男 <input type="radio" value="2" name="sex1">女 <br> <h4>3.1使用list</h4> <s:radio list="{'男','女'}" name="sex2" label="性别"></s:radio> <h4>3.2使用map</h4> <s:radio list="#{'1':'男','2':'女'}" name="sex3" label="性别"></s:radio>
-
%
常用于在HTML标签中,显示获取数据(也可以直接嵌套,但在OGNL的标签中无法嵌套);也就是强制解析OGNL语句,同样也可以强制失效OGNL
<s:textField name="name" value="%{#request.name}"/> <input type="text" name="name2" value="<s:propery value='#request.name'>"> //可嵌套 <s:property value="%{‘#request.name’}"/>
-
$
一般在配置文件xml或properties中使用,来使用OGNL获得数据。
- 属性文件:使用$来限定OGNL表达式,否则会被视为字符串,如${#session.user.username}
- xml:同上使用
案例:查询优化,将查询的数据存入值栈中,在页面使用OGNL获取。
4. Struts拦截器
-
interceptor,作用时拦截(过滤)Action;相比Filter过滤的是客户端向服务器发送的请求,而拦截器拦截的是客户端对Action的访问,粒度更细,可以拦截到Action的具体方法。拦截器可以用来做一些权限,控制之类的功能。
Struts2框架核心的功能都是依赖拦截器实现
-
Struts的执行流程
客户端向服务器发送一个Action的请求,执行核心过滤器(doFilter)方法。在这个方法中,调用executeAction()方法,在这个方法内部调用dispatcher.serviceAction();在这个方法内部创建一个Action代理,最终执行的是Action代理中的execute(),在代理中执行的execute方法中调用ActionInvocation的invoke方法。在这个方法内部递归执行一组拦截器(完成部分功能),如果没有下一个拦截器,就会执行目标Action,根据Action的返回的结果进行页面跳转。
-
自定义拦截器:编写一个类实现Interceptor接口或者继承AbstractInterceptor类;同样,在设置默写权限的时候,可以继承MethodFilterInterceptor,来完成更小粒度(基于方法)的控制和过滤。
public class InterceptorDemo3 extends AbstractInterceptor { @Override public String intercept(ActionInvocation invocation) throws Exception { System.out.println("intercepto3 EXE。。。。"); //递归执行其他的拦截器 String obj = invocation.invoke(); System.out.println("intercepto3 Done。。。。"); return obj; } }
配置拦截器:
<package name="demo1" extends="struts-default" namespace="/"> <!-- 配置拦截器 --> <interceptors> <interceptor name="interceptorDemo1" class="com.leehao.strutslearning.interceptor.InterceptorDemo1" /> <interceptor name="interceptorDemo2" class="com.leehao.strutslearning.interceptor.InterceptorDemo2" /> <interceptor name="interceptorDemo3" class="com.leehao.strutslearning.interceptor.InterceptorDemo3" /> </interceptors> <action name="actionDemo1" class="com.leehao.strutslearning.action.ActionDemo1"> <result>/demo1.jsp</result> <!-- 将拦截器引入栈,注:一旦添加了自定义的拦截器,则必须要手动选择是否执行默认的那一堆拦截器 --> <interceptor-ref name="defaultStack"></interceptor-ref> <interceptor-ref name="interceptorDemo1"></interceptor-ref> <interceptor-ref name="interceptorDemo2"></interceptor-ref> <interceptor-ref name="interceptorDemo3"></interceptor-ref> </action> </package>
方法二:
<package name="demo1" extends="struts-default" namespace="/"> <!-- 配置拦截器 --> <interceptors> <interceptor name="interceptorDemo1" class="com.leehao.strutslearning.interceptor.InterceptorDemo1" /> <interceptor name="interceptorDemo2" class="com.leehao.strutslearning.interceptor.InterceptorDemo2" /> <interceptor name="interceptorDemo3" class="com.leehao.strutslearning.interceptor.InterceptorDemo3" /> <interceptor-stack name="myStack"> <interceptor-ref name="defaultStack"></interceptor-ref> <interceptor-ref name="interceptorDemo1"></interceptor-ref> <interceptor-ref name="interceptorDemo2"></interceptor-ref> <interceptor-ref name="interceptorDemo3"></interceptor-ref> </interceptor-stack> </interceptors> <action name="actionDemo1" class="com.leehao.strutslearning.action.ActionDemo1"> <result>/demo1.jsp</result> <interceptor-ref name="myStack"></interceptor-ref> </action> </package>
-
-
注意点
- 使用拦截器是在各个action中配置的,建议使用method粒度的拦截器;
- 拦截器主要针对不同的action,页面如JSP的访问使用过滤器来进行控制
- 拦截器可以分别在action中配置,也可以设置为拦截器的栈,按名称引用;
- 一定记得同时配置默认的拦截器栈——defaultStack
5. Struts 的标签
struts有自己的标签,主要的好处是方便数据的记录和回显。分为通用标签(语句)和UI标签(表单等)。
-
通用标签——表达式
最常用:
- 判断标签if
- 迭代标签iterator
- property
- debug
- date
-
UI标签——常用作数据回显,且有自己的样式,较为灵活。可以在struts.properties中设置属性,也可以对单个的标签进行theme=simple的设置,取消默认为xhtml的样式。