ssh框架-Struts2(二)

时间:2024-01-02 14:57:44

上篇文章我们了解了怎么配置struts.xml文件,以及前端控制器配置怎么配置,,Action进阶,Result结果配置,Struts2中的Servlet的API的访问,以及怎么获得请求参数.今天我们在深入讲解一下OGNL表达式,OGNL中的符号,和常用的拦截器,标签库

一,OGNL表达式

1.概述

1.1什么是OGNL

​ OGNL是Object-Graph Navigation Language的缩写,俗称对象图导航语言. 它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。

​ Eg: hibernate 查询方式 : 对象导航查询.

​ 其实就是查询出来了一个对象之后,通过对象里面的getXXX() 来获取关联的对象。

​ 它是一个开源项目,并不是struts发明出来的,只是struts里面默认的表达式是使用OGNL表达式.

1.2OGNL作用
  • 获取对象的成员(支持对象方法的调用,支持对象对象属性访问,支持静态方法的调用,支持集合对象操作)

  • 进行运算

2.OGNL要素

​ OGNL 有三个核心元素 ,我们只有理解这三个核心元素,才能去更好的学习OGNL表达式

2.1表达式 (Expression)

​ 表达式用于表示我们到底想执行什么操作 。 比如:想获取成员 ,直接用对象.成员名字就可以得到该成员的值

2.2 根元素 (Root)

​ OGNL 表述的是对象导航语言、那么必须的指明根元素是什么。 好比我们要在树上找果子、必须指定到底是那一棵树是一样的道理。

2.3上下文 (Context)

​ 上下文其实就是我们的那个root寄存的位置,它在内存当中其实就是一个Map .OGNL 有一个类专门用来表示上下文环境 叫做OGNLContext , 它其实就是一个Map.

  • EG:Map里面存放了很多的User对象, 那么我们必须指明是在哪一个根上面找这些user对象。

  • 3.OGNL入门

    ​ 大家在使用OGNL的时候,要在脑海中有一个想像,就是内存中已经有了一个对象,我们需要去操作这个对象,获取里面的成员,或者去调用它的某个方法。那么表达式应该怎么写。这和我们的EL 表达式有异曲同工之妙,都是用于获取对象、然后再关联到具体的某一个成员.当然我们现在给大家在代码里面演示OGNL的一些用法,一般我们使用OGNL 最多的地方还是在jsp页面上。

    ​ 使用OGNL先导入两个Jar包ognl-xxx.jar,javassist-xxx.jar

    3.1表达式与根对象
    • 访问根对象属性

      • /**
             * OGNL表达式和根对象: 访问属性
             * @throws OgnlException
             */
            @Test
            public void  fun01() throws OgnlException{
                //根对象
                User root = new User("张三", 18);
                //表达式(想得到什么)
                String expression = "username";
                //通过Ognl方式获得根对象中的表达式结果
                Object value = Ognl.getValue(expression , root);
                System.out.println("value="+value);
        
            }
    • 访问根对象方法
      • /**
                 * OGNL表达式和根对象: 访问方法
             * @throws OgnlException
             */
            @Test
            public void  fun02() throws OgnlException{
                //根对象
                User root = new User("张三", 18);
                //表达式(想得到什么)
                String expression = "getUsername()";
                //通过Ognl方式获得根对象中的表达式结果
                Object value = Ognl.getValue(expression , root);
                System.out.println("value="+value);
        
            }        

  

  3.2表达式和上下文
  • 结合上下文访问根对象属性
      •   
            @Test
            public void  fun03() throws OgnlException{
                User user1 = new User("张三", 18);
                User user2 = new User("李四", 19);
                //上下文
                Map<String, User> context  = new HashMap<String, User>();
                context.put("user1", user1);
                context.put("user2", user2);
        
                //根对象(指定user1为根对象)
                Object root = user1;
        
                //表达式(想得到什么)
                String expression = "username";
                //通过Ognl方式获得根对象中的表达式结果(获得根对象的username的值,也就是user1的)
                Object value = Ognl.getValue(expression, context, root);
                System.out.println("value="+value);
        
                //获得非根对象的值(user2的)
                expression = "#user2.username";
                value = Ognl.getValue(expression, context, root);
                System.out.println("value="+value);
        
            }
            

        

  • 翻译上面的getValue方法的含义是: 在上下文 context里面找到一个 根user1 然后取它的name值

    ​ 如果想要获取user2的数据 因为user2不是根对象,所以要想取它的值,需要写成 #key.name 。 String expression = "#user2.name"

二,ValueStack值栈

  1.概述

   1.1什么是值栈

​     value stack : 翻译过来是值栈的意思。

      ​ 回想以前我们在学习servlet的时候,我们要想 servlet操作完成后,生成了一个集合或者对象数据, 在页面上显示,我们通常的做法是。把这个对象或者集合存储在作用域里面,然后到页面上再取出来。

     strtus 框架是对我们的servlet进行了包装 ,它内部自己也有一套存值和取值的机制 ,这一套机制就是现在说的 值栈。

ssh框架-Struts2(二)

2.值栈创建的时机

2.1 Servlet和Action的区别
  • Servlet: Servlet只会创建一次实例,以后再过来请求,不会创建实例

  • Action: Action是多例,来一次请求就创建一次实例。 创建一次Action的实例,就创建一次ActionContext实例,并且就创建出来一个值栈的实例。

2.2什么时候创建值栈
  • 请求到来的时候才会创建值栈, 当来了请求,会执行前端控制器的doFilter方法,在doFilter方法里面,有如下代码

    prepare.createActionContext(request, response);
  • 在createActionContext()这个方法内部

    //从ThreadLocal里面获取ActionContext实例,一开始是没有的,所以该对象是 null
     ActionContext oldContext = ActionContext.getContext();
        if (oldContext != null) {
          // detected existing context, so we are probably in a forward
          ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
        } else {
    ​
          //创建值栈对象  是OgnlValueStack 对象
         ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
    ​
        //给值栈里面的上下文区域存东西 。 存request \ response \session \application...
        stack.getContext().putAll(dispatcher.createContextMap(request, response, null));
        //创建ActionContext的对象。 然后跟值栈关联上
        ctx = new ActionContext(stack.getContext());
    }

3.值栈的内部结构

​ 我们查看值栈的内部结构,其实是想研究值栈存值的时候,是存到了什么地方去的。

  • 值栈其实就是一个类 OgnlValueStack ,我们如果往值栈存值, 其实是存到了这个类中的两个集合去。

    CompoundRoot root;  //这其实是一个集合  list
    transient Map<String, Object> context;  //这是OGNL上下文  OGNLContext
    ​
    //底下还有一行代码
     context.setRoot(root);
    //也就是设置了OGNL上下文的根其实就是一个list集合 。  说的具体一点。 就是那个CompoundRoot对象。

我们使用OGNL表达式取值的时候,都是去上下文里面取值。

ssh框架-Struts2(二)

​ root 区,根对象。具体类型为CompoundRoot。CompoundRoot实现了List接口,其实就是一个list集合。其实我们存值的话都是存在root区域.

​ context 区:上下文对象。具体类型为OgnlContext。OgnlContext内部操控的是一个Map集合,其实context区可以理解为一个Map集合。

4.获得ValueStack

4.1通过ActionContext对象的实例获得

​ 由于前面创建好值栈后,让它和ActionContext关联上了,所以我们只要通过ActionContext去获取它即可。

  • 获得值栈代码

ValueStack valueStack = ActionContext.getContext().getValueStack();

4.2通过request域获得ValueStack

​ 在执行完我们的Action方法前,struts会把值栈往request对象里面存起来,所以我们使用request对象即可获取到.

在前端过滤器的doFilter()方法里面有 execute.executeAction(request, response, mapping);这行代码,执行Action.

具体源码参见: Dispatcher类中的serviceAction方法 , 位于540行:request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());所以后续我们也只要使用request对象然后结合对应的STRUTS_VALUESTACK_KEY 这个key即可获取到值栈

  • 获得值栈代码

     ValueStack valueStack = (ValueStack) ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); 

5.值栈存取值

5.1push方式

​ 直接放置在栈顶,没有什么key与之对应。所以取值的话,直接写属性名即可。 但是如果push 了多个,并且还想获取的不是栈顶的值,是栈顶下来的某一个位置的值,那么可以采用[0] \ [1] 这种做法

  • 存值Java代码

User user = new User("admin" ,"10086");
ValueStack stack = ActionContext.getContext().getValueStack();
//执行的是压栈的动作。 放置的东西永远会在栈顶。
stack.push(user );
​
//第二次的时候,user02已经位于栈顶 ,之前的那个user就要往下压一格
User user02 = new User("zhangsan" ,"1000000");
stack.push(user02 );
  • 取值(演示[0]和[1]的区别)

    取push放的值<br>
    <s:property value="username"/> , <s:property value="password"/><br><br>取push的值 就取第二个,不取栈顶<br>
    <s:property value="[1].top.username"/>  , <s:property value="[1].password"/><br>
5.2set方式

​ set 和 push 的区别在于, push 是直接把数据压倒栈顶 , 但是set方式是用一个map集合来包装数据,然后才把map压倒栈顶。 所以取值手法也有点不一样。

  • 存值Java代码

User user = new User("admin" ,"10086"); ValueStack stack = ActionContext.getContext().getValueStack(); stack.set("user01", user);

  • 取值

    取set放的值<br>
    <s:property value="user01.username"/> , <s:property value="user01.password"/><br>
5.3属性驱动方式

​ 和之前的获得请求参数属性驱动类似.

  • 存值Java代码

    public class ActionDemo01 extends ActionSupport {
        private String str;
        public String fun01(){
            str = "hello";
            return "success";
        }
        //提供get方法
        public String getStr() {
            return str;
        }
    ​
    }
  • 取值

     取属性封装的值<br> <s:property value="str"/> 
5.4模型驱动方式
  • 存值

    public class ActionDemo01 extends ActionSupport implements ModelDriven<User> {
        private User user;
        public String fun01(){
            return "success";
        }
        @Override
        public User getModel() {
            if(user == null){
                user = new User("李四", "66666666");
            }
            return user;
        }
    ​
    }
  • 取值

    
    
    <s:property value="model.username"/> , <s:property value="model.password"/><br>
    或者:
    <s:property value="username"/> , <s:property value="password"/><br>

    由于使用模型驱动封装,存值的时候,也还是存到action的范围里面去.

5.5使用EL表达式取值

​ EL表达式也可以取到值栈的值,本来EL表达式是用于取作用域的值,但是值栈和作用域是两个东西。 为什么EL 表达式也能去值栈的值呢?原因是 : struts对EL 表达式取值的代码进行了扩展,如果从作用域取不到值就会去值栈找。

​ reuqest.setAttribute("user" , user); ${user.name} ------> pageContext.findAttrbute(); --> 先从page范围找, 没有,就找request, 还没有就找session。request类型被包装成了 StrutsRequestWrapper 在里面的getAttribute 做了判定,如果从作用域中去不到值,就去值栈取值

6.OGNL中的符号

6.1#号的作用
  • 获取Context中的数据,非根对象里面的数据

    
    
    <s:property value="#request.name"/>
    <s:property value="#session.name"/>
  • 构建一个Map集合

    //var变量会存一份到root也会存一份到上下文
    <s:iterator var="entry" value="#{ 'aa':'11','bb':'22','cc':'33' }">
    <s:property value="key"/>--<s:property value="value"/><br/>
    <s:property value="#entry.key"/>--<s:property value="#entry.value"/><br/>
    </s:iterator>
6.2%号的作用
  • 强制解析OGNL表达式

    
    
    <s:textfield value="%{#request.name}" name="name"/>
  • 强制不解析OGNL表达式

<s:property value="%{'#request.name'}"/>

三,拦截器

1.概述

1.1什么是拦截器

​ 在struts2中,拦截器(Interceptor)是用来动态拦截Action执行的对象。

​ 拦截器有点类似以前Servlet阶段的Filter(过滤器) , 能够在请求到达Action之前进行拦截操作, 可以在里面进行判断校验。 典型的例子: 登录拦截.

注:过滤器可以过滤servlet, 拦截器可以拦截Action , 过滤器除了可以过滤servlet之外,还可以过滤网络资源(html / jsp ) , 拦截器只能拦截Action

ssh框架-Struts2(二)

ssh框架-Struts2(二)
1.2 拦截器执行流程

​ 客户端请求Action,执行核心过滤器,在核心过滤器内部创建了Action的代理类,调用代理类的execute方法,在execute方法内部执行ActionInvocation的invoke方法。在invoke方法内部递归调用拦截器的拦截的方法。如果没有下一个拦截器执行目标Action,Action执行结束后根据Result进行页面跳转,执行拦截器的后面相应的代码,最后由response对象生成响应。

  • 流程图

ssh框架-Struts2(二)

ssh框架-Struts2(二)

  • 时序图

ssh框架-Struts2(二)

ssh框架-Struts2(二)

2.自定义拦截器

2.1方式一:实现Interceptor接口

​ 最原始的方式,也是最基本的方式. 要实现三个方法,其中init 和 destroy ,平常我们都不太用。

  • 创建一个类实现Interceptor接口

    public class Interceptor01 implements Interceptor {
        @Override
        public String intercept(ActionInvocation invocation) throws Exception {
            System.out.println("Interceptor01 执行了...");
            //放行
            invocation.invoke();
            return null;
        }
        @Override
        public void destroy() {
        }
        @Override
        public void init() {
        }
    }
  • 在struts.xml里面声明拦截器

    
    
    <package name="test" extends="struts-default" namespace="/">
        <!-- 声明拦截器 -->
        <interceptors>
            <interceptor name="interceptor01" class="demo.web.interceptor.Interceptor01">                </interceptor>
        </interceptors>
    </package>
  • 在action里面使用拦截器

    
    
    <action name="demo_*" class="demo.web.action.ActionDemo"  method="{1}">
            <interceptor-ref name="interceptor01"></interceptor-ref>
    </action>
2.2方式二:继承AbstractInterceptor

​ 是Interceptor的一个子类, 只需要重写intercept()方法.

  • 创建一个类继承AbstractInterceptor

    
    
    public class Interceptor02 extends AbstractInterceptor {
        @Override
        public String intercept(ActionInvocation invocation) throws Exception {
            System.out.println("Interceptor01 执行了...");
            //放行
            invocation.invoke();
            return null;
        }
    }
  • 在struts.xml里面声明拦截器

    
    
    <package name="test" extends="struts-default" namespace="/">
        <!-- 声明拦截器 -->
        <interceptors>
            <interceptor name="interceptor02" class="demo.web.interceptor.Interceptor02">                </interceptor>
        </interceptors>
    </package>
  • 在action里面使用拦截器

    
    
    <action name="demo_*" class="demo.web.action.ActionDemo"  method="{1}">
        <interceptor-ref name="interceptor02"></interceptor-ref>
    </action>
2.3方式三:继承MethodFilterInterceptor

​ 是AbstractInterceptor的一个子类.可以精确的控制拦截或者不拦截哪一个方法

  • 创建一个类继承MethodFilterInterceptor

    public class Interceptor03 extends MethodFilterInterceptor {
    ​
        @Override
        protected String doIntercept(ActionInvocation invocation) throws Exception {
            System.out.println("Interceptor03 执行了...");
            //放行
            invocation.invoke();
            return null;
        }
    
    }
  • 在struts.xml里面声明拦截器

    
    
    <package name="test" extends="struts-default" namespace="/">
        <!-- 声明拦截器 -->
        <interceptors>
            <interceptor name="interceptor03" class="demo.web.interceptor.Interceptor03">                </interceptor>
        </interceptors>
    </package>
  • 在action里面使用拦截器

    
    
    <action name="demo_*" class="demo.web.action.ActionDemo"  method="{1}">
            <interceptor-ref name="interceptor03">
                <!--不拦截fun02()方法 -->
                <param name="excludeMethods">fun02</param>
            </interceptor-ref>
    </action>

3.自定义拦截器相关的操作

3.1拦截器不放行

​ 拦截器的处理结果,莫过于两种:

​ 放行: 如果后面还有拦截器就执行下一个拦截器,如果后面没有了拦截器,就执行action

​ 拦截: 但是注意,拦截后也需要返回到一个具体的页面。 所以需要配置result标签

  • 放行, 调用invoke()方法即可

  • 拦截,跳转其它页面

3.2自定义拦截器的问题

​ 一旦自定义了自己的拦截器,那么struts默认的哪一套 拦截器就不会走。 有点像有参构造和无参构造的关系。

  • struts默认的拦截器都有哪些呢?在struts-default.xml里面有一个package 名字也叫作 struts-default 在这个包中定义了一个拦截器栈,名字叫做 defaultStack.

      <interceptor-stack name="defaultStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="alias"/>
                <interceptor-ref name="servletConfig"/>
                <interceptor-ref name="i18n"/>
                <interceptor-ref name="prepare"/>
                <interceptor-ref name="chain"/>
                <interceptor-ref name="scopedModelDriven"/>
                <interceptor-ref name="modelDriven"/>
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="checkbox"/>
                <interceptor-ref name="datetime"/>
                <interceptor-ref name="multiselect"/>
                <interceptor-ref name="staticParams"/>
                <interceptor-ref name="actionMappingParams"/>
                <interceptor-ref name="params"/>
                <interceptor-ref name="conversionError"/>
                <interceptor-ref name="validation">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
                <interceptor-ref name="debugging"/>
                <interceptor-ref name="deprecation"/>
        </interceptor-stack>
            ....
        <default-interceptor-ref name="defaultStack"/>
  • 如果我们给自己的action使用了自己定义的拦截器,那么上面默认的这些拦截器都不会生效了,失去了这些功能,如果还想使用默认的这些拦截器。那么可以在action里面再引用它即可。

     <action name="actionDemo_*" class="demo.action.ActionDemo" method="{1}">
           <!-- 执行这个action,就必然要走名字叫做myInterceptor02的拦截器 -->
    ​
           <!-- 使用自定义的拦截器 -->
           <interceptor-ref name="myInterceptor"></interceptor-ref>
    ​
           <!-- 使用默认的拦截器 -->
           <interceptor-ref name="defaultStack"></interceptor-ref>
    ​
           <result name="login">/index.jsp</result>
     </action>

四,标签库

1.概述

1.1 Struts2标签库介绍

​ Struts2标签库是一个比较完善,且功能强大的标签库,它将所有标签都统一到一个标签库中,从而简化了标签的使用,它还提供主题和模板的支持,极大地简化了视图页面代码的编写。

1.2Struts2标签库分类

ssh框架-Struts2(二)ssh框架-Struts2(二)

1.3使用步骤
  • 如果使用Struts2的标签库,必须导入库到页面

    
    
    <%@ taglib uri="/struts-tags" prefix="s" %>
  • 根据语法书写标签库...

2.流程控制标签库

  • if标签elseif标签...类似JSP里面的c:if

    取值:
    <s:property value="#request.a"/><br/>
    ​
    <s:if test="#request.a > 10">
        a 大于10
    </s:if>
    <s:else>
        a 小于或者等于10
    </s:else>
  • iterator标签类似JSP里面的c:foreach

    <s:iterator value="list" var="u">
        <s:property value="username"/>
        <s:property value="password"/>
    </s:iterator>

3.UI标签

3.1表单标签

ssh框架-Struts2(二)ssh框架-Struts2(二)

  • 和HTML标签对比

    
    
    <h1>一,HTML的表单标签</h1>
    <form action="${pageContext.request.contextPath }/demo03_fun03" method="post">
        用户名:<input type="text" name="username"/><br/>
        密码: <input type="password" name="password"/><br/>
        性别:<input type="radio" name="sex" value="1"/>男<input type="radio" name="sex" value="0"/>女<br/>
        爱好:
        <input type="checkbox" name="hobby" value="lq"/>篮球
        <input type="checkbox" name="hobby" value="zq"/>足球
        <input type="checkbox" name="hobby" value="ppq"/>乒乓球
        <input type="checkbox" name="hobby" value="qdm"/>敲代码<br/>
        籍贯:<select name="address">
                <option value="-1">-请选择-</option>
                <option value="0">北京</option>
                <option value="1">上海</option>
                <option value="2">深圳</option>
            </select><br/>
        自我介绍:<textarea rows="2" cols="8" name="introduce">哈哈哈</textarea><br/>
        <input type="submit" />
    </form>
    ​
    <h1>二,Struts2的表单标签</h1>
    <!--默认请求方式就是post,action不用写项目路径  -->
    <s:form action="/demo03_fun03">
        <s:textfield name="username" label="用户名" />
        <s:password name="password" label="密码"/>
        <%-- <s:radio list="{'男','女'}" name="sex" label="性别"/> --%>
        <s:radio list="#{'1':'男','0':'女' }" name="sex" label="性别" value="1"></s:radio>
        <s:checkboxlist list="{'篮球','足球','乒乓球','敲代码'}" label="爱好" name="hobby"/>
        <s:select list="#{'0':'北京','1':'上海','2':'深圳' }" label="籍贯" name="address" headerKey="-1" headerValue="请选择" value="1"></s:select>
        <s:textarea cols="8" rows="2" name="introduce" label="籍贯" value="哈哈"></s:textarea>
        <s:submit value="注册" align="left"/>
    </s:form>

Struct2标签特点:

​ 自动加了样式, 标签都套了一层表格.

​ 有自动回显数据的功能.

3.2Struts2模版和标题

​ 主题就是一种风格化标签,能够让所有UI标签能够产生同样的视觉效果而归集到一起的一组模板,即风格相近的模板被打包为一个主题. struts2提供了四种主题simple、xhtml、css_xhtml、ajax。

​ simple:把UI标签翻译成最简单的HTML对应元素,而且会忽视行标属性

​ xhtml:默认的主题。这个主题的模板通过使用一个布局表格提供了一种自动化的排版机制

​ css_xhtml:这个主题里的模板与xhtml主题里的模板很相似,但他们将使用css来进行布局和排版

​ ajax:这个主题里的模板以xhtml主题里的模板为基础,但增加了一些ajax功能。

  • 通过配置struts.xml文件中的常量属性struts.ui.theme修改主题风格. 全局的

    <constant name="struts.ui.theme" value="simple"></constant>
  • 也可以通过UI标签的theme属性进行修改

    <s:form action="emp-save" theme="simple">