学习Java Web开发,一开始好像都练不过Servlet/JSP,多多少少都学习过他们的基本使用方法。其中在JSP的使用中,更是涉及到taglib、Jstl、EL这些东西。那具体到我们在页面中写的类似这种形式的EL表达式:

  1. ${1+2}

  2. ${1000}


在使用JSP进行页面展示时都会使用到。其本质上,人如其名,表达式也一样,${} 中间的也是一个表达式。而在页面中,我们实质上使用表达式是为了求值,并不仅仅是为了放到那里原样显示的,所以这就涉及到表达式的一个问题:


你的表达式到底能不能进行求值


例如上面的${1+2}是可以求值的,最后计算后,页面上显示3。那如果你写了一个${1+2x},这个时候谁也不知道它应该是多少,无法进行求值,所以这个时候JSP会具体的告诉你,表达式不合法,并且提示你具体的错误位置,就像这个样子:

Tomcat的EL表达式解析器_java

我们看上面的图片,上方是具体的stackTrace。下方的root cause是具体更详细的错误产生原因。那这里比较有意思了,他提示了你具体的错误位置,还有后面期望的,即允许输入的内容,明显的x不在这个范围所以就表达式不合法了。


那这个校验是谁干的呢?

就是大名鼎鼎的ELParser。江湖人称EL表达式解析器。了解过编译原理的朋友可能都知道,进行一项语法解析的时候,大致分为词法分析和语法分析两步。词法分析解析出的Token流,再输入语法分析器进行校验,最终得出结果。


下图即为一个语法解析器的示意图:


Tomcat的EL表达式解析器_java_02

Tomcat的EL表达式解析器_java_03



而要实现上面的词法和语法解析,要了解BNF范式啥的,各种边界问题,一大堆东西,并不容易。前面描述了这么多,那我们来看Tomcat是怎么实现EL表达式的语法校验的呢?


在el相关的源码中,除了功能实现相关的lang包之外,还有一个parser包,里面有比较主要的三个相关文件:ELParser, TokenManager,还有一个名为ELParser.jjt的文件。


这里就是Tomcat对于EL表达式解析的关键所在,它正是使用了JavaCC完成的。


JavaCC(Java Compiler Compiler)是一个用JAVA开发的最受欢迎的语法分析生成器。这个分析生成器工具可以读取上下文无关且有着特殊意义的语法并把它转换成可以识别且匹配该语法的JAVA程序。JavaCC可以在Java虚拟机(JVM) V1.2或更高的版本上使用,它是100%的纯Java代码,可以在多种平台上运行,与Sun当时推出Java的口号"Write Once Run Anywhere"相一致。JavaCC还提供JJTree工具来帮助我们建立语法树,JJDoc工具为我们的源文件生成BNF范式(巴科斯-诺尔范式)文档(Html)。


下载:

JavaCC:           https://javacc.java.net/

eclipse插件:  http://www.easyeclipse.org/site/plugins/JavaCC.html


JavaCC有和BNF类似的语法,可以在javacc的samples目录中查看,

javaCC有三个工具

  1. javaCC 用来处理语法文件(jj)生成解析代码;

  2. jjTree 用来处理jjt文件,生成树节点代码和jj文件,然后再通过javaCC生成解析代码;

  3. jjDoc 根据jj文件生成bnf范式文档(html)


JavaCC根据.jj文件定义的规则,生成一系列java文件。 如果使用默认属性配置,一个.jj文件会生成以下7个文件。

自定义文件(XXX是指定的名称)

  • SimpleCharStream.java - represent the stream of input characters.

  • Token.java - represents a single input token

  • TokenMgrError.java - an error thrown from the token manager.

  • ParseException.java - an exception indicating that the input did not conform to the parser's grammar.

  • XXX.java - the parser class

  • XXXTokenManager.java - the token manager class.

  • XXXConstants.java - an interface associating token classes with symbolic names.


XXXTokenManager类似于一个词法分析器,将输入的串分成各个token.每个token对象中有两个比较有用的属性Kind和image。 kind是在常量文件中具体的int值,image是该token实际的值,比如从字符串中解析到的String。XXXConstants是在jj文件中定义的各类TOKEN常量。

以上是关于javaCC的介绍,这里有个注意问题,如果在Token定义的时候,如果使用到了汉字,需要通过设置OPTION, UNICODE_INPUT=true来解决。

一般的使用步骤是在jj或者jjt文件中,进行Token等语法定义,哪些是允许输入的项,哪些项和项之间是允许出现的符号等,之后通过eclipse或者javacc的脚本,来生成具体的解析器java代码。


Tomcat是使用了比jj文件更复杂的jjt来进行定义和解析,生成词法和语法分析器的。

我2014年在公司的某个系统里进行用户自定义公式的输入语法校验时,当时毫无头绪。想着使用正则表达式或者自己写一个自定义的数据结构来进行校验,类似于校验括号是否匹配这一类的,但这些方法都不能很好的解析具体错误位置提示,而且当时自定义的公式种式繁多,还有各种自定义函数等等,边界条件都可能想不明白。后来了解到了JavaCC,用到了项目里,很好的解决了问题。


这里推荐给各位有需要的朋友。除此之外,还有一些开源的项目里是使用的Antlr进行语法解析,原理一致,两者各有千秋。我当时选择是因为JavaCC语法上和Java类似,编写和上手更容易,而且更轻量级,编译后不再需要依赖JavaCC的任何内容。


所以,Tomcat对于EL表达式解析的实现,就是将用户页面上输入的表达式,以串的形式交给ELParser,再根据是否有异常再判断结果。