Expression Language 3.0新特性

编程技术  /  houtizong 发布于 3年前   69

Expression Language 3.0表达式语言规范最终版从2013-4-29发布到现在已经非常久的时间了;目前如Tomcat 8、Jetty 9、GlasshFish 4已经支持EL 3.0。新特性包括:如字符串拼接操作符、赋值、分号操作符、对象方法调用、Lambda表达式、静态字段/方法调用、构造器调用、Java8集合操作。目前Glassfish 4/Jetty实现最好,对大多数新特性都支持、Tomcat8对如Lambda、静态字段/方法调用、构造器调用目前不支持。

 

点击下载EL 3.0规范。另外可以访问规范官网下载javadoc等:https://jcp.org/en/jsr/detail?id=341 

 

初始数据

<%@page pageEncoding="utf-8" trimDirectiveWhitespaces="true"%><%@page import="java.util.*"%><%@page import="com.User"%><%User user = new User();user.setId(1);user.setName("zhangsan");pageContext.setAttribute("user", user);List<String> list = new ArrayList<String>();list.add("1");list.add("2");pageContext.setAttribute("list", list);%>

 

1、字符串拼接

${a="1"}<br/>${b="2"}<br/>${a=a+=b}<br/>${a}<br/>

a+=b 连接a和b,并把结果返回;注意,此处不等价于a=a+b。

 

2、赋值操作符

${i = 1}<br/>${i}<br/><%=pageContext.getAttribute("i")%><br/>${set={1,2,3,3}}<br/>${list=[1,2,3,3]}<br/>${list[3]}<br/>${map = {"k1" : "123", "k2": "234"}}<br/>${map['k1']}<br/><br/>

如${i=1} 会把1赋值给i,i默认存储在page作用域;可以使用pageContext.getAttribute("i")获取。另外可以创建Set、List、Map类型的对象。

 

3、分号操作符

${a=1;123}<br/><br/>

最后一个分号后的表达式是整个表达式的值。

 

4、调用对象方法 

${user.setId(123)}${user.getId()}<br/><br/>

 

5、Lambda表达式

${(()->9)()}<br/>${((x,y)->x+y)(1,2)}<br/>${fact = n -> n==0? 1: n*fact(n-1); fact(5)}<br/><br/>

 

6、静态字段/方法调用、构造器调用

<%pageContext.getELContext().getImportHandler().importClass("java.util.UUID");pageContext.getELContext().getImportHandler().importClass("java.util.Locale");pageContext.getELContext().getImportHandler().importPackage("java.io");pageContext.getELContext().getImportHandler().importStatic("java.util.UUID.randomUUID");pageContext.getELContext().getImportHandler().importStatic("java.lang.Math.PI");pageContext.getELContext().getImportHandler().importClass("com.User");%>${Integer.valueOf("123")}<br/>${UUID.randomUUID()}<br/>${Locale.CHINESE}<br/>${File("D:\\aa.txt").exists()}<br/>${randomUUID()}<br/>${PI}<br/>${User.i}<br/>${User.hello()}<br/>${Boolean(true)}<br/>${User()}<br/><%javax.el.ELProcessor elp = new javax.el.ELProcessor();elp.getELManager().importStatic("com.User.i");out.print(elp.eval("i"));%><br/><br/> 

使用前必须通过importHandler导入相应的包/类/静态字段/方法。导入包后,可以直接使用如${UUID.randomUUID()}访问该包下的类的静态字段/方法;也可以只导入某个类,然后也可以使用如${UUID.randomUUID()}访问该类的静态字段/方法;另外可以直接导入静态字段/方法,然后直接使用如${PI} / ${randomUUID()}访问静态字段/方法。 也可以使用如${Boolean(true)}、${User()}、${File("D:\\aa.txt").exists()}访问构造器创建对象。此处默认导入"java.lang"包下的内容。

 

6、集合对象操作

${list.stream().filter(i -> i != "1").toList()} <br/>

非常类似于Java 8中的集合操作类库,更多语法请参考EL 3.0规范。

 

 

 

tomcat 8目前对Lambda表达式、静态字段/方法调用、构造器调用暂不支持。Jetty 9/ GlasshFish对静态字段/方法访问还是有点小bug的,可以通过修改如下代码修复:

 

问题1、类加载问题

首先如tomcat类加载器模型如下:

            Bootstrap

                  |

             System

                  |

            Common

               /     \

 Webapp1   Webapp2 ... 

tomcat类加载器模型请参考http://tomcat.apache.org/tomcat-8.0-doc/class-loader-howto.html

另外类加载器加载类时使用委托模型,即先从父加载,父没有再自己加载。此处javax.el.jar是由common类加载器加载;如我们的el webapp,是由自己的类加载器加载。

 

在[5、静态字段/方法调用、构造器调用]我们使用pageContext.getELContext().getImportHandler().importClass("java.util.UUID")注册类;然后呢当我们访问如${UUID.randomUUID()}时会首先调用ImportHandler.getClassFor("UUID")得到类:

 

    private Class<?> getClassFor(String className) {        if (!notAClass.contains(className)) {            try {                return Class.forName(className, false, getClass().getClassLoader());            } catch (ClassNotFoundException ex) {                notAClass.add(className);            }        }        return null;    }

 首先尝试common类加载器加载,其委托给System类加载,其又委托给Bootstrap类加载加载,此时会加载到java.util.UUID类;没有什么问题;当我们使用pageContext.getELContext().getImportHandler().importClass("com.User");注册el webapp的类时;此处要注意的是pageContext.getELContext().getImportHandler()该类是common类加载加载的;因此加载User时是使用该common类加载器加载,此时会加载不到el webapp中的User类,因为无法向后代加载器加载。因此会有问题;不过我们可以使用: 

return Class.forName(className, false, Thread.currentThread().getContextClassLoader());

即使用当前线程类加载器加载User,此时就能加载到User了。 

 

请到https://svn.java.net/svn/uel~svn/trunk/签出代码进行修改。另外可以直接下载附件中已经修改好的javax.el.jar进行测试。

 

问题2、静态字段解析问题

经过测试,无法正确解析静态导入静态字段解析;如pageContext.getELContext().getImportHandler().importStatic("java.lang.Math.PI");,无法使用${PI}获取其值。这是JSP中提供的ScopedAttributeELResolver解析器的问题,该解析器的作用是从作用域中查找属性值;其不管解析到还是解析不到都会调用context.setPropertyResolved(true)告知属性已经解析出来了,此处将中断后续的静态字段解析。 

    public Object getValue(ELContext context,                           Object base,                           Object property) {        if (context == null) {            throw new NullPointerException();        }        if (base == null) {            context.setPropertyResolved(true);            if (property instanceof String) {                String attribute = (String) property;                PageContext ctxt = (PageContext)                                       context.getContext(JspContext.class);                Object value = ctxt.findAttribute(attribute);                // To support reference of static fields for imported class in                // EL 3.0, if a scoped attribute returns null, this attribute                // is further checked to see if it is the name of an imported                // class.  If so, an ELClass instance is returned.                // Note: the JSP spec needs to be updated for this behavior. Note                // also that this behavior is not backward compatible with JSP 2.2                // and a runtime switch may be needed to force backward                // compatility.                if (value == null) {                    // check to see if the property is an imported class                    if (context.getImportHandler() != null) {                        Class<?> c = context.getImportHandler().resolveClass(attribute);                        if (c != null) {                            value = new ELClass(c);                            // A possible optimization is to set the ELClass                            // instance in an attribute map.                        }                    }                }                return value;            }        }        return null;    }

修改为 

    public Object getValue(ELContext context,                           Object base,                           Object property) {        if (context == null) {            throw new NullPointerException();        }        if (base == null) {            if (property instanceof String) {                String attribute = (String) property;                PageContext ctxt = (PageContext)                                       context.getContext(JspContext.class);                Object value = ctxt.findAttribute(attribute);                // To support reference of static fields for imported class in                // EL 3.0, if a scoped attribute returns null, this attribute                // is further checked to see if it is the name of an imported                // class.  If so, an ELClass instance is returned.                // Note: the JSP spec needs to be updated for this behavior. Note                // also that this behavior is not backward compatible with JSP 2.2                // and a runtime switch may be needed to force backward                // compatility.                if (value == null) {                    // check to see if the property is an imported class                    if (context.getImportHandler() != null) {                        Class<?> c = context.getImportHandler().resolveClass(attribute);                        if (c != null) {                            value = new ELClass(c);                            // A possible optimization is to set the ELClass                            // instance in an attribute map.                        }                    }                }                if(value != null) {                    context.setPropertyResolved(true);                }                return value;            }        }        return null;    }

修改后,即解析到时才调用 context.setPropertyResolved(true)告知已经解析到属性值,后续不再解析了。

 

请到https://svn.java.net/svn/jsp~svn/trunk/api签出代码进行修改。另外可以直接下载附件中已经修改好的javax.servlet.jsp-api.jar进行测试。

 

glassfish4:需要把这两个jar复制到:glassfish4\glassfish\modules下,把其内部的覆盖掉;

jetty9:需要把这两个jar复制到:jetty-distribution-9.1.2.v20140210\lib\jsp下,把javax.el-3.0.0.jar和javax.servlet.jsp-api-2.3.1.jar删掉,并添加新的jar;

tomcat8:通过替换jar包还是解决不了问题,没有仔细研究,希望未来官方修复吧。

 

其实从如上可以看出,调用静态字段/方法、调用构造器比较麻烦;如果需要可以写一个taglib消除java脚本的调用。

 

另外也可以在普通java环境使用如下代码直接进行表达式计算:

javax.el.ELProcessor elp = new javax.el.ELProcessor();elp.getELManager().importStatic("com.User.i");out.print(elp.eval("i"));

 

 

从目前来看,以后可能用的最多的就是:赋值操作符、调用对象方法;其他的可能会用的比较少。

 

另外EL 3.0拥有OGNL的大多数功能,但是EL在JSP中大部分时间都是编译期翻译,所以相对于OGNL更安全,不会像OGNL那样漏洞百出。 

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!

留言需要登陆哦

技术博客集 - 网站简介:
前后端技术:
后端基于Hyperf2.1框架开发,前端使用Bootstrap可视化布局系统生成

网站主要作用:
1.编程技术分享及讨论交流,内置聊天系统;
2.测试交流框架问题,比如:Hyperf、Laravel、TP、beego;
3.本站数据是基于大数据采集等爬虫技术为基础助力分享知识,如有侵权请发邮件到站长邮箱,站长会尽快处理;
4.站长邮箱:[email protected];

      订阅博客周刊 去订阅

文章归档

文章标签

友情链接

Auther ·HouTiZong
侯体宗的博客
© 2020 zongscan.com
版权所有ICP证 : 粤ICP备20027696号
PHP交流群 也可以扫右边的二维码
侯体宗的博客