Struts2之OGNL与ValueStack
阅读原文时间:2023年07月09日阅读:2

时间:2017-1-12 12:02

——OGNL

1、OGNL表达式是什么
    OGNL的全称是Object-Graph Navigation Language的缩写,中文名是对象图导航语言,它是一种功能强大的表达式语言。
    比EL表达式功能强大。

    Struts2将OGNL表达式语言集成到Struts2框架中,作为它的默认表达式语言。

2、OGNL表达式的功能
    1)支持对象方法调用,如:xxx.doSomeMethod()
    2)支持类静态的方法调用和值访问。
    3)访问OGNL上下文(OGNL Context)和ActionContext:(重点,操作ValueStack)
    4)支持赋值操作和表达式串联。
    5)操作集合对象。

3、OGNL中“根”与“非根”的区别

    root:只能有一个根,用于存储Action相关数据
    contextMap:用于存储Web相关数据

4、示例代码

在Java代码中使用ognl表达式。

根中数据不需要使用#获取。
非根中数据需要使用#获取。
OGNL叫做对象图导航语言。

Person.java

public class Person {

    private String name;

    private Dog dog;

    public Dog getDog() {

        return dog;

    }

    public void setDog(Dog dog) {

        this.dog = dog;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

}

----------------------------------------------------------------------------------------------------------------------------

Dog.java

public class Dog {

    private String name;

    private Person p;

    public Person getP() {

        return p;

    }

    public void setP(Person p) {

        this.p = p;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

}

----------------------------------------------------------------------------------------------------------------------------

OgnlDemo1.java

import ognl.Ognl;

import ognl.OgnlContext;

import ognl.OgnlException;

public class OgnlDemo1 {

    public static void main(String[] args) throws OgnlException{

        // ognl可以通过对象调用方法

        System.out.println("aaa".length());

        /*

         * 使用ognl来实现上面操作

         * 1、创建一个ognl上下文对象

         * 2、调用getValue()方法

         */

        OgnlContext context = new OgnlContext();

        Object o1 = Ognl.getValue("'aaa'.length()", context.getRoot());

        System.out.println(o1);
 

        // 调用Math的静态方法和静态成员

        System.out.println(Math.max(10, 20));

        System.out.println(Math.PI);

        /*

         * 在ognl表达式中调用静态方法

         */

        Object o2 = Ognl.getValue("@java.lang.Math@max(10, 20)", context.getRoot());

        System.out.println(o2);

        Object o3 = Ognl.getValue("@java.lang.Math@PI", context.getRoot());

        System.out.println(o3);

    }

}

----------------------------------------------------------------------------------------------------------------------------

OgnlDemo2.java

import ognl.Ognl;

import ognl.OgnlContext;

import ognl.OgnlException;

public class OgnlDemo2 {

    public static void main(String[] args) throws OgnlException {

        /*

         * 创建一个ognl上下文 OgnlContext是一个Map集合 

         * public class ognl.OnglContext implements java.util.Map

         */

        OgnlContext context = new OgnlContext();

        Person p = new Person();

        p.setName("张三");

        Dog dog = new Dog();

        dog.setName("王五");

        p.setDog(dog);

        // 设置根

        context.setRoot(p);

        Dog d = new Dog();

        d.setName("李四");

        Person p2 = new Person();

        p2.setName("赵六");

        d.setP(p2);

        // 设置属性(非根)

        context.put("dog", d);

        // 使用ognl来获取根中数据

        // 获取根中数据,不需要加#

        Object o1 = Ognl.getValue("name", context, context.getRoot());

        System.out.println(o1);

        // 使用ognl来获取非根中的数据

        // 获取非根中数据,需要使用#

        Object o2 = Ognl.getValue("#dog.name", context, context.getRoot());

        System.out.println(o2);

        // 获取p中的dog对象

        // p表示根,所以可以省略p

        Object o3 = Ognl.getValue("dog.name", context, context.getRoot());

        System.out.println(o3);

        // 获取dog中的person

        Object o4 = Ognl.getValue("#dog.p.name", context, context.getRoot());

        System.out.println(o4);

    }

}

——在Struts2中使用ognl表达式

需要结合Struts2的标签使用:
    *   value:
        书写ognl表达式
    *   default:
        默认值
    *   escapeHtml
        是否解析HTML
    *   escapeJavaScript
        是否解析JS
    *   escapeXml
        是否解析XML

注意:在Struts的JSP页面中访问静态成员时,必须设置一个常量值:
    struts.ognl.allowStaticMethodAccess=false

示例代码:

 

使用ognl通过对象来调用方法

 

 


 

 

——ValueStack

1、ValueStack是什么
    从技术角度来讲,ValueStack是一个接口(com.opensymphony.xwork2.util.ValueStack)。
    从实用角度来讲,ValueStack是一个容器,用于将数据携带到action数据页面,然后在页面通过ognl表达式来获取。

    ValueStack是Struts2提供的一个接口,实现类是OgnlValueStack。

    OGNL表达式是从ValueStack中获取数据的。

    每个Action实例都有一个ValueStack对象(一个请求对应一个ValueStack对象)
    ValueStack中保存当前Action对象和其他常用Web对象,例如request, session, application, parameters(值栈中是有Action引用的)
    Struts2框架把ValueStack对象保存在key为“struts.valueStack”的request域中(request中值栈对象是request的一个属性)

    一个request一个Action,一个Action一个ValueStack,request - Action - ValueStack是一一对应的。
    ValueStack生命周期就是一个request的生命周期。

    流程(源码分析):
        从第一个请求开始,被StrutsPrepareAndExecuteFilter拦截后执行doFilter()中的execute.executeAction(request, response, mapping);方法,然后一直调用,进入到Dispatcher中的serviceAction()方法,在该方法中通过request获取ValueStack对象:ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);如果是第一次获取,则ValueStack对象为null,然后创建一个新的ValueStack,因为每次请求都是一个新的请求,所以每次请求都会创建一个新的ValueStack。当请求结束后,ValueStack就被释放了,所以ValueStack的生命周期等同于request的生命周期。

        然后通过Dispatcher类中的serviceAction()方法中的:request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());来获取一个新的ValueStack对象,并保存到request域中,保存的key为:ServletActionContext.STRUTS_VALUESTACK_KEY,在ServletActionContext中可以查看,该常量的值为:struts.valueStack。

2、ValueStack的内部结构
    查看ValueStack接口,可以看到两个方法:
        *   public abstract Map getContext();
        *   public abstract CompoundRoot getRoot();
            >   public class CompoundRoot extends ArrayList
            >   CompoundRoot类继承了ArrayList,提供了peek() pop() push()方法,相当于栈。

    ValueStack接口中声明了root属性(CompoundRoot)、context属性(OgnlContext):
        *   CompoundRoot就是ArrayList。
        *   OgnlContext就是Map

    值栈由两部分组成:
        ObjcetStack:Struts2把Action和相关对象压入ObjectStack中,用一个List保存
            保存Action相关信息。
 
        ContextMap:Struts2把各种各样的映射关系(一些Map类型的对象)压入ContextMap中。
            比较常见的映射关系就是常见Web对象。

    Struts2会把下面这些映射压入ContextMap中:
        *   parameters:该Map中包含当前请求的请求参数
        *   request:该Map中包含当前request对象的所有属性
        *   session:该Map中包含当前session对象的所有属性
        *   application:该Map中包含当前application对象的所有属性。
        *   attr:该Map按如下顺序来检索某个属性:request、session、application,相当于全域查找
        *   对象引用
    通过断点可以发现:
        

    其中root在ContextMap中也有一个映射关系:
        ValueStack中包含ContextMap和Root,而在ContextMap中又持有了Root的引用。
    可以在断点调试中发现:
        

    OGNL表达式访问root(List)中数据时,不需要使用#访问。

    访问(Map)request、session、application、attr、parameters、对象引用时,必须写#。

    操作ValueStack时,默认是指操作root元素。

    其实ContextMap就是一个OgnlContext,可以查看ValutStack接口的实现类:OgnlValueStack
        protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot, boolean allowStaticMethodAccess) {

        this.root = compoundRoot;

        this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess);

        this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter), securityMemberAccess);

        context.put(VALUE_STACK, this);

        Ognl.setClassResolver(context, accessor);

        ((OgnlContext) context).setTraceEvaluations(false);

        ((OgnlContext) context).setKeepLastEvaluation(false);

    }

结论:
        ValueStack有两个部分,一个是List,一个是Map。
        在Struts2中List就是Root,Map就是OgnlContext。
        在Struts2中,默认情况下(不加#)从ValueStack中的Root获取数据。

3、ValueStack对象的创建,ValueStack和ActionContext是什么关系?
    当请求发出时,会被doFilter()拦截然后调用Dispatcher类中的serviceAction()方法,方法中可以创建ValueStack对象:
        // 刚开始会到request域中获取

        ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
        // 如果获取不到ValueStack对象

        boolean nullStack = stack == null;

        if (nullStack) {
            // 会到ActionContext中获取 

            ActionContext ctx = ActionContext.getContext();

            if (ctx != null) {

                stack = ctx.getValueStack();

            }

        }

    ValueStack和ActionContext的关系:
        ActionContext中持有了ValueStack的引用。

4、如何获取ValueStack对象
    有两种方式可以获取ValueStack对象:
        1)通过request获取
        2)通过ActionContext获取

    public class OgnlDemo1Action extends ActionSupport {

        @Override

        public String execute() throws Exception {

            /*

             * 获取ValueStack

             */

            // 1、通过request获取

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

            System.out.println(stack1);

            // 2、通过ActionContext获取(推荐)

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

            System.out.println(stack2);

            return null;

        }

}

5、向ValueStack中保存数据(主要针对root)
    在ValueStack接口中有两个方法:
    有两种方式:
        *   push(Object obj)
        *   set(String key, Object obj)

    1)push(Object obj)
        在ValueStack接口的实现类OgnlValueStack中,重写了此方法:

            public void push(Object obj) {

                root.push(obj);

            }
        底层调用的是:root.add(0, obj);
        所以push方法会将对象压入栈顶。
        如果当前位置存在元素,那么当前元素会先后移,然后再在栈顶压入元素。

2)set(String key, Object obj)
        在ValueStack接口的实现类OgnlValueStack中,重写了此方法:
            public void set(String key, Object o) {

                Map setMap = retrieveSetMap();

                setMap.put(key, o);

            }

            private Map retrieveSetMap() {

                Map setMap;

                Object topObj = peek();

                if (shouldUseOldMap(topObj)) {

                    setMap = (Map) topObj;

                } else {

                    setMap = new HashMap();
                    // 将数据封装到一个新的HashMap中 

                    setMap.put(MAP_IDENTIFIER_KEY, "");
                    // 将Map集合压入栈顶

                    push(setMap);

                }

                return setMap;

            }
        底层是将数据封装到HashMap中,再将这个HashMap压入栈顶,保存到List集合中。 

示例代码:

        public String execute() throws Exception {

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

            stack.push("1");

            stack.push("2");

            stack.push("3");

            stack.set("username", "张三"); 

            List list = stack.getRoot();

            System.out.println(list);

            return null;

        }

输出结果:
        [3, 2, 1, com.wyc.action.OgnlDemo2Action@320859ec, com.opensymphony.xwork2.DefaultTextProvider@5c1958e7]

6、在JSP页面中使用标签查看ValueStack中的数据

    

        

使用debug标签查看ValueStack数据

        

        

    

7、在JSP页面中获取ValueStack数据
    获取Root中数据不需要#
    获取ContextMap中数据需要#

获取Root中的数据:

    1)如果栈顶是一个Map集合,那么获取时可以直接通过Map集合的key来获取value。
        

        如果只写属性名,那么即使栈顶不是Map集合,也会从栈顶开始依次往下查找。

    2)如果栈顶元素不是Map,不能通过key来获取,可以使用下标来获取元素:
       
            只查找0位置上的数据。

    JSP页面示例代码:

        1、获取栈顶的Map

        

        

        


        2、获取栈顶非Map集合数据

        

        

        

        


        

        

        


        

        

        


        

        

        


获取OgnlContext中的数据:

    1)request数据
        request.setAttribute()
    2)session数据
        session.setAttribute()
    3)application数据
        application.setAttribute()
    4)attr
        依次从request, session, application域中查找。
        相当于pageContext的全域查找。
    5)parameters数据
        获取请求参数。

    JSP页面代码:
        3、获取OgnlContext中的数据

        <%

            request.setAttribute("rname", "rvalue");

            session.setAttribute("sname", "svalue");

            application.setAttribute("aname", "avalue");

        %>

        

        

        

        

        

        

        

        

        

        

        

8、ValueStack有什么作用
    使用ValueStack最大的作用就是将Action相关的数据以及Web相关的对象,传递到页面上。
    简单来讲,在Struts2中通过ValueStack将Action中的数据携带到页面上进行展示。

    1)Action向JSP携带的数据都是什么类型的数据?

    *   普通文本(字符串)

            >   fieldError:校验数据错误信息提示(常用于表单校验),this.addFieldError("msg", "字段错误信息")
            >   actionError:关于逻辑操作时的错误信息(普通错误信息,例如登录失败),this.addActionError("Action全局错误信息")
            >   message:通用信息,this.addActionMessage("Action普通消息信息")

            在JSP中使用Struts2标签显示错误信息:
                *  
                *  
                *  

    *   复杂数据

            可以使用ValueStack存储:

            Action中存储数据:

                public String execute() throws Exception {

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

                    List users = new ArrayList();

                    users.add(new User("zhangsan", "111", 20, "男"));

                    users.add(new User("lisi", "222", 30, "女"));

                    users.add(new User("wangwu", "333", 40, "男"));

                    // stack.push(users);

                    stack.set("users", users);

                    return SUCCESS;

                }

JSP中获取数据:
               

                    

使用ognl表达式来获取ValueStack中复杂数据

                    1、使用push()存储时获取数据(保存到OgnlContext中,是一个Map集合)

                    

                    


                    

                        username:

                        password:

                        sex:

                        age:


                    

                    

                    

                    

                        username:

                        password:

                        sex:

                        age:


                    

                    


                    2、使用set存储数据(保存到List集合中)

                    

                    


                    

                        username:

                        password:

                        sex:

                        age:


                    

                

9、关于默认压入到ValueStack中的数据分析
    当前的Action会被默认压入ValueStack

    1)属性驱动
        每次请求访问Action对象,Action对象都会被压入ValueStack,在DefaultActioninvocation的init()方法中:
            stack.push(action);

        DefaultActioninvocation源码:

            public void init(ActionProxy proxy) {

                this.proxy = proxy;

                Map contextMap = createContextMap();

                // Setting this so that other classes, like object factories, can use the ActionProxy and other

                // contextual information to operate

                ActionContext actionContext = ActionContext.getContext();

                if (actionContext != null) {

                    actionContext.setActionInvocation(this);

                }

                createAction(contextMap);

                if (pushAction) {

                    stack.push(action);

                    contextMap.put("action", action);

                }
                ……
            } 

在拦截器被调用之前就压入ValueStack了。

        作用:
            当Action被压入ValueStack之后,Action如果向传递数据给JSP,只要将数据保存为成员变量,并且提供get()方法就可以了。

        当Action中声明了一个getXxx()方法后,ValueStack会将get之后的JavaBean的名称放到ValueStack的key中,然后在页面中可以直接使用JavaBean对象的key值来获取value值。

        

        示例代码:
            public class OgnlDemo4Action extends ActionSupport {

                private List users;
 

                public List getUsers() {

                    return users;

                }

                public void setUsers(List users) {

                    this.users = users;

                }

                @Override

                public String execute() throws Exception {

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

                    users = new ArrayList();

                    users.add(new User("zhangsan", "111", 20, "男"));

                    users.add(new User("lisi", "222", 30, "女"));

                    users.add(new User("wangwu", "333", 40, "男"));

                    return SUCCESS;

                }

            }

        JSP代码:
            

                

            

2)模型驱动
        ModelDriven接口有一个单独的拦截器
           
        在拦截器中,将Model对象压入了ValueStack:stack.push(model);
        如果Action实现了ModelDriven接口,ValueStack默认栈顶对象就是Model对象。
        因为Action对象在拦截器执行前就已经压入,而Model对象在ModelDrivenInterceptor拦截器执行时才会压入。

        ModelDrivenInterceptor源码:
             public String intercept(ActionInvocation invocation) throws Exception {

                Object action = invocation.getAction();

                // 将实现了ModelDriven接口的Action中的getModel()方法的返回值也就是内部封装的JavaBean对象压入到了ValueStack
                if (action instanceof ModelDriven) {

                    ModelDriven modelDriven = (ModelDriven) action;

                    ValueStack stack = invocation.getStack();

                    Object model = modelDriven.getModel();

                    if (model !=  null) {

                        stack.push(model);

                    }

                    if (refreshModelBeforeResult) {

                        invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));

                    }

                }

                return invocation.invoke();

            }

在同一个请求中,Action中声明的private User user = new User();和getModel()方法中返回的User是相同的,也就是说在JSP页面中通过ValueStack获取到的User对象是同一个对象。但是,如果在execute()方法中对user重新赋值,那么push到ValueStack中的Action所包含的User就是后赋值的user对象,因为这个User对象是通过getXxx()方法获得的。而Model依然是private User user = new User();对象,因为Model是在execute()方法执行之前被压入的,所以在获取值时需要注意,这是两个对象。(new了两次,是两个对象。)

    ModelDriven中保存的是初始化时压入的对象。
    Action中保存的是execute()方法中赋值的对象。

    示例代码:

        public class OgnlDemo4Action extends ActionSupport implements ModelDriven {

            private User user = new User();

            @Override

            public String execute() throws Exception {

                return SUCCESS;

            }

            public Object getModel() {

                return this.user;

            }

        }

10、为什么EL表达式可以访问ValueStack中的数据

    Action中:
        stack.set("username", "张三");
    JSP中:

        ognl获取:

        EL获取:${username }

Struts2框架中所使用的request对象,是增强后的request对象,重写了getAttribute()方法。

    StrutsPreparedAndExecuteFilter的doFilter()方法中:
        request = prepare.wrapRequest(request)
        *   对Request对象进行了包装,包装类:StrutsRequestWrapper
        *   重写了request的getAttribute()方法
        Object attribute = super.getAttribute(key)
        if(attribute == null) {
            attribute = stack.findValue(key);
        }

    增强后的request,会首先在request范围查找,如果查找不到,会到ValueStack中查找。

——OGNL表达式的常见使用

1、#
    1)#相当于ActionContext.getContext()上下文
       
        相当于:ActionContext.getContext().getRequest().get("name");

    2)不写#默认在ValueStack的Root中进行查找

    3)进行投影映射以及过滤操作(结合复杂对象遍历)
        映射:
            

            1、使用iterator进行遍历

            

                

                

                

                

            


            2、对集合进行投影,只得到指定的属性

            

            

                

            

        过滤: 

            3、对集合进行过滤操作

            

            

                名称:

                价格:

                数量:

            

            


            4、对集合进行过滤操作

            

            

                名称:

            

    4)使用#构造Map集合
        经常结合Struts2标签用来生成select、checkbox、radio

        示例代码:
            1、使用#构造一个Map集合

                

                     ---

                

                


                2、构造一个List集合

                

                    

                

                


                3、手动创建一个集合,在Struts2中结合表单标签使用

                

                    

                    

                    

                    

                    

                    

                

2、%

    作用是用来设定当前字符串是否要解析为ognl表达式。

    

演示%用法

    <%

        request.setAttribute("username", "tom");

    %>

    

    

    

    

    

    

    

    

3、$
    作用是在配置文件中写ognl表达式来获取ValueStack中的数据。

    1)struts.xml
                    ${contentType}        

    2)在校验文件中使用
       
       
       
        校验文件会引入校验器,校验器会被加载,所以数据也会保存到ValueStack中。

    3)在国际化文件中使用
        在properties文件中:
            username=${#request.username}

        在JSP页面:
            <%

                request.setAttribute("username", "李四");

            %>

            

                

            

        在properties文件中也可以使用%获取值:
            username=%{request.username} 

——总结

1、ognl介绍
2、ValueStack介绍
3、什么是ValueStack
4、ValueStack内部结构
5、ValueStack创建以及ActionContext关系
6、如何获取ValueStack
7、如何向ValueStack存储数据(Root)
8、JSP页面中如何获取ValueStack数据
9、关于ValueStack携带数据分析
    *   携带数据类型
    *   如何在页面上获取复杂数据
    *   ValueStack默认压入数据

10、如何使用OGNL表达式
    *   #
    *   %

    *   $

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章