Tomcat 源码解析一EL表达式源码解析

  我相信以前用过JSP及Servlet的小伙伴肯定对EL表达式并不陌生,那段时间,前后端没有分离,所有的页面逻辑就是div + css + jsp 相关语法,及el表达式,jsp相关的源码我们在下一篇博客来分析,先来分析其中的一个点EL表达式,关于语言层面的博客我相信已经分析过不少了,之前的https://github.com/quyixiao/testshell 项目,关于自己实现自己的测试框架,以及SPEL表达式,都分析过, SPEL表达式虽然和EL表达式像,但源码实现完全不同,而EL表达式的源码解析和beanshell的源码很类似,只是语法解析方式不相同,但是无论哪种语言的源码解析,我相信都离不开一个一个字符的读取,将读取到的关键字,字面量封装成一颗语法树,通过语法树的左右节点的值提取并做相关运算,得到最终表达式的值 。
  而语法树的解析是一个痛苦的过程,需要一个字符一个字符的读取及分析,将读取到的内容封装成相关的对象,而这个对象刚好又是语法树一个节点,表达式最终的值就是通过计算一个一个节点的值并做相关运算而得到最终的值,这是理论知识,鉴于我在网上查找并没有找到相关的EL表达式源码解析的相关博客,而我进入源码一看。 我的天。
在这里插入图片描述
  上面这些代码写得什么东西,完全看不懂, 我相信写EL表达式源码解析肯定是一个吃力不讨好的事情, 鉴于我正在研究Tomcat源码,如果连EL表达式都不去研究,那即便能知道整个架构的设计思想,整个架构的流程走向,在和别人交流Tomcat这一块时能侃侃而谈, 如果这些细节都不知道,那又有什么意义呢? 现实生活中有很多开发都是如此,感觉什么都知道,专业名词层出不穷,但到了解决具体问题时,却无从下手,正如
“蚓无爪牙之利,筋骨之强,上食埃土,下饮黄泉,用心一也。蟹六跪而二螯,非蛇鳝之穴无可寄托者,用心躁也。” ,因此作为一个技术开发,还是要静下心来,细节决定成败 。
  那我们进入正题,这篇博客主要从两部分来分析EL表达式,第一部分,EL表达式解析成语法树,第二部分,EL 表达式的值获取 。

  我们先来看一个例子。 创建index.jsp

<%--
  Created by IntelliJ IDEA.
  User: quyixiao
  Date: 2022/5/22
  Time: 11:02
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.sql.*,javax.sql.*,javax.naming.*" %>
<%@ page import="com.example.servelettest.Person" %>
<%-- 我在测试 --%>
<%
      Person person =new Person();
      person.setName("帅哥");
      person.setHeight(167);
      person.setAge(20);
      request.setAttribute("person", person);
%>
名字 : ${person.name} <br>
人身高 : ${person.height}

  其中${person.name} 和 ${person.height} 就是EL表达式的应用,那我们先来看一下生成的index_jsp.java源码。

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {
	... 
  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {
    final javax.servlet.jsp.PageContext pageContext;
    javax.servlet.http.HttpSession session = null;
    final javax.servlet.ServletContext application;
    final javax.servlet.ServletConfig config;
    javax.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;
    try {
      response.setContentType("text/html;charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
            null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write("\n");
      out.write("\n");
      out.write("\n");
      out.write("\n");
      out.write('\n');

      Person person =new Person();
      person.setName("帅哥");
      person.setHeight(167);
      person.setAge(20);
      request.setAttribute("person", person);

      out.write("\n");
      out.write("名字 : ");
      out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${person.name}", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, null, false));
      out.write(" <br>\n");
      out.write("人身高 : ");
      out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${person.height}", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, null, false));
    } catch (java.lang.Throwable t) {
    	...
  }
}

  从解析出来的源码可以看到,最终jsp生成的java文件是调了PageContextImpl的proprietaryEvaluate方法。我们进入这个方法 。

public static Object proprietaryEvaluate(final String expression,
        final Class<?> expectedType, final PageContext pageContext,
        final ProtectedFunctionMapper functionMap, final boolean escape)
        throws ELException {
    final ExpressionFactory exprFactory = jspf.getJspApplicationContext(pageContext.getServletContext()).getExpressionFactory();
    ELContext ctx = pageContext.getELContext();
    ELContextImpl ctxImpl;
    if (ctx instanceof ELContextWrapper) {
        ctxImpl = (ELContextImpl) ((ELContextWrapper) ctx).getWrappedELContext();
    } else {
        ctxImpl = (ELContextImpl) ctx;
    }
    ctxImpl.setFunctionMapper(functionMap);
    ValueExpression ve = exprFactory.createValueExpression(ctx, expression, expectedType);
    return ve.getValue(ctx);
}

  上面的一堆代码,其他的后面再来分析,先看加粗代码 。

public ValueExpression createValueExpression(ELContext context,
        String expression, Class<?> expectedType) {
    if (expectedType == null) {
        throw new NullPointerException(MessageFactory
                .get("error.value.expectedType"));
    }
    ExpressionBuilder builder = new ExpressionBuilder(expression, context);
    return builder.createValueExpression(expectedType);
}



public ValueExpression createValueExpression(Class<?> expectedType)
        throws ELException {
    Node n = this.build();
    return new ValueExpressionImpl(this.expression, n, this.fnMapper,
            this.varMapper, expectedType);
}


private Node build() throws ELException {
    Node n = createNodeInternal(this.expression);
    this.prepare(n);
    if (n instanceof AstDeferredExpression
            || n instanceof AstDynamicExpression) {
        n = n.jjtGetChild(0);
    }
    return n;
}


private static final Node createNodeInternal(String expr)
        throws ELException {
    if (expr == null) {
        throw new ELException(MessageFactory.get("error.null"));
    }
    // 如果表达式已经解析过, 则从内存中获取
    Node n = cache.get(expr);
    if (n == null) {
        try {
            n = (new ELParser(new StringReader(expr)))
                    .CompositeExpression();
                    
            int numChildren = n.jjtGetNumChildren();
            if (numChildren == 1) {
                n = n.jjtGetChild(0);
            } else {
                Class<?> type = null;
                Node child = null;
                for (int i = 0; i < numChildren; i++) {
                    child = n.jjtGetChild(i);
                    if (child instanceof AstLiteralExpression)
                        continue;
                    if (type == null)
                        type = child.getClass();
                    else {
                        if (!type.equals(child.getClass())) {
                            throw new ELException(MessageFactory.get(
                                    "error.mixed", expr));
                        }
                    }
                }
            }

            if (n instanceof AstDeferredExpression
                    || n instanceof AstDynamicExpression) {
                n = n.jjtGetChild(0);
            }
            cache.put(expr, n);
        } catch (Exception e) {
            throw new ELException(
                    MessageFactory.get("error.parseFail", expr), e);
        }
    }
    return n;
}

  如果表达式已经解析过, 则从缓存中获取语法树,如果没有解析过,则调用上面加粗代码进行表达式的解析。当表达式解析分析完后再来分析其他代码。
  我们进入ELParser,而ELParser类依赖于ELParserTokenManager类,ELParserTokenManager类中就有之前难以理解代码 。

  之前也看过,上面这些代码如此难理解,那怎么办呢? 我们着手开始替换,先将这些数字替换成字符,为了不修改原来的源码,重新起一个项目 , 将EL 表达式相源码拷贝过来。 创建项目 https://github.com/quyixiao/tomcat-el

在这里插入图片描述

  我们将所有能替换的先替换掉, 这样来看,是不是就清晰一点。 再将ELParserConstants中的相关数字
在这里插入图片描述

  全部替换成标识符

在这里插入图片描述

  为什么我知道这些数字代表着相应的标识符呢?因为这些数字刚好和tokenImage相对应,即DOT=16 ,实际上对应的就是tokenImage[16] 。
在这里插入图片描述
  我们再来看一个工具类SimpleCharStream,这个工具类的实际用途对EL表达式一个字符一个字符读取,将读取的内容返回给调用端,我之所以分析SimpleCharStream类,主要是其中的算法意义值得我们借鉴。

SimpleCharStream
public class SimpleCharStream {

    public static final boolean staticFlag = false;
    int bufsize;                                            // buffer数组的长度
    int available;                                          // buffer的可用长度
    int tokenBegin;                                         // 每一次获取 token时,在 buffer数组的起始位置

    public int bufpos = -1;                                 //最后一次读取字符所在buffer数组中的位置
    protected int bufline[];                                //buffer 数组中的每一个字符对应在脚本中的行
    protected int bufcolumn[];                              //buffer 数组中每一个字符对应的列

    protected int column = 0;                               //列下标从0开始
    protected int line = 1;                                 //行下标从1开始

    protected boolean prevCharIsCR = false;
    protected boolean prevCharIsLF = false;

    protected java.io.Reader inputStream;
    public final static int  expandSize=0;
    public final static int  initSize=8;

    protected char[] buffer;                                //外部每一次读取的字符缓存数组
    // 每次从 inputStream 中读取的固定长度
    //nextCharBuf索引计数器,每ReadByte()一次,nextCharInd值加1,
    //当maxNextCharInd == nextCharInd 时,inputStream需要再次 reader byte 到nextCharBuf中,直到文件读取结束
    protected int maxNextCharInd = 0;
    protected int inBuf = 0;                                //每次backup回退的byte数
    protected int tabSize = 8;

    protected void setTabSize(int i) {
        tabSize = i;
    }
    protected int getTabSize(int i) {
        return tabSize;
    }
    //此函数主要用于buffer数组扩容
    protected void ExpandBuff(boolean wrapAround) {
        char[] newbuffer = new char[bufsize + expandSize];
        int newbufline[] = new int[bufsize + expandSize];
        int newbufcolumn[] = new int[bufsize + expandSize];
        try {
            if (wrapAround) {
                System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
                System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos);
                buffer = newbuffer;

                System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
                System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
                bufline = newbufline;

                System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
                System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
                bufcolumn = newbufcolumn;

                maxNextCharInd = (bufpos += (bufsize - tokenBegin));
            } else {
                System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
                buffer = newbuffer;
                System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
                bufline = newbufline;
                System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
                bufcolumn = newbufcolumn;
                maxNextCharInd = (bufpos -= tokenBegin);
            }
        } catch (Throwable t) {
            throw new Error(t.getMessage());
        }
        
        bufsize += expandSize;
        available = bufsize;
        tokenBegin = 0;
    }

	// 只要调用了BeginToken()方法,为了节省内存空间
	// BeginToken()方法之前所有readChar()过的字符都是可以从buffer数组中丢弃的
    public char BeginToken() throws java.io.IOException {
        tokenBegin = -1;
        char c = readChar();
        tokenBegin = bufpos;

        return c;
    }

	// 更新当前字符在表达式中的行和列
    protected void UpdateLineColumn(char c) {
    	
        column++;
		// 如果当前字符的上一个字符是\n 
        if (prevCharIsLF) {
            prevCharIsLF = false;
            // 行号 + 1  , 列为第1列 
            line += (column = 1);
        } else if (prevCharIsCR) {		// 如果当前字符的上一个字符是\r 
            prevCharIsCR = false;
            // 之前的字符是\r,当前字符为\n,也就是\r\n同时出现时,只需要行+1 ,列为1即可 
            if (c == '\n') {
                prevCharIsLF = true;
            } else
            	// 当前字符的列为1 ,行号+1 
                line += (column = 1);
        }

        switch (c) {
            case '\r':
                prevCharIsCR = true;
                break;
            case '\n':
                prevCharIsLF = true;
                break;
            case '\t':
            	// 因为一进入UpdateLineColumn方法时column 就++了 
            	// 此时column 要-- 
                column--; 
                // 如果当前列为3  ,column = 8 - (3 % 8 ) = 5 
                // 如果当前光标定位为3 ,则\t 之后,光标移到到第8列
                column += (tabSize - (column % tabSize));
                break;
            default:
                break;
        }

        bufline[bufpos] = line;
        bufcolumn[bufpos] = column;
    }

    //主要是提供给外部调用,当上一次调用了 backup函数时
    //inBuf > 0 ,此时不再从nextCharBuf数组中读取,而是从缓存buffer中读取
    public char readChar() throws java.io.IOException {
    	// 当调用backup(n)时, inBuf = inBuf + n 
    	// 因此,当调用backup(n) 时,字符要从之前的读过的字符读取 
        if (inBuf > 0) {
            --inBuf;
			// 如果已经读取到了buffer[bufsize-1]的字符时
			// 需要从buffer数组的开头开始读取 
            if (++bufpos == bufsize)
                bufpos = 0;
			// 返回buffer中的字符
            return buffer[bufpos];
        }

		// maxNextCharInd 为已经读取过的字符的起始位置
		// buffer[maxNextCharInd] , buffer [maxNextCharInd + 1 ] , buffer[maxNextCharInd+2] ....
		// 这些字符都已经被read了或都被丢弃了, 此时需要对buffer
		// 数组中的字符重新读取或对buffer数组扩容 
        if (++bufpos >= maxNextCharInd)
            FillBuff();
        char c = buffer[bufpos];

        UpdateLineColumn(c);
        return c;
    }
	... 
    public void backup(int amount) {
        inBuf += amount;
        if ((bufpos -= amount) < 0)
            bufpos += bufsize;
    }

   
    public SimpleCharStream(java.io.Reader dstream, int startline,
                            int startcolumn, int buffersize) {
        inputStream = dstream;
        line = startline;
        column = startcolumn - 1;

        available = bufsize = buffersize;
        buffer = new char[buffersize];
        bufline = new int[buffersize];
        bufcolumn = new int[buffersize];
    }


    public SimpleCharStream(java.io.Reader dstream, int startline,
                            int startcolumn) {
        this(dstream, startline, startcolumn, initSize);
    }


    public SimpleCharStream(java.io.Reader dstream) {
        this(dstream, 1, 1, initSize);
    }

    ... 


    public String GetImage() {
        if (bufpos >= tokenBegin)
            return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
        else
            return new String(buffer, tokenBegin, bufsize - tokenBegin) +
                    new String(buffer, 0, bufpos + 1);
    }

    ... 
    
    protected void FillBuff() throws java.io.IOException {
        if (maxNextCharInd == available) {
        	
            if (available == bufsize) {				//1
                if (tokenBegin > expandSize) {		//1.1
                    bufpos = maxNextCharInd = 0;
                    available = tokenBegin;
                } else if (tokenBegin < 0)			//1.2
                    bufpos = maxNextCharInd = 0;
                else
                    ExpandBuff(false);   			//1.3
            } else if (available > tokenBegin)	 	//2
                available = bufsize;
            else if ((tokenBegin - available) < expandSize) //3 
                ExpandBuff(true);
            else
                available = tokenBegin;  			//4 
        }
        int i;
        try {
        	// 从流中将字符读取到buffer中
            if ((i = inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) == -1) {
                inputStream.close();
                throw new java.io.IOException();
            } else {
                System.out.println(Arrays.toString(buffer));
                maxNextCharInd += i;
            }
            return;
        } catch (java.io.IOException e) {
            --bufpos;
            backup(0);
            if (tokenBegin == -1)
                tokenBegin = bufpos;
            throw e;
        }
    }
}

  假如buffer初始化长度为8,每一次扩容数组的容量(expandSize) 为4,下面对上面注释中1-4的几种情况进行分析 。

1.1

  当调用BeginToken()方法时,会修改tokenBegin的值,而调用这个方法的意义在于,之前读取过的字符都可以被丢弃掉,因为之前读取的字符已经被封装成Token,保存到树节点中,因此这些字符已经没有任何用处,可以被覆盖掉, 对于FillBuff()方法中,1.1的情况,如下图所示。假如 EL表达式的内容为 abcdefghijklmnopqrstuvwxyz,tokenBegin=6,因此buffer数组中a,b,c,d,e,f是可以被覆盖的,因为tokenBegin = 6 并且大于数组可扩容大小4,因此与其扩容,不如直接覆盖掉之前已经丢弃掉的字符,因此就注释1.1 的业务逻辑。
在这里插入图片描述
测试代码如下

public static void test1() throws Exception{
    Reader in = new FileReader("/Users/quyixiao/gitlab/tomcat-el/src/test/el/test4.tsh");
    SimpleCharStream jj_input_stream = new SimpleCharStream(in, 1, 1);

    for(int i = 0 ;i < 7;i ++){
        jj_input_stream.readChar();
    }
    jj_input_stream.BeginToken();
    for(int i = 0 ;i < 12;i ++){
        jj_input_stream.readChar();
    }
}

test4.tsh中保存了字符串abcdefghijklmnopqrstuvwxyz

1.2

  当刚好整个buffer数组中读取完时,调用BeginToken()方法, 此时buffer数组中的所有字符可以清理掉,因此就会走到1.2 的代码 。
在这里插入图片描述

public static void test1() throws Exception{
    Reader in = new FileReader("/Users/quyixiao/gitlab/tomcat-el/src/test/el/test4.tsh");
    SimpleCharStream jj_input_stream = new SimpleCharStream(in, 1, 1);


    for(int i = 0 ;i < 8;i ++){
        jj_input_stream.readChar();
    }
    jj_input_stream.BeginToken();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
}

1.3

  当buffer数组已经读取完,仍然没有调用BeginToken()方法,表示buffer数组中的所有字符不能被丢弃掉,这种情况只能对buffer数组扩容了。

public static void test1() throws Exception{
    Reader in = new FileReader("/Users/quyixiao/gitlab/tomcat-el/src/test/el/test4.tsh");
    SimpleCharStream jj_input_stream = new SimpleCharStream(in, 1, 1);


    for(int i = 0 ;i < 8;i ++){
        jj_input_stream.readChar();
    }
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
}

  程序执行效果如下 。
在这里插入图片描述

2

  初始化buffer为abcdefgh,当读取到f字符时调用BeginToken()方法,继续read字符,当h字符读取完后,此时满足注释1.1 的情况,tokenBegin > expandSize ,当前可丢弃字符的长度大于数组扩容的长度,因此走了注释1.1的代码逻辑,此时继续读取3个字符后调用BeginToken()方法,此时tokenBegin = 2 , 继续不断的读取字符,当读取到字符n时,下一个字符o是可丢弃的字符,因此直接将available=bufsize即可,而此时通过inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd))方法,则只会覆盖掉之前的o 和p 字符 。
在这里插入图片描述

  测试代码如下

public static void test1() throws Exception{
    Reader in = new FileReader("/Users/quyixiao/gitlab/tomcat-el/src/test/el/test4.tsh");
    SimpleCharStream jj_input_stream = new SimpleCharStream(in, 1, 1);

    for(int i = 0 ;i < 6;i ++){
        jj_input_stream.readChar();
    }
    
    jj_input_stream.BeginToken();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();

    jj_input_stream.BeginToken();
    
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
}

在这里插入图片描述

3

  在注释2的基础上,如果字符在读取过程中第二次BeginToken()方法并没有调用,说明buffer中整个数组中的字符都不能被丢弃掉,因此需要调用ExpandBuff(true)方法对buffer数组进行扩容,为了保证字符读取的顺序,要先将读取的字符g,h拷贝到新数组前面,而后读取的i,j,k,l,m,n拷贝到新数组的后面。
在这里插入图片描述
测试代码如下

public static void test1() throws Exception{
    Reader in = new FileReader("/Users/quyixiao/gitlab/tomcat-el/src/test/el/test4.tsh");
    SimpleCharStream jj_input_stream = new SimpleCharStream(in, 1, 1);

    for(int i = 0 ;i < 6;i ++){
        jj_input_stream.readChar();
    }

    jj_input_stream.BeginToken();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
}

在这里插入图片描述

4

  对于注释4的情况,tomcat的开发者帮我们考虑到另外一种情况,当(tokenBegin - available) > expandSize 时,此时会设置available = tokenBegin , 即 available 和 tokenBegin之间的字符是可被丢弃的,并且大于本次数组扩容的长度,此时只需要将inputStream流中的字符读取并覆盖掉 available 和 tokenBegin之间的字符即可,因为扩容得到的容量大小还不如 available 和 tokenBegin 之间的空闲字符大小,因此将available = tokenBegin即可,而inputStream.read(buffer, maxNextCharInd, available - maxNextCharInd)) 则会将未读取的字符读取到buffer[available] ~ buffer[tokenBegin] 之间,理论是很好的,但是我想了很久都没有想到这种情况,只有在极端的情况下,才会出现(tokenBegin - available) >= expandSize 的情况,也就是我们将expandSize设置为0,用

public static void test1() throws Exception{
    Reader in = new FileReader("/Users/quyixiao/gitlab/tomcat-el/src/test/el/test4.tsh");
    SimpleCharStream jj_input_stream = new SimpleCharStream(in, 1, 1);

    for(int i = 0 ;i < 6;i ++){
        jj_input_stream.readChar();
    }

    jj_input_stream.BeginToken();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
    jj_input_stream.readChar();
}

  代码继续测试,发现当expandSize设置为0时,会进入available=tokenBegin的代码,因为只要expandSize > 0 ,不可能出现tokenBegin > available的情况,我想了很久都没有找到相应的测试用例,如果有小伙伴发现相关的测试用例,希望告诉我,也让我开开眼界。
在这里插入图片描述
  我们终于研究完了SimpleCharStream类,这个类的内部实现算法真正的做到了【精打细算】,不浪费一点内存空间,同时程序的健壮性及算法值得我们学习,但如果对SimpleCharStream类的内部算法不感兴趣的小伙伴只需要记得3个经常用的方法即可。

  1. BeginToken() : 这个方法调用之后,之前buffer数组中readChar()的字符都可以被删除或覆盖掉。
  2. readChar() : 从buffer数组中读取字符,如果已经到buffer[buffer.length -1 ]了,则修改bufpos = 0 ,从buffer[0]继续读取 。
  3. backup() :因为每次调用BeginToken() 方法之后,之前所有的readChar的字符是可以被清除掉的,如果多次readChar()之后,发现这些字符是不能被清除掉的,需要backup(n) 来将bufpos的位置前移,保证下次调用BeginToken()方法,只会丢弃掉bufpos之前的字符,bufpos之后的字符是不被丢弃或覆盖的。

  如果你对算法理解比较晕,其实只需要记得上面三个方法的用途即可。 因为在EL 表达式解析过程中大量用到了上面三个方法,如果不理解,你就不知道源码在干什么了?

  java.io.Reader stream 实际上就是new StringReader(expr) 。

public ELParser(java.io.Reader stream) {
    jj_input_stream = new SimpleCharStream(stream, 1, 1);
    token_source = new ELParserTokenManager(jj_input_stream);
    token = new Token();
    jj_ntk = jj_kind;
    jj_gen = 0;
    for (int i = 0; i < 36; i++) jj_la1[i] = -1;
    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
}

  ELParser构造函数初始化了jj_input_stream,而后面对字符的操作,如backup(),readChar(),BeginToken() 方法都是通过jj_input_stream来操作。

final public AstCompositeExpression CompositeExpression() throws ParseException {
    AstCompositeExpression jjtn000 = new AstCompositeExpression("CompositeExpression");
    boolean jjtc000 = true;
    jjtree.openNodeScope(jjtn000);
    try {
        label_1:
        while (true) {
            switch ((Utils.eq(jj_ntk, jj_kind)) ? jj_ntk() : jj_ntk) {
                case LITERAL_EXPRESSION:         // <LITERAL_EXPRESSION>
                case START_DYNAMIC_EXPRESSION:   // "${" 
                case START_DEFERRED_EXPRESSION:  // "#{"
                    ;
                    break;
                default:
                    jj_la1[0] = jj_gen;
                    break label_1;
            }
            switch ((Utils.eq(jj_ntk, jj_kind)) ? jj_ntk() : jj_ntk) {
                case START_DEFERRED_EXPRESSION:     // "#{"
                    DeferredExpression();
                    break;
                case START_DYNAMIC_EXPRESSION:      // "${"
                    DynamicExpression();
                    break;
                case LITERAL_EXPRESSION:            // <LITERAL_EXPRESSION>
                    LiteralExpression();
                    break;
                default:
                    jj_la1[1] = jj_gen;
                    jj_consume_token(jj_kind);
                    throw new ParseException();
            }
        }
        
        jj_consume_token(EOF);
        jjtree.closeNodeScope(jjtn000, true);
        jjtc000 = false;
        {
            if (true) return jjtn000;
        }
    } catch (Throwable jjte000) {
        if (jjtc000) {
            jjtree.clearNodeScope(jjtn000);
            jjtc000 = false;
        } else {
            jjtree.popNode();
        }
        if (jjte000 instanceof RuntimeException) {
            {
                if (true) throw (RuntimeException) jjte000;
            }
        }
        if (jjte000 instanceof ParseException) {
            {
                if (true) throw (ParseException) jjte000;
            }
        }
        {
            if (true) throw (Error) jjte000;
        }
    } finally {
        if (jjtc000) {
            jjtree.closeNodeScope(jjtn000, true);
        }
    }
    throw new Error("Missing return statement in function");
}

  首先一进来,就看到创建了一个AstCompositeExpression jjtn000 = new AstCompositeExpression(“CompositeExpression”); 复杂表达式, 接着jjtree.openNodeScope()方法。最后再调用了 jjtree.closeNodeScope(jjtn000, true);方法,而jjtree是什么类呢?竟然是JJTELParserState这个类,也相当于一个工具类。

public class JJTELParserState {
    private java.util.List<Node> nodes;
    private java.util.List<Integer> marks;
    
    private int sp; 
    private int mk;      
    private boolean node_created;

    public JJTELParserState() {
        nodes = new java.util.ArrayList<Node>();
        marks = new java.util.ArrayList<Integer>();
        sp = 0;
        mk = 0;
    }

    public boolean nodeCreated() {
        return node_created;
    }

    public void reset() {
        nodes.clear();
        marks.clear();
        sp = 0;
        mk = 0;
    }
    
    public Node rootNode() {
        return nodes.get(0);
    }
    public void pushNode(Node n) {
        nodes.add(n);
        ++sp;
    }
    
    public Node popNode() {
        if (--sp < mk) {
            mk = marks.remove(marks.size() - 1);
        }
        return nodes.remove(nodes.size() - 1);
    }

    public Node peekNode() {
        return nodes.get(nodes.size() - 1);
    }
    
    public int nodeArity() {
        return sp - mk;
    }

    public void clearNodeScope(Node n) {
        while (sp > mk) {
            popNode();
        }
        mk = marks.remove(marks.size() - 1);
    }

    public void openNodeScope(Node n) {
        marks.add(mk);
        mk = sp;
        n.jjtOpen();
    }


    public void closeNodeScope(Node n, int num) {
        mk = marks.remove(marks.size() - 1);
        // 从栈中弹出固定数量节点加入到c中成为n的子节点
        while (num-- > 0) {
            Node c = popNode();
            c.jjtSetParent(n);
            n.jjtAddChild(c, num);
        }
        n.jjtClose();
        pushNode(n);
        node_created = true;
    }


    public void closeNodeScope(Node n, boolean condition) {
        if (condition) {
            int a = nodeArity();
            mk = marks.remove(marks.size() - 1);
            while (a-- > 0) {
                Node c = popNode();
                // 从栈中弹出的节点设置其父节点为n 
                c.jjtSetParent(n);
                // 将栈中a个节点加到n中,成为n的子节点
                n.jjtAddChild(c, a);
            }
            n.jjtClose();
            // 将n 节点压入栈顶
            pushNode(n);
            node_created = true;
        } else {
            mk = marks.remove(marks.size() - 1);
            node_created = false;
        }
    }
}

  上面这个类看上去那么多,真正常用的就是openNodeScope和closeNodeScope方法。而List<Node> nodes;模拟了一个栈结构。每一次pushNode时,将节点加到nodes的尾部,每一次popNode时,将节点从nodes尾部弹出,最终我们代码中会出现很多这样的包含关系 。

public class SimpleNode1  extends SimpleNode {
    public SimpleNode1(String i) {
        super(i);
    }
}

  先创建SimpleNode1对象,这几个对象没有什么实际意义,只是继承了SimpleNode 。接下来,看一个例子。

public class TestNode {
    public static  JJTELParserState jjtree = new JJTELParserState();
    public static void main(String[] args) {
        // 1
        SimpleNode1 node1 = new SimpleNode1("1");
        try {
            jjtree.openNodeScope(node1);
            // 2.1
            SimpleNode1 node21 = new SimpleNode1("2.1");
            try {
                jjtree.openNodeScope(node21);
                // 3.1
                SimpleNode1 node211 = new SimpleNode1("3.1");
                try {
                    jjtree.openNodeScope(node211);
                }finally {
                    jjtree.closeNodeScope(node211,true);
                }
                // 3.2
                SimpleNode1 node212 = new SimpleNode1("3.2");
                try {
                    jjtree.openNodeScope(node212);
                }finally {
                    jjtree.closeNodeScope(node212,true);
                }
            }finally {
                jjtree.closeNodeScope(node21,true);
            }

            // 2.2
            SimpleNode1 node22=  new SimpleNode1("2.2");;
            try {
                jjtree.openNodeScope(node22);
            }finally {
                jjtree.closeNodeScope(node22,true);
            }

        }finally {
            jjtree.closeNodeScope(node1,true);
        }
        System.out.println(node1);

    }
}

  根据上面例子对closeNodeScope方法及openNodeScope方法分析,每次调用closeNodeScope()方法会将自己节点推入到栈顶。 同时会将之前pushNode()的节点加入到当前node的子节点中 。在上述例子中,将名称为3.1 和3.2的节点加入到2.1的子节点中,再将2.1和2.2节点加入到名称为1的节点中,因此得到如下树状结构 。
在这里插入图片描述
  我们继续看比较重要的方法,jj_ntk方法,又这个方法的名称中可以理解为next kind ,意思是下一个节点类型。

private String jj_ntk() {
    if ((jj_nt = token.next) == null)
        return (jj_ntk = (token.next = token_source.getNextToken()).kind);
    else
        return (jj_ntk = jj_nt.kind);
}

  而这些节点的总类又分为如下这些情况 <EOF>, <LITERAL_EXPRESSION>, ${, #{, , \t, \n, \r, <INTEGER_LITERAL>, <FLOATING_POINT_LITERAL>, <EXPONENT>, <STRING_LITERAL>, true, false, null, }, ., (, ), [, ], :, , >, gt, <, lt, >=, ge, <=, le, ==, eq, !=, ne, !, not, &&, and, ||, or, empty, instanceof, *, +, -, ?, /, div, %, mod, <IDENTIFIER>, <FUNCTIONSUFFIX>, #, <LETTER>, <DIGIT>, <ILLEGAL_CHARACTER>,在ELParser类中根据返回的kind类型,做相应的逻辑处理,从而封装成相应的Node节点 。 话不多说,先来研究kind是如何获取的呢? 我们进入getNextToken()方法。

public Token getNextToken() {
    Token matchedToken;
    int curPos = 0;
    EOFLoop:
    for (; ; ) {
        try {
        	// 每次进入for循环时,之前读取过的字符都是可以被清除掉的,如果不想被清除掉,则需要调用backup()方法
        	// 将读取字符的下标前移,从而保证字符不会因为buffer不够而被新读取的字符覆盖掉
            curChar = input_stream.BeginToken();
        } catch (java.io.IOException e) {
            jjmatchedKind = EOF;
            matchedToken = jjFillToken();
            return matchedToken;
        }
        switch (curLexState) {
            case 0:         // 如果当前 curLexState 为 }
                jjmatchedKind = default_jjmatchedKind;
                jjmatchedPos = 0;
                
                curPos = jjMoveStringLiteralDfa0_0();
                break;
            case 1:         // 如果当前 curLexState 为 ${ 或 #{
                try {
                    input_stream.backup(0);
                    // 如果 curChar是 水平制表符 ,换行键,回车键 ,空格时,则重新读取
                    while (curChar <= 32 && (0x100002600L & (1L << curChar)) != 0L)
                        curChar = input_stream.BeginToken();
                } catch (java.io.IOException e1) {
                    continue EOFLoop;
                }
                jjmatchedKind = default_jjmatchedKind;
                jjmatchedPos = 0;
                curPos = jjMoveStringLiteralDfa0_1();
                // 当kind类型大于56时,则当前类型为非法字符类型
                if (jjmatchedPos == 0 && ELParser.getKindPos(jjmatchedKind) > 56) {  // 如果是非法字符
                    jjmatchedKind = ELParserConstants.ILLEGAL_CHARACTER;
                }
                break;
        }
        
        // 如果读取到的内容不是默认类型,则读取成功
        if (!Utils.eq(jjmatchedKind, default_jjmatchedKind)) {
        	// 如读取到 true+空格+( , 此时jjmatchedPos = 3  ,curPos = 5  ,显然 3 + 1 < 5 ,则需要回退一个字符
        	// 此时kind类型为 true , 而 ( 虽然已经被读取,但是本次读取的是true ,并不是本次读取的内容,因此需要调用backup()方法进行回退1 个字符,当下次
        	// 调用 getNextToken方法时,首先会调用BeginToken()方法,这个方法调用,之前读取过的字符都有可能被覆盖或丢弃掉,因此这里需要调用backup()方法,
        	// 回退获取本次kind 多读取的字符
            if (jjmatchedPos + 1 < curPos)
                input_stream.backup(curPos - jjmatchedPos - 1);
            int index = ELParser.getKindPos(jjmatchedKind);
            // 如果非 空格 ,\t ,\n , \r , <EXPONENT>, #, <LETTER>, <DIGIT>  ,则退出循环
            if ((jjtoToken[index >> 6] & (1L << (index & 077))) != 0L) { 
                matchedToken = jjFillToken();
                // 如果 jjmatchedKind 为 ${ , #{ , } ,则设置当前curLexState为 jjmatchedKind
                if (jjnewLexState[ELParser.getKindPos(jjmatchedKind)] != -1)
                    curLexState = jjnewLexState[ELParser.getKindPos(jjmatchedKind)];
                return matchedToken;
            } else { // 如果是 空格 ,\t ,\n , \r , <EXPONENT>, #, <LETTER>, <DIGIT> 则继续循环
                if (jjnewLexState[ELParser.getKindPos(jjmatchedKind)] != -1)
                    curLexState = jjnewLexState[ELParser.getKindPos(jjmatchedKind)];
                continue EOFLoop;
            }
        }
        int error_line = input_stream.getEndLine();
        int error_column = input_stream.getEndColumn();
        String error_after = null;
        boolean EOFSeen = false;
        try {
            input_stream.readChar();
            input_stream.backup(1);
        } catch (java.io.IOException e1) {
            EOFSeen = true;
            error_after = curPos <= 1 ? "" : input_stream.GetImage();
            if (curChar == '\n' || curChar == '\r') {
                error_line++;
                error_column = 0;
            } else
                error_column++;
        }
        if (!EOFSeen) {
            input_stream.backup(1);
            error_after = curPos <= 1 ? "" : input_stream.GetImage();
        }
        throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
    }
}

  当第一次读取表达式时, curLexState 的初始化值为0 ,此时会进入上面case = 0 的情况,因此会走到jjMoveStringLiteralDfa0_0()方法 。

private int jjMoveStringLiteralDfa0_0() {
    switch (curChar) {
        case '#':
            return jjMoveStringLiteralDfa1_0(0x8L);
        case '$':
            return jjMoveStringLiteralDfa1_0(0x4L);
        default:
            return jjMoveNfa_0(7, 0);
    }
}

  当第一个字符为#或 $ 时,都是调用了jjMoveStringLiteralDfa1_0()方法,但唯一区别就是参数不同,如果字符为#时,则传入的参数为0x8L,如果第一个参数为 $ 时,则传入的参数为0x4L ,我们进入jjMoveStringLiteralDfa1_0方法 。

private int jjMoveStringLiteralDfa1_0(long active0) {
    try {
    	// 读取一个字符
        curChar = input_stream.readChar();
    } catch (java.io.IOException e) {
        jjStopStringLiteralDfa_0(0, active0);
        return 1;
    }
    switch (curChar) {
        case '{':
        	// 如果这个字符为{ ,并且前一个字符为 $ 时,则当前kind为START_DYNAMIC_EXPRESSION 
        	// 如果当前字符为{ ,并且前一个字符为 # 时,则当前的kind为START_DEFERRED_EXPRESSION 
            if ((active0 & 0x4L) != 0L)
                return jjStopAtPos(1, START_DYNAMIC_EXPRESSION); // ${
            else if ((active0 & 0x8L) != 0L)
                return jjStopAtPos(1, START_DEFERRED_EXPRESSION); // #{
            break;
        default:
            break;
    }
    return jjStartNfa_0(0, active0);
}

  相信分析到这里,对于kind的获取已经有点感觉了。 但是我相信大家对上面有些注释感到莫名其妙的。
在这里插入图片描述
  你怎么知道【如果 curChar是 水平制表符 ,换行键,回车键 ,空格时,则重新读取】我相信大家肯定感觉到困惑,都不知道(0x100002600L & (1L << curChar)) != 0L是什么意思,而在源码中大量的出现这种写法,我相信这也是一般开发者看到EL 表达式源码就像放弃的原因 。 看个例子就知道了。 while (curChar <= 32 && (0x100002600L & (1L << curChar)) != 0L) ,这一行代码可以换算成如下测试代码 。

public class TestEL5 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        for(int i = 0 ;i <=32 ;i ++){
            char curChar = (char)i;
            if((curChar <= 32 && (0x100002600L & (1L << curChar)) != 0L)){
                sb.append(i ).append(" ");
            }
        }
        System.out.println(sb);
    }
}

结果输出 : 9 10 13 32
在这里插入图片描述
  通过写一个测试方法,得到ASCII为9,10,13,32 ,即水平制表符,换行键,回车键,空格。这4个字符时,需要重新读取,直到不再是这4个字符,则跳出循环。
  再进入另外一行代码 。

if (jjnewLexState[ELParser.getKindPos(jjmatchedKind)] != -1)
  curLexState = jjnewLexState[ELParser.getKindPos(jjmatchedKind)];

  我们看jjnewLexState数组,这个数组的定义如下。

public static final int[] jjnewLexState = {
        -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1,
};

  在jjnewLexState数组中有一个特点,jjnewLexState[2],jjnewLexState[3],jjnewLexState[15] 对应的值分别为1,1,0,其他所有的都为-1。

public class TestEl6 {

    public static final int[] jjnewLexState = {
            -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1,
    };

    public static String[] tokenImage = {
            "<EOF>", "<LITERAL_EXPRESSION>","${","#{"," ","\\t", "\\n", "\\r", "<INTEGER_LITERAL>", "<FLOATING_POINT_LITERAL>", "<EXPONENT>", "<STRING_LITERAL>",
            "true", "false", "null", "}", ".", "(", ")", "[", "]", ":", ",", ">", "gt", "<", "lt", ">=", "ge", "<=", "le", "==", "eq", "!=", "ne", "!", "not",
            "&&", "and", "||", "or", "empty", "instanceof", "*", "+", "-", "?", "/", "div", "%", "mod", "<IDENTIFIER>", "<FUNCTIONSUFFIX>", "#",
            "<LETTER>", "<DIGIT>", "<ILLEGAL_CHARACTER>",
    };

    
    public static void main(String[] args) {
        System.out.println("jjnewLexState.length = "+jjnewLexState.length );
        System.out.println("tokenImage.length = "+tokenImage.length );
        for(int i = 0 ;i < jjnewLexState.length ;i ++){
            int flag = jjnewLexState[i];
            if(flag >= 0 ){
                System.out.println(flag + " " + tokenImage[i]);
            }
        }
    }
}

结果输出 :
jjnewLexState.length =57
tokenImage.length =57
1 ${
1 #{
0 }

  从测试结果来看, curLexState可能是0和1的情况,只可能是} 和 ${,#{ ,因此就影响到curLexState的switch判断。
在这里插入图片描述

  当进入到getNextToken()的case 1 : 的情况时,有一个重要方法jjMoveStringLiteralDfa0_1() ,这个方法会返回当前读取字符的位置,但这个方法内部会修改全局变量jjmatchedPos的值,如 “true (” 字符串,jjMoveStringLiteralDfa0_1()方法会返回curPos的值为5 , jjmatchedPos的值为3,因为true只占4个字符。在生成Token对象时会调用jjFillToken()方法生成token 。

protected Token jjFillToken() {
    final Token t;
    final String curTokenImage;
    final int beginLine;
    final int endLine;
    final int beginColumn;
    final int endColumn;
    String im = jjstrLiteralImages[ELParser.getKindPos(jjmatchedKind)];
    curTokenImage = (im == null) ? input_stream.GetImage() : im;
    beginLine = input_stream.getBeginLine();
    beginColumn = input_stream.getBeginColumn();
    endLine = input_stream.getEndLine();
    endColumn = input_stream.getEndColumn();
    
    t =new Token(jjmatchedKind, curTokenImage);

    t.beginLine = beginLine;
    t.endLine = endLine;
    t.beginColumn = beginColumn;
    t.endColumn = endColumn;
    
    return t;
}

int curLexState = 0;
int defaultLexState = 0;
int jjnewStateCnt;
int jjround;
int jjmatchedPos;
String jjmatchedKind;

public Token(String kind, String image){
  this.kind = kind;
  this.image = image;
}

  在生成token时需要注意,如果从jjstrLiteralImages数组能获取到值且不为空,则不需要调用input_stream.GetImage()方法获取image,否则需要调用GetImage()方法获取字符串数据 ,那当前读取到的kind是什么类型时,不需要调用GetImage()方法呢?我们来看一个测试方法 。

public class TestEl7 {
    public static final String[] jjstrLiteralImages = {
            "", null, "\44\173", "\43\173", null, null, null, null, null, null, null, null,
            "\164\162\165\145", "\146\141\154\163\145", "\156\165\154\154", "\175", "\56", "\50", "\51",
            "\133", "\135", "\72", "\54", "\76", "\147\164", "\74", "\154\164", "\76\75",
            "\147\145", "\74\75", "\154\145", "\75\75", "\145\161", "\41\75", "\156\145", "\41",
            "\156\157\164", "\46\46", "\141\156\144", "\174\174", "\157\162", "\145\155\160\164\171",
            "\151\156\163\164\141\156\143\145\157\146", "\52", "\53", "\55", "\77", "\57", "\144\151\166", "\45", "\155\157\144", null,
            null, null, null, null, null,};


    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        for (String l : jjstrLiteralImages) {
            if (l != null) {
                sb.append(l).append(" ");
            }
        }
        System.out.println(sb);
    }

}

执行结果:
${    #{    true    false    null    }    .    (    )    [    ]    :    ,    >    gt    <    lt    >=    ge    <=    le    ==    eq    !=    ne    !    not    &&    and    ||    or    empty    instanceof    *    +    -    ?    /    div    %    mod    

  当kind类型为上述时,不会调用input_stream.GetImage() 获取当前读取的字符 。

private int jjMoveStringLiteralDfa0_1() {
    switch (curChar) {
        case '!':
            jjmatchedKind = NOT0;       // !
            return jjMoveStringLiteralDfa1_1(0x200000000L);
        case '%':
            return jjStopAtPos(0, MOD0);
        case '&':
            return jjMoveStringLiteralDfa1_1(0x2000000000L);
        case '(':
            return jjStopAtPos(0, LPAREN);
        case ')':
            return jjStopAtPos(0, RPAREN);
        case '*':
            return jjStopAtPos(0, MULT);
        case '+':
            return jjStopAtPos(0, PLUS);
        case ',':
            return jjStopAtPos(0, COMMA);
        case '-':
            return jjStopAtPos(0, MINUS);
        case '.':
            return jjStartNfaWithStates_1(0, DOT, 1);
        case '/':
            return jjStopAtPos(0, DIV0);
        case ':':
            return jjStopAtPos(0, COLON);
        case '<':
            jjmatchedKind = LT0;
            return jjMoveStringLiteralDfa1_1(0x20000000L);
        case '=':
            return jjMoveStringLiteralDfa1_1(0x80000000L);
        case '>':
            jjmatchedKind = GT0;
            return jjMoveStringLiteralDfa1_1(0x8000000L);
        case '?':
            return jjStopAtPos(0, QUESTIONMARK); // ?
        case '[':
            return jjStopAtPos(0, LBRACK); // [
        case ']':
            return jjStopAtPos(0, RBRACK); // ]
        case 'a':
            return jjMoveStringLiteralDfa1_1(0x4000000000L);
        case 'd':
            return jjMoveStringLiteralDfa1_1(0x1000000000000L);
        case 'e':
            return jjMoveStringLiteralDfa1_1(0x20100000000L);
        case 'f':
            return jjMoveStringLiteralDfa1_1(0x2000L);
        case 'g':
            return jjMoveStringLiteralDfa1_1(0x11000000L);
        case 'i':
            return jjMoveStringLiteralDfa1_1(0x40000000000L);
        case 'l':
            return jjMoveStringLiteralDfa1_1(0x44000000L);
        case 'm':
            return jjMoveStringLiteralDfa1_1(0x4000000000000L);
        case 'n':
            return jjMoveStringLiteralDfa1_1(0x1400004000L);
        case 'o':
            return jjMoveStringLiteralDfa1_1(0x10000000000L);
        case 't':
            return jjMoveStringLiteralDfa1_1(0x1000L);
        case '|':
            return jjMoveStringLiteralDfa1_1(0x8000000000L);
        case '}':
            return jjStopAtPos(0, END_EXPRESSION);  // }
        default:
            return jjMoveNfa_1(0, 0);
    }
}


private int jjStopAtPos(int pos, String kind) {
    jjmatchedKind = kind;
    jjmatchedPos = pos;
    return pos + 1;
}

  如果当前字符是 % , 勿庸置疑,肯定当前读取到字符的kind为 取余(% ), 但如果当前读取到的字符为 & ,不知道下一个字符是什么,因此需要调用 jjMoveStringLiteralDfa1_1 方法继续判断。

private int jjMoveStringLiteralDfa1_1(long active0) {
    try {
    	// 再次读取一个字符 
        curChar = input_stream.readChar();
    } catch (java.io.IOException e) {
        jjStopStringLiteralDfa_1(0, active0);
        return 1;
    }
    switch (curChar) {
        case '&':
            if ((active0 & 0x2000000000L) != 0L)
                return jjStopAtPos(1, AND0);               // &&
            break;
        case '=':
            if ((active0 & 0x8000000L) != 0L)                   // >=
                return jjStopAtPos(1, GE0);
            else if ((active0 & 0x20000000L) != 0L)             // <=
                return jjStopAtPos(1, LE0);
            else if ((active0 & 0x80000000L) != 0L)             // ==
                return jjStopAtPos(1, EQ0);
            else if ((active0 & 0x200000000L) != 0L)            // !
                return jjStopAtPos(1, NE0);
            break;
        case 'a':
            return jjMoveStringLiteralDfa2_1(active0, 0x2000L);
        case 'e':
            if ((active0 & 0x10000000L) != 0L)
                return jjStartNfaWithStates_1(1, GE1, 30);  // ge
            else if ((active0 & 0x40000000L) != 0L)
                return jjStartNfaWithStates_1(1, LE1, 30);  // le
            else if ((active0 & 0x400000000L) != 0L)        //
                return jjStartNfaWithStates_1(1, NE1, 30);  // ne
            break;
        case 'i':
            return jjMoveStringLiteralDfa2_1(active0, 0x1000000000000L);
        case 'm':
            return jjMoveStringLiteralDfa2_1(active0, 0x20000000000L);
        case 'n':
            return jjMoveStringLiteralDfa2_1(active0, 0x44000000000L);
        case 'o':
            return jjMoveStringLiteralDfa2_1(active0, 0x4001000000000L);
        case 'q':
            if ((active0 & 0x100000000L) != 0L)
                return jjStartNfaWithStates_1(1, EQ1, 30);      // eq
            break;
        case 'r':
            if ((active0 & 0x10000000000L) != 0L)
                return jjStartNfaWithStates_1(1, OR1, 30);      // or
            return jjMoveStringLiteralDfa2_1(active0, 0x1000L);
        case 't':
            if ((active0 & 0x1000000L) != 0L)
                return jjStartNfaWithStates_1(1, GT1, 30);      // gt
            else if ((active0 & 0x4000000L) != 0L)
                return jjStartNfaWithStates_1(1, LT1, 30);      // lt
            break;
        case 'u':
            return jjMoveStringLiteralDfa2_1(active0, 0x4000L);
        case '|':
            if ((active0 & 0x8000000000L) != 0L)
                return jjStopAtPos(1, OR0);                            // ||
            break;
        default:
            break;
    }
    return jjStartNfa_1(0, active0);
}

  第一次读取到字符&,会调用 jjMoveStringLiteralDfa1_1(0x2000000000L)方法,而在jjMoveStringLiteralDfa1_1方法中加粗代码,如果传入过来的值active0 & 0x2000000000L 不等于0,则表示本次读取到两个字符 && ,如果前一个字符不是& ,肯定 传过来的 active0 & 0x2000000000L == 0 ,因此读取到的字符kind类型不可能是&& ,需要调用jjStartNfa_1方法继续判断,大家发现EL表达式解析规率没有? 通过 & 后的值是否为0来判断前一个字符是什么,与当前读取到的字符组合,从而判断是不是关键字的方式,来确定是否继续读取字符,当然这样就够了吗? 我们再来看另外一个技术难点 ,进入 jjMoveStringLiteralDfa2_1方法 。

private int jjMoveStringLiteralDfa2_1(long old0, long active0) {
    // 在 ! & < = > a d e f g i l m n o t |
    // 和 a i m n o r u 的排列组合中
    // 除 an di em fa in mo no nu tr or  组合外, 其他的都会进入下面方法  如 aa da ea 等
    if (((active0 &= old0)) == 0L)
        return jjStartNfa_1(0, old0);
    try {
        // 如果排列组合为除 an di em fa in mo no nu tr 则会进入下面的代码
        // 因为上面的这些字符可以组成 and div empty false instanceof mod not null true
        curChar = input_stream.readChar();
    } catch (java.io.IOException e) {
        jjStopStringLiteralDfa_1(1, active0);
        return 2;
    }
    switch (curChar) {
        case 'd':
            if ((active0 & 0x4000000000L) != 0L)
                return jjStartNfaWithStates_1(2, AND1, 30);     // and
            else if ((active0 & 0x4000000000000L) != 0L)
                return jjStartNfaWithStates_1(2, MOD1, 30);     // mod
            break;
        case 'l':                           // 如果第3个字符为l ,则可能为null 关键字
            return jjMoveStringLiteralDfa3_1(active0, 0x6000L);
        case 'p':                           // 如果第3个字符为p ,则可能为empty 关键字
            return jjMoveStringLiteralDfa3_1(active0, 0x20000000000L);
        case 's':                           // 如果第三个字符为s,则可能是 instanceof 关键字
            return jjMoveStringLiteralDfa3_1(active0, 0x40000000000L);
        case 't':
            if ((active0 & 0x1000000000L) != 0L)
                return jjStartNfaWithStates_1(2, NOT1, 30); // not
            break;
        case 'u':
            return jjMoveStringLiteralDfa3_1(active0, 0x1000L);
        case 'v':
            if ((active0 & 0x1000000000000L) != 0L)
                return jjStartNfaWithStates_1(2, DIV1, 30);     // div
            break;
        default:
            break;
    }
    return jjStartNfa_1(1, active0);
}

  上面加粗代码不讲武徳,纯心想让我们看不懂代码, (((active0 &= old0)) == 0L) 这个判断什么意思嘛?一般人跟进到这里就晕了, 本来用 & xxx 是否等于0 的写代码方式就让人费解了,现在还用这种写法太难了,但代码还是要看懂的,怎么办呢?还是用穷举法,我们写一个例子。

public class ELTest8 {

    public static void main(String[] args) {
        long[] a1 = new long[]{0x200000000L, 0x2000000000L, 0x20000000L, 0x80000000L, 0x8000000L, 0x4000000000L, 0x1000000000000L,
                0x20100000000L, 0x2000L, 0x11000000L, 0x40000000000L, 0x44000000L, 0x4000000000000L, 0x1400004000L, 0x10000000000L,
                0x1000L, 0x8000000000L};
        char ac[] = new char[]{'!', '&', '<', '=', '>', 'a', 'd', 'e', 'f', 'g', 'i', 'l', 'm', 'n', 'o', 't', '|'};
        StringBuilder sb = new StringBuilder();
        for (char a : ac) {
            sb.append(a).append(" ");
        }

        long a2[] = new long[]{0x2000L, 0x1000000000000L, 0x20000000000L, 0x44000000000L, 0x4001000000000L, 0x1000L, 0x4000L};
        char ad[] = new char[]{'a', 'i', 'm', 'n', 'o', 'r', 'u'};
        StringBuilder sb2 = new StringBuilder();

        for (char b : ad) {
            sb2.append(b).append(" ");
        }


        StringBuilder sb3 = new StringBuilder();
        for (int i = 0; i < a1.length; i++) {
            for (int j = 0; j < ad.length; j++) {
                jjMoveStringLiteralDfa2_1(a1[i], a2[j], ac[i], ad[j], sb3);
            }
        }
        System.out.println(sb3);
    }
    
    private static int jjMoveStringLiteralDfa2_1(long old0, long active0, char c, char d, StringBuilder sb3) {
        if (((active0 &= old0)) == 0L) {
            return -2;//jjStartNfa_1(0, old0);
        }
        sb3.append(c).append(d).append(" ");
        return -1;
    }
}

  我们将字符和对应的active0封装成数组,模拟调用jjMoveStringLiteralDfa2_1方法,将第一个字符和第二个字符没有进入
if (((active0 &= old0)) == 0L) 条件判断打印出来,看哪些字符组合不会进入if (((active0 &= old0)) == 0L)条件判断 。上面方法执行结果如下 。
  an di em fa in mo no nu tr
  发现没有当两个字符的组合为上面打印结果时,刚好可以组成关键字 and div empty false instanceof mod not null true,大家发现规率没有。同理jjMoveStringLiteralDfa3_1,jjMoveStringLiteralDfa4_1,jjMoveStringLiteralDfa5_1,jjMoveStringLiteralDfa6_1,jjMoveStringLiteralDfa7_1,jjMoveStringLiteralDfa8_1,jjMoveStringLiteralDfa9_1方法可以用相同的测试方法推导出来,我们来看另外一个技术难点的方法 。

private int jjMoveNfa_1(int startState, int curPos) {
    int startsAt = 0;
    jjnewStateCnt = 30;
    int i = 1;
    jjstateSet[0] = startState;
    String kind = "0x7fffffff";
    for (; ; ) {
        if (++jjround == 0x7fffffff)
            ReInitRounds();
        if (curChar < 64) {
            long l = 1L << curChar;
            do {
                switch (jjstateSet[--i]) {
                    case 0:
                        if ((0x3ff000000000000L & l) != 0L) {            // 如果curChar 为 0 1 2 3 4 5 6 7 8 9
                            if (ELParser.getKindPos(kind) > 8)
                                kind = INTEGER_LITERAL;
                            jjCheckNAddStates(18, 22);
                        } else if ((0x1800000000L & l) != 0L) {           // 如果curChar 为 # $
                            if (ELParser.getKindPos(kind) > 51)
                                kind = IDENTIFIER;
                            jjCheckNAddTwoStates(28, 29);
                        } else if (curChar == '\'')
                            jjCheckNAddStates(23, 25);
                        else if (curChar == '\"')
                            jjCheckNAddStates(26, 28);
                        else if (curChar == '.')
                            jjCheckNAdd(1);
                        break;
                    case 30:
                        if ((0x3ff001000000000L & l) != 0L) {                // 如果curChar 为 $ 0 1 2 3 4 5 6 7 8 9
                            if (ELParser.getKindPos(kind) > 52)
                                kind = FUNCTIONSUFFIX;
                            jjCheckNAdd(29);
                        }
                        if ((0x3ff001000000000L & l) != 0L) {               // 如果curChar 为 $ 0 1 2 3 4 5 6 7 8 9
                            if (ELParser.getKindPos(kind) > 51)
                                kind = IDENTIFIER;
                            jjCheckNAdd(28);
                        }
                        break;
                    case 1:
                        if ((0x3ff000000000000L & l) == 0L)
                            break;
                        if (ELParser.getKindPos(kind) > 9)                  // 如果curChar 为 0 1 2 3 4 5 6 7 8 9
                            kind = FLOATING_POINT_LITERAL;
                        jjCheckNAddTwoStates(1, 2);
                        break;
                    case 3:
                        if ((0x280000000000L & l) != 0L)                    // 如果curChar 为 + -
                            jjCheckNAdd(4);
                        break;
                    case 4:
                        if ((0x3ff000000000000L & l) == 0L)
                            break;
                        if (ELParser.getKindPos(kind) > 9)                  // 如果curChar 为 0 1 2 3 4 5 6 7 8 9
                            kind = FLOATING_POINT_LITERAL;
                        jjCheckNAdd(4);
                        break;
                    case 5:
                        if (curChar == 34)                                  // 双引号
                            jjCheckNAddStates(26, 28);
                        break;
                    case 6:
                        if ((0xfffffffbffffffffL & l) != 0L)                // 如果curChar 非 "
                            jjCheckNAddStates(26, 28);
                        break;
                    case 8:
                        if ((0x8400000000L & l) != 0L)                      // 如果curChar 为 " '
                            jjCheckNAddStates(26, 28);
                        break;
                    case 9:
                        if (curChar == 34 && ELParser.getKindPos(kind) > 11)    // 如果curChar 为双引号
                            kind = STRING_LITERAL;
                        break;
                    case 10:
                        if (curChar == 39)                                     // 单引号
                            jjCheckNAddStates(23, 25);
                        break;
                    case 11:
                        // 如果curChar 非 '
                        if ((0xffffff7fffffffffL & l) != 0L)
                            jjCheckNAddStates(23, 25);
                        break;
                    case 13:
                        if ((0x8400000000L & l) != 0L)                          // 如果curChar 为 " '
                            jjCheckNAddStates(23, 25);
                        break;
                    case 14:
                        if (curChar == 39 && ELParser.getKindPos(kind) > 11)    // 单引号
                            kind = STRING_LITERAL;
                        break;
                    case 15:
                        // 如果curChar 非 0 1 2 3 4 5 6 7 8 9
                        if ((0x3ff000000000000L & l) == 0L)
                            break;
                        if (ELParser.getKindPos(kind) > 8)
                            kind = INTEGER_LITERAL;
                        jjCheckNAddStates(18, 22);
                        break;
                    case 16:
                        if ((0x3ff000000000000L & l) == 0L)            // 如果curChar 非 0 1 2 3 4 5 6 7 8 9
                            break;
                        if (ELParser.getKindPos(kind) > 8)
                            kind = INTEGER_LITERAL;
                        jjCheckNAdd(16);
                        break;
                    case 17:
                        if ((0x3ff000000000000L & l) != 0L)             // 如果curChar 为 0 1 2 3 4 5 6 7 8 9
                            jjCheckNAddTwoStates(17, 18);
                        break;
                    case 18:
                        if (curChar != 46)                              // . 小数点
                            break;
                        if (ELParser.getKindPos(kind) > 9)
                            kind = FLOATING_POINT_LITERAL;
                        jjCheckNAddTwoStates(19, 20);
                        break;
                    case 19:
                        if ((0x3ff000000000000L & l) == 0L)                  // 如果curChar 非 0 1 2 3 4 5 6 7 8 9
                            break;
                        if (ELParser.getKindPos(kind) > 9)
                            kind = FLOATING_POINT_LITERAL;
                        jjCheckNAddTwoStates(19, 20);
                        break;
                    case 21:
                        if ((0x280000000000L & l) != 0L)                // 如果curChar 为 + -
                            jjCheckNAdd(22);
                        break;
                    case 22:
                        if ((0x3ff000000000000L & l) == 0L)
                            break;
                        if (ELParser.getKindPos(kind) > 9)              // 如果curChar 为 0 1 2 3 4 5 6 7 8 9
                            kind = FLOATING_POINT_LITERAL;
                        jjCheckNAdd(22);
                        break;
                    case 23:
                        if ((0x3ff000000000000L & l) != 0L)             // 如果curChar 为 0 1 2 3 4 5 6 7 8 9
                            jjCheckNAddTwoStates(23, 24);
                        break;
                    case 25:
                        if ((0x280000000000L & l) != 0L)                // 如果curChar 为 + -
                            jjCheckNAdd(26);
                        break;
                    case 26:
                        if ((0x3ff000000000000L & l) == 0L)
                            break;
                        if (ELParser.getKindPos(kind) > 9)              // 如果curChar 为 0 1 2 3 4 5 6 7 8 9
                            kind = FLOATING_POINT_LITERAL;
                        jjCheckNAdd(26);
                        break;
                    case 27:
                        if ((0x1800000000L & l) == 0L)
                            break;
                        if (ELParser.getKindPos(kind) > 51)             // 如果curChar 为 # $
                            kind = IDENTIFIER;
                        jjCheckNAddTwoStates(28, 29);
                        break;
                    case 28:
                        //   ! " # % & ' ( ) * + , - . / : ; < = > ?
                        if ((0x3ff001000000000L & l) == 0L)
                            break;
                        if (ELParser.getKindPos(kind) > 51)             // 如果curChar 为 $ 0 1 2 3 4 5 6 7 8 9
                            kind = IDENTIFIER;
                        jjCheckNAdd(28);
                        break;
                    case 29:
                        //   ! " # % & ' ( ) * + , - . / : ; < = > ?
                        if ((0x3ff001000000000L & l) == 0L)
                            break;
                        if (ELParser.getKindPos(kind) > 52)             // 如果curChar 为 $ 0 1 2 3 4 5 6 7 8 9
                            kind = FUNCTIONSUFFIX;
                        jjCheckNAdd(29);
                        break;
                    default:
                        break;
                }
            } while (i != startsAt);
        } else if (curChar < 128) {
            long l = 1L << (curChar & 077);
            do {
                switch (jjstateSet[--i]) {
                    case 0:
                        if ((0x7fffffe87fffffeL & l) == 0L)             // 如果curChar 为 ; < = > ? @ [ \ ] ^ ` { | } ~ 删除键
                            break;
                        if (ELParser.getKindPos(kind) > 51)
                            kind = IDENTIFIER;
                        jjCheckNAddTwoStates(28, 29);
                        break;
                    case 30:
                        // 如果curChar 非 ; < = > ? @ [ \ ] ^ ` { | } ~ 删除键
                        if ((0x7fffffe87fffffeL & l) != 0L) {
                            if (ELParser.getKindPos(kind) > 52)
                                kind = FUNCTIONSUFFIX;
                            jjCheckNAdd(29);
                        }
                        // 如果curChar 非 ; < = > ? @ [ \ ] ^ ` { | } ~ 删除键
                        if ((0x7fffffe87fffffeL & l) != 0L) {
                            if (ELParser.getKindPos(kind) > 51)
                                kind = IDENTIFIER;
                            jjCheckNAdd(28);
                        }
                        break;
                    case 2:
                        // 如果curChar 为 % E e
                        if ((0x2000000020L & l) != 0L)
                            jjAddStates(29, 30);
                        break;
                    case 6:
                       // 如果curChar 为 ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F
                        // G H I J K L M N O P Q R S T U V W X Y Z [ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ 
                        if ((0xffffffffefffffffL & l) != 0L)
                            jjCheckNAddStates(26, 28);
                        break;
                    case 7:
                        if (curChar == 92)      // 反斜杠
                            jjstateSet[jjnewStateCnt++] = 8;
                        break;
                    case 8:
                        if (curChar == 92)      // 反斜杠
                            jjCheckNAddStates(26, 28);
                        break;
                    case 11:
                        // 如果curChar 为 ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F
                        // G H I J K L M N O P Q R S T U V W X Y Z [ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ 
                        if ((0xffffffffefffffffL & l) != 0L)
                            jjCheckNAddStates(23, 25);
                        break;
                    case 12:
                        if (curChar == 92)          //  反斜杠
                            jjstateSet[jjnewStateCnt++] = 13;
                        break;
                    case 13:
                        if (curChar == 92)          // 反斜杠
                            jjCheckNAddStates(23, 25);
                        break;
                    case 20:
                        // 如果curChar 为 % E e
                        if ((0x2000000020L & l) != 0L)
                            jjAddStates(31, 32);
                        break;
                    case 24:
                        // 如果curChar 为 % E e
                        if ((0x2000000020L & l) != 0L)
                            jjAddStates(33, 34);
                        break;
                    case 28:
                        // 如果curChar 为 ; < = > ? @ [ \ ] ^ ` { | } ~ 
                        if ((0x7fffffe87fffffeL & l) == 0L)
                            break;
                        if (ELParser.getKindPos(kind) > 51)
                            kind = IDENTIFIER;
                        jjCheckNAdd(28);
                        break;
                    case 29:
                        // 如果curChar 为 ; < = > ? @ [ \ ] ^ ` { | } ~ 
                        if ((0x7fffffe87fffffeL & l) == 0L)
                            break;
                        if (ELParser.getKindPos(kind) > 52)
                            kind = FUNCTIONSUFFIX;
                        jjCheckNAdd(29);
                        break;
                    default:
                        break;
                }
            } while (i != startsAt);
        } else {
            int hiByte = (int) (curChar >> 8);
            int i1 = hiByte >> 6;
            long l1 = 1L << (hiByte & 077);
            int i2 = (curChar & 0xff) >> 6;
            long l2 = 1L << (curChar & 077);
            do {
                switch (jjstateSet[--i]) {
                    case 0:
                        if (!jjCanMove_1(hiByte, i1, i2, l1, l2))   // 如果ASCII 为  128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 215 247
                            break;
                        if (ELParser.getKindPos(kind) > 51)
                            kind = IDENTIFIER;
                        jjCheckNAddTwoStates(28, 29);
                        break;
                    case 30:
                        if (jjCanMove_1(hiByte, i1, i2, l1, l2)) {   // 如果ASCII为 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 248 249 250 251 252 253 254
                            if (ELParser.getKindPos(kind) > 51)
                                kind = IDENTIFIER;
                            jjCheckNAdd(28);
                        }
                        if (jjCanMove_1(hiByte, i1, i2, l1, l2)) {   // 如果ASCII为 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 248 249 250 251 252 253 254
                            if (ELParser.getKindPos(kind) > 52)
                                kind = FUNCTIONSUFFIX;
                            jjCheckNAdd(29);
                        }
                        break;
                    case 6:
                        if (jjCanMove_0(hiByte, i1, i2, l1, l2))   // 如果ASCII 为 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
                            jjAddStates(26, 28);
                        break;
                    case 11:
                        if (jjCanMove_0(hiByte, i1, i2, l1, l2))    // 如果ASCII 为 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
                            jjAddStates(23, 25);
                        break;
                    case 28:
                        if (!jjCanMove_1(hiByte, i1, i2, l1, l2))   // 如果ASCII 为 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 215 247
                            break;
                        if (ELParser.getKindPos(kind) > 51)
                            kind = IDENTIFIER;
                        jjCheckNAdd(28);
                        break;
                    case 29:
                        if (!jjCanMove_1(hiByte, i1, i2, l1, l2))   // 如果ASSCII 为 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 215 247
                            break;
                        if (ELParser.getKindPos(kind) > 52)
                            kind = FUNCTIONSUFFIX;
                        jjCheckNAdd(29);
                        break;
                    default:
                        break;
                }
            } while (i != startsAt);
        }
        if (!Utils.eq(kind, "0x7fffffff")) {
            jjmatchedKind = kind;
            jjmatchedPos = curPos;
            kind = "0x7fffffff";
        }
        ++curPos;
        i = jjnewStateCnt;
        jjnewStateCnt = startsAt;
        startsAt = 30 - startsAt;
        //if ((i = jjnewStateCnt) == (startsAt = 30 - (jjnewStateCnt = startsAt)))
        if (i == startsAt)
            return curPos;
        try {
            curChar = input_stream.readChar();
        } catch (java.io.IOException e) {
            return curPos;
        }
    }
}

  大家可能心里会想,你写了那么多的注释,到底对不对哦,如果误人子弟怎么办,你又知道的呢? 还是之前的老办法,穷举法。 如

private int jjMoveNfa_0(int startState, int curPos) {
    int startsAt = 0;
    jjnewStateCnt = 8;
    int i = 1;
    jjstateSet[0] = startState;
    String kind = "0x7fffffff";
    for (; ; ) {
        if (++jjround == 0x7fffffff)
            ReInitRounds();
        if (curChar < 64) {
            long l = 1L << curChar;
            do {
                switch (jjstateSet[--i]) {
                    case 7:
                        if ((0xffffffe7ffffffffL & l) != 0L) {          // 如果 0 ~ 63 , curChar 非 # $
                            if (ELParser.getKindPos(kind) > 1)
                                kind = ELParserConstants.LITERAL_EXPRESSION;
                            jjCheckNAddStates(0, 4);
...

  我怎么知道 0 ~ 63 , curChar 非 # $ 会进入 if ((0xffffffe7ffffffffL & l) != 0L) 判断呢? 用穷举法来看一个例子。
在这里插入图片描述
  其他的注释有相同的方法测试即可。

private int jjMoveNfa_1(int startState, int curPos) {
    int startsAt = 0;
    jjnewStateCnt = 30;
    int i = 1;
    jjstateSet[0] = startState;
    String kind = "0x7fffffff";
    for (; ; ) {
        if (++jjround == 0x7fffffff)
            ReInitRounds();
        if (curChar < 64) {
            long l = 1L << curChar;
            do {
                switch (jjstateSet[--i]) {
                    case 0:
                        if ((0x3ff000000000000L & l) != 0L) {            // 如果curChar 为 0 1 2 3 4 5 6 7 8 9
                            if (ELParser.getKindPos(kind) > 8)
                                kind = INTEGER_LITERAL;
                            jjCheckNAddStates(18, 22);
           ... 
        if (!Utils.eq(kind, "0x7fffffff")) {
            jjmatchedKind = kind;
            jjmatchedPos = curPos;
            kind = "0x7fffffff";
        }
        ++curPos;
        
        i = jjnewStateCnt;
        jjnewStateCnt = startsAt;
        startsAt = 30 - startsAt;
        
        if (i == startsAt)
            return curPos;
        try {
            curChar = input_stream.readChar();
        } catch (java.io.IOException e) {
            return curPos;
        }
    }
}

private void jjCheckNAdd(int state) {
    if (jjrounds[state] != jjround) {
        jjstateSet[jjnewStateCnt++] = state;
        jjrounds[state] = jjround;
    }
}

private void jjCheckNAddStates(int start, int end) {
    do {
        jjCheckNAdd(jjnextStates[start]);
    } while (start++ != end);
}

static final int[] jjnextStates = {
        0, 1, 3, 4, 2, 0, 1, 4, 2, 0, 1, 4, 5, 2, 0, 1,
        2, 6, 16, 17, 18, 23, 24, 11, 12, 14, 6, 7, 9, 3, 4, 21,
        22, 25, 26,
};

  jjnextStates是什么变量 ,用来做什么的呢?就本例而言, jjCheckNAddStates(18, 22)方法,将jjnextStates下标从18 ~ 22加入到jjstateSet中
在这里插入图片描述

  jjstateSet[30]=16 , jjstateSet[31]= 17 , jjstateSet[32]=18,jjstateSet[33] = 23, jjstateSet[34] = 24,如果下一个字符ASCII码小于64时,因为上面加粗代码
  i = jjnewStateCnt;
  jjnewStateCnt = startsAt;
  startsAt = 30 - startsAt;
  此时 i = 35 ,而每次调用 switch (jjstateSet[–i])时,也就是先判断
switch ( switch (jjstateSet[–i])) {
  case 24 :
    …
    break;
  case 23:
    …
  break;
  case 18 :
    …
    break;
  case 17 :
    …
    break;
  case 16:
    …
    break;
  因此在原来代码中,就变成了
在这里插入图片描述
  这样判断的目的就是得到当前读取到的字符的类型是什么?如下图所示,读取到的字符可能是IDENTIFIER,也可能是函数后缀。
在这里插入图片描述
  在调用过程中,jjnewStateCnt 没有发生过变化,则退出do while 循环,得到最终读取到字符的kind类型。

  程序读取到这里,对读取字符所有技术难点都分析了,下一步来分析ELParser的解析语法 。

  在分析ELParser类之前,先来看一个例子。

public class TestEl {
    public static void main(String[] args) throws Exception {
        String expr = "${ 1 + 2 > 0 }";
        Node n =  (new ELParser(new StringReader(expr))).CompositeExpression();
        System.out.println(n);
    }
}

  我们在Value()方法调用处打一个断点 ,如下图所示 。

在这里插入图片描述
  从方法的调用链中,DynamicExpression() -> Expression() -> Choice() -> Or() -> And() -> Equality() -> Compare() -> Math() -> Multiplication() -> Unary()方法。来看看方法的用途。

  1. Choice() : a ? b : c ; 三目运算符处理方法。
  2. Or() : or 或 || 关键字处理方法
  3. And() :and 或 && 关键字处理方法
  4. Equality() :== eq != ne 比较运算符处理方法
  5. Compare() :> ,< ,>= ,<= 比较运行符处理方法 ,
  6. Math() :+ , - 加减算术运算符处理方法
  7. Multiplication() : * / % 乘除取余运算符处理方法
  8. Unary() :! ,not , empty 一元运算符处理方法

  为什么方法的调用关系是这样的呢?大家有没有觉得很眼熟,这不就是我们小学就知道的运算符的优先级不? 先乘除后加减,如
a + b * c > d ,肯定先计算b * c 的值,再加a 的值 再和d 的值做比较,这就是上述方法调用链这样组装的原因,再来看下面加粗代码 。

final public void Unary() throws ParseException {
    switch ((Utils.eq(jj_ntk, jj_kind)) ? jj_ntk() : jj_ntk) {
        case MINUS:                 // -
            jj_consume_token(MINUS);
    ....        

  我们在代码中经常会遇到jj_consume_token()方法,这个方法的用途是什么呢?从方法的名字上来理解就是消费掉token 。

private Token jj_consume_token(String kind) throws ParseException {
    Token oldToken;
    if ((oldToken = token).next != null)
        token = token.next;
    else
    	//
        token = token.next = token_source.getNextToken();
    jj_ntk = jj_kind;
    
    if (Utils.eq(token.kind, kind)) {
        jj_gen++;
        if (++jj_gc > 100) {
            jj_gc = 0;
            for (int i = 0; i < jj_2_rtns.length; i++) {
                JJCalls c = jj_2_rtns[i];
                while (c != null) {
                    if (c.gen < jj_gen) c.first = null;
                    c = c.next;
                }
            }
        }
        return token;
    }
    // 如果此时的token不是我们期望的token ,则抛出异常
    token = oldToken;
    jj_kind = kind;
    String message = token.beginLine + "行," + token.beginColumn + "列,代码书写有误";
    throw generateParseException();
}

  这个方法有两个用途,第一个就是像上例中的一个,为了避免影响到之后字符的读取,需要将token消费掉,如此时已经读取到MINUS,为了不影响后面字符的读取,需要将此时token.kind为MINUS消费掉。

final public void Unary() throws ParseException {
    switch ((Utils.eq(jj_ntk, jj_kind)) ? jj_ntk() : jj_ntk) {
        case MINUS:                 // -
            jj_consume_token(MINUS);
    ....        

  还有一种情况如下图所示 。

在这里插入图片描述
  既然已经知道是一个三目运算符 a ? b : c ; 当Choice()方法调用之后,肯定是 : 分号,如果不是分号,就抛出异常,所以jj_consume_token()方法不仅起到消费的作用,还起到一个较验的作用。

  此时此刻已经分析完所有EL表达式的技术难点,所有的EL表达式被解析成一棵语法树,自己感兴趣可以自己打断点调试一下,这里就不带着大家去打断点了,下面看几个例子,表达式和最终生成的语法树。

  1. ${ 1 + 2 > 0 }
    在这里插入图片描述
  2. ns:function(1,2)
    在这里插入图片描述
    3.${pageContext.request.contextPath }
    在这里插入图片描述
      关于表达式的解析已经告一段落,下面我们来分析EL表达式在jsp中的应用,以及值是如何获取的。

  我们以EL表达式详解 这篇博客为基础进行EL 表达式源码解析 。

1、如果没有使用EL的内置对象,则查找数据顺序是依次按照由小到大范围从四大域中查找指定名称的属性值,先来看博客中的一个例子。

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
</head>
<body>
<%
    pageContext.setAttribute("name", "linjie");
    request.setAttribute("name", "lucy");
    session.setAttribute("name", "king");
    application.setAttribute("name", "bilibili");
%>
name=${name }
</body>
</html>

执行结果
在这里插入图片描述
  我想知道的是,为什么结果输出是name=linjie。
  我们看最终生成的index_jsp.java文件,大家肯定会问,index_jsp.java文件是怎样来的,你怎么知道index.jsp最终会生成index_jsp.java文件呢?这个文件的生成规则及文件内容又是怎么生成的呢? 先不用担心,我们下一篇博客自然来分析,先来看index_jsp.java生成的内容 。
在这里插入图片描述
  看到这里就先来剧透一下当我们访问一个jsp时,整个工作原理,当我们访问一个url,如http://localhost:8080/servelet-test-1.0/,localhost 决定访问的是本机, 8080端口决定访问哪个端口,tomcat 启动了8080端口的监听器,不断的轮询阻塞读取字节数据,当我们在浏览器中访问 http://localhost:8080/servelet-test-1.0/ 时, tomcat将监听到浏览器发送过来的数据,而根据uri 为servelet-test-1.0,则会将请求次给servelet-test-1.0的StandardContext处理,经过StandardContext的管道和阈门层层处理,最终到了StandardWrapper层,因为servelet-test-1.0后面并没有任何字符,Tomcat 默认会访问webapps/servelet-test-1.0的index.html,index.htm,index.jsp 文件,当然优先级最高的是index.html文件,其次是index.htm ,最后才是index.jsp,如果在webapps/servelet-test-1.0目录下放index.html和index.jsp两个文件,当然返回的内容肯定是index.html的内容,其他情况以此类推, 在本例中,并没有写index.html和index.htm文件,当然定位到了index.jsp文件,当发现是jsp文件,先解析jsp文件,再生成index_jsp.java文件,通过JSP编译器编译后,生成对应的Servlet Java 文件,接下来,要把Servlet Java 文件编译成Class 文件,对于这部分,完全没有必要重新造轮子,常见的优秀编译工具是Eclipse JDT Java 编译器和Ant编译器Tomcat其实同时支持,而默认使用Eclipse JDT编译器,将Servlet Java文件装载到容器中,执行Servlet,将结果输出到网页端。

  上例中最终返回的html文件如下 。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
</head>
<body>
name=${name }
</body>
</html>

  理解了整个原理,来看看${name} 表达式是如何解析得到值的。进入PageContextImpl类的proprietaryEvaluate方法。

在这里插入图片描述

private static final JspFactory jspf = JspFactory.getDefaultFactory();

public static Object proprietaryEvaluate(final String expression,
        final Class<?> expectedType, final PageContext pageContext,
        final ProtectedFunctionMapper functionMap, final boolean escape)
        throws ELException {
    final ExpressionFactory exprFactory = jspf.getJspApplicationContext(pageContext.getServletContext()).getExpressionFactory();
    ELContext ctx = pageContext.getELContext();
    ELContextImpl ctxImpl;
    if (ctx instanceof ELContextWrapper) {
        ctxImpl = (ELContextImpl) ((ELContextWrapper) ctx).getWrappedELContext();
    } else {
        ctxImpl = (ELContextImpl) ctx;
    }
    ctxImpl.setFunctionMapper(functionMap);
    ValueExpression ve = exprFactory.createValueExpression(ctx, expression, expectedType);
    return ve.getValue(ctx);
}

  会调用createValueExpression()方法获取ValueExpression,但exprFactory 工厂类是哪里来的呢?首先我们进入getJspApplicationContext()方法 。

public JspApplicationContext getJspApplicationContext(
        final ServletContext context) {
    // 默认情况IS_SECURITY_ENABLED为false
    if (Constants.IS_SECURITY_ENABLED) {
        return AccessController.doPrivileged(
                new PrivilegedAction<JspApplicationContext>() {
            @Override
            public JspApplicationContext run() {
                return JspApplicationContextImpl.getInstance(context);
            }
        });
    } else {
        return JspApplicationContextImpl.getInstance(context);
    }
}

  默认情况IS_SECURITY_ENABLED为false ,而重点代码是getInstance()方法,我们进入getInstance()方法 。

public static JspApplicationContextImpl getInstance(ServletContext context) {
    if (context == null) {
        throw new IllegalArgumentException("ServletContext was null");
    }
    JspApplicationContextImpl impl = (JspApplicationContextImpl) context
            .getAttribute("org.apache.jasper.runtime.JspApplicationContextImpl");
    if (impl == null) {
        impl = new JspApplicationContextImpl();
        context.setAttribute("org.apache.jasper.runtime.JspApplicationContextImpl", impl);
    }
    return impl;
}

  上面这个方法做了两件事情,如果impl为空,则创建JspApplicationContextImpl对象,并且将引用保存到context属性中。 至少此时我们知道了JspApplicationContext为JspApplicationContextImpl对象,接下来调用他的getExpressionFactory()方法获取ExpressionFactory。

private final ExpressionFactory expressionFactory =
        ExpressionFactory.newInstance();
        
public ExpressionFactory getExpressionFactory() {
    return expressionFactory;
}

  显然在getExpressionFactory()方法中只看到了expressionFactory是通过调用ExpressionFactory.newInstance()获取的。

在这里插入图片描述
在这里插入图片描述

  又在代码中寻寻觅觅,如果在系统环境中没有配置javax.el.ExpressionFactory时,默认取org.apache.el.ExpressionFactoryImpl通过反射构建expressionFactory对象,因此在默认情况下,会调用ExpressionFactoryImpl的createValueExpression方法获取表达式。 接下来,我们来看下面这一行代码 。
  ELContext ctx = pageContext.getELContext();首先要明白pageContext是从哪里来的。

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {

  private static final javax.servlet.jsp.JspFactory _jspxFactory =
          javax.servlet.jsp.JspFactory.getDefaultFactory();
          
  ...  
  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {

    final javax.servlet.jsp.PageContext pageContext;
 

    try {

      pageContext = _jspxFactory.getPageContext(this, request, response,
                null, true, 8192, true);
  	 _jspx_page_context = pageContext;
  	 
      ... 
      out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${name }", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, null, false));
      ... 
    } catch (java.lang.Throwable t) {
      ... 
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}

  通过生成的index_jsp.java的代码逻辑可以得知,_jspx_page_context 实际上是通过上面加粗代码得来的,又在代码中寻寻觅觅。通过下图可以得知,最终调用getPageContext()方法,默认创建了PageContextImpl为pageContext。
在这里插入图片描述
  有了上图的知识,再来理解ELContext ctx = pageContext.getELContext();这一行代码就方便了,pageContext默认为PageContextImpl,实际上调用的是PageContextImpl的getELContext方法,进入getELContext()方法 。

public ELContext getELContext() {
    if (this.elContext == null) {
        this.elContext = this.applicationContext.createELContext(this);
    }
    return this.elContext;
}

public ELContextImpl createELContext(JspContext context) {
    if (context == null) {
        throw new IllegalArgumentException("JspContext was null");
    }
	// 创建EL解析器
    final ELResolver r = this.createELResolver();
    ELContextImpl ctx;
    if (Constants.IS_SECURITY_ENABLED) {
        ctx = AccessController.doPrivileged(
                new PrivilegedAction<ELContextImpl>() {
                    @Override
                    public ELContextImpl run() {
                        return new ELContextImpl(r);
                    }
                });
    } else {
    	// 设置ELContextImpl 中 EL 解析器
        ctx = new ELContextImpl(r);
    }
    
    // 将context put 到ELContextImpl中
    ctx.putContext(JspContext.class, context);
    ELContextEvent event = new ELContextEvent(ctx);
    for (int i = 0; i < this.contextListeners.size(); i++) {
    	// 发送ELContext创建事件 
        this.contextListeners.get(i).contextCreated(event);
    }

    return ctx;
}

private ELResolver createELResolver() {
    this.instantiated = true;
    if (this.resolver == null) {
        CompositeELResolver r = new JasperELResolver(this.resolvers);
        this.resolver = r;
    }
    return this.resolver;
}

public JasperELResolver(List<ELResolver> appResolvers) {
    appResolversSize = appResolvers.size();
    resolvers = new ELResolver[appResolversSize + 7];
    size = 0;
	// 添加默认的解析器
    add(new ImplicitObjectELResolver());
    for (ELResolver appResolver : appResolvers) {
        add(appResolver);
    }
    add(new MapELResolver());
    add(new ResourceBundleELResolver());
    add(new ListELResolver());
    add(new ArrayELResolver());
    add(new BeanELResolver());
    add(new ScopedAttributeELResolver());
}



@Override
public synchronized void add(ELResolver elResolver) {
    super.add(elResolver);

    if (resolvers.length > size) {
        resolvers[size] = elResolver;
    } else {
        ELResolver[] nr = new ELResolver[size + 1];
        System.arraycopy(resolvers, 0, nr, 0, size);
        nr[size] = elResolver;

        resolvers = nr;
    }
    size ++;
}


public ELContextImpl(ELResolver resolver) {
    this.resolver = resolver;
}
    

  上面代码,看上去写了很多,其实原理也很简单,首先创建一个EL解析器,EL默认解析器为JasperELResolver,JasperELResolver为一个总的解析器,默认会添加ImplicitObjectELResolver,MapELResolver,ResourceBundleELResolver,ListELResolver,ArrayELResolver,BeanELResolver,ScopedAttributeELResolver解析器到resolvers属性中,再创建 ELContextImpl 对象,设置其解析器为刚刚创建的JasperELResolver对象, 如果设置了contextListeners监听器,则调用所有的监听器触发contextCreated方法,生成的jsp中,默认functionMap值为null,则ctxImpl.setFunctionMapper(functionMap);这一行代码没有什么意义。之前分析过exprFactory默认为ExpressionFactoryImpl对象,直接进入createValueExpression()方法。

public ValueExpression createValueExpression(ELContext context,
        String expression, Class<?> expectedType) {
    // 如果期望的类型为空,则抛出异常,生成的jsp 文件默认期望类型都为java.lang.String
    if (expectedType == null) {
        throw new NullPointerException(MessageFactory
                .get("error.value.expectedType"));
    }
    
    ExpressionBuilder builder = new ExpressionBuilder(expression, context);
    return builder.createValueExpression(expectedType);
}

  上面这个方法,先来看表达式的构建。

public ExpressionBuilder(String expression, ELContext ctx)
        throws ELException {
    // 设置EL 表达式 
    this.expression = expression;

    FunctionMapper ctxFn = ctx.getFunctionMapper();
    VariableMapper ctxVar = ctx.getVariableMapper();

    if (ctxFn != null) {
        this.fnMapper = new FunctionMapperFactory(ctxFn);
    }
    if (ctxVar != null) {
        this.varMapper = new VariableMapperFactory(ctxVar);
    }
}

  上面代码最重要的就是设置EL表达式,接着继续看createValueExpression()方法。

public ValueExpression createValueExpression(Class<?> expectedType)
        throws ELException {
    Node n = this.build();
    return new ValueExpressionImpl(this.expression, n, this.fnMapper,
            this.varMapper, expectedType);
}

  build()方法是重点,博客的前半部分都是围绕着build方法展开,build方法就是如何将字符串解析成语法树的过程都是在 此。

private Node build() throws ELException {
	// 生成语法树,之前分析过,这里就不深入了
    Node n = createNodeInternal(this.expression);
    this.prepare(n);
    // 之前分析过,如果是${} 和 #{} 
    if (n instanceof AstDeferredExpression
            || n instanceof AstDynamicExpression) {
        n = n.jjtGetChild(0);
    }
    return n;
}

  prepare()方法做了哪些事情呢?

private void prepare(Node node) throws ELException {
    try {
        node.accept(this);
    } catch (Exception e) {
        if (e instanceof ELException) {
            throw (ELException) e;
        } else {
            throw (new ELException(e));
        }
    }
    if (this.fnMapper instanceof FunctionMapperFactory) {
        this.fnMapper = ((FunctionMapperFactory) this.fnMapper).create();
    }
    if (this.varMapper instanceof VariableMapperFactory) {
        this.varMapper = ((VariableMapperFactory) this.varMapper).create();
    }
}

public void accept(NodeVisitor visitor) throws Exception {
    visitor.visit(this);
    if (this.children != null && this.children.length > 0) {
        for (int i = 0; i < this.children.length; i++) {
        	// 调用子节点的所有accept方法
            this.children[i].accept(visitor);
        }
    }
}

  上面代码看上去很绕,其实原理还是很简单的,从树的根节点开始,遍历所有的子节点,并访问其visit方法,树的结构如下。
在这里插入图片描述
  接着我们进入visit方法的实现。

public void visit(Node node) throws ELException {
    if (node instanceof AstFunction) {

        AstFunction funcNode = (AstFunction) node;

        if (this.fnMapper == null) {
            throw new ELException(MessageFactory.get("error.fnMapper.null"));
        }
        Method m = fnMapper.resolveFunction(funcNode.getPrefix(), funcNode
                .getLocalName());
        if (m == null) {
            throw new ELException(MessageFactory.get(
                    "error.fnMapper.method", funcNode.getOutputName()));
        }
        // 方法的参数个数
        int methodParameterCount = m.getParameterTypes().length;
        // el 表达式中传入方法的参数个数
        int inputParameterCount = node.jjtGetNumChildren();
        // 1. 如果有可变参数,el 表达式中方法传入参数个数小于方法声明参数个数减1 ,或
        // 2. 如果方法声明中没有可变参数并且声明方法参数个数和el表达式中方法传入的参数个数不相等,则抛出异常
        if (m.isVarArgs() && inputParameterCount < methodParameterCount - 1 ||
                !m.isVarArgs() && inputParameterCount != methodParameterCount) {
            throw new ELException(MessageFactory.get(
                    "error.fnMapper.paramcount", funcNode.getOutputName(),
                    "" + methodParameterCount, "" + node.jjtGetNumChildren()));
        }
    } else if (node instanceof AstIdentifier && this.varMapper != null) {
        String variable = ((AstIdentifier) node).getImage();
        this.varMapper.resolveVariable(variable);
    }
}

  关于el表达式中方法的使用,先来看一个例子。

自定义EL函数

因为EL本身不具有处理字符串能力,所以可以自定义EL函数

  • 定义函数(新建MyEL.java类)
  • 注册:先找到jsp2-example-taglib.tld,将头部以及注册函数复制到自己创建的.tld文件中(.tld放在WEB-INF下)
  • 在index.jsp中使用,使用时需要<%@ taglib uri=”http://tomcat.apache.org/jsp2-example-taglib” prefix=”MyEL” %>

1、定义函数MyEL.java

package com.example.servelettest;//自定义函数
//该类及其函数,需要在扩展名为.tld的xml文件中注册
//tld:tag library definition(标签库定义)
//xml文件是需要约束的,即需要配置文件头部。这个头部约束可以从一下文件中进行复制
//在Tomcat安装目录下:webapps\examples\WEB-INF\jsp2
//文件为:jsp2-example-taglib.tld

//这个.tld的xml文件,需要定义在当前web项目的WEB-INF目录下,在此目录下创建以.tld结尾的xml文件
//将jsp2-example-taglib.tld中头部复制到创建的xml文件中

//再将函数注册,还是在jsp2-example-taglib.tld底部中复制
public class MyEL {
    private static MyEL instance;

    public static MyEL getInstance() {
        if (instance == null) {
            instance = new MyEL();
        }
        return instance;
    }

    //字符串小写变大写
    public static String LowerToUpper(String str) {
        return str.toUpperCase();
    }
}

2、将jsp2-example-taglib.tld中头部部分以及底部的注册函数部分复制到自己创建的tld(在WEB-INF下)文件中

在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
        version="2.0">

    <!-- 定义标签库信息 -->
    <description>A tag library exercising SimpleTag handlers.</description>
    <tlib-version>1.0</tlib-version>
    <short-name>MyEL</short-name><!-- 标签库名称,一般定义成和文件名一样 -->
    <uri>http://tomcat.apache.org/jsp2-example-taglib</uri>

    <!--  注册函数 -->
    <function>
        <name>MyLowerToUpper</name>
        <function-class>com.example.servelettest.MyEL</function-class><!-- 方法得类 -->
        <function-signature>java.lang.String LowerToUpper( java.lang.String )</function-signature><!-- 方法签名 :需要返回值以及方法名、参数-->
    </function>
</taglib>

3、在index.jsp中使用,使用时需要<%@ taglib uri=”http://tomcat.apache.org/jsp2-example-taglib” prefix=”MyEL” %>

在这里插入图片描述

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<%@ taglib uri="http://tomcat.apache.org/jsp2-example-taglib" prefix="MyEL" %><!-- tld中的uri和short-name -->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Insert title here</title>
</head>
<body>
<!-- 这个方法名是在tld注册时的name -->
${MyEL:MyLowerToUpper("sasas") }<br>


<!-- EL函数只能处理四大域中的属性值及String常量 -->
<%
      String name="xlj";
      pageContext.setAttribute("name", name);
%>



${MyEL:MyLowerToUpper(name) }<br>

</body>
</html>

客户浏览器显示结果

在这里插入图片描述

  先来看生成的index_jsp.java文件 。

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {

private static org.apache.jasper.runtime.ProtectedFunctionMapper _jspx_fnmap_0;

static {
  _jspx_fnmap_0= org.apache.jasper.runtime.ProtectedFunctionMapper.getMapForFunction("MyEL:MyLowerToUpper", com.example.servelettest.MyEL.class, "LowerToUpper", new Class[] {java.lang.String.class});
}
	... 
  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {

    final javax.servlet.jsp.PageContext pageContext;
    ... 

    try {
      response.setContentType("text/html; charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
                null, true, 8192, true);
      _jspx_page_context = pageContext;
      ... 
      out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${MyEL:MyLowerToUpper(\"sasas\") }", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, _jspx_fnmap_0, false));
      ... 
      String name="xlj";
      pageContext.setAttribute("name", name);
      ...  
      out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${MyEL:MyLowerToUpper(name) }", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, _jspx_fnmap_0, false));
      ... 
    } catch (java.lang.Throwable t) {
      ... 
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
}

  index_jsp.java文件内容怎么生成的,还是下一篇博客再来分析,先来分析上面加粗代码。jspx_fnmap_0= org.apache.jasper.runtime.ProtectedFunctionMapper.getMapForFunction(
  “MyEL:MyLowerToUpper”,
  icom.example.servelettest.MyEL.class,
  i"LowerToUpper",
  inew Class[] {java.lang.String.class});
  getMapForFunction方法传了4个参数,如上所示,进入getMapForFunction方法,看其实现。

public static ProtectedFunctionMapper getMapForFunction(String fnQName,
        final Class<?> c, final String methodName, final Class<?>[] args) {
    java.lang.reflect.Method method;
    ProtectedFunctionMapper funcMapper = new ProtectedFunctionMapper();
    try {
        method = c.getMethod(methodName, args);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(
                "Invalid function mapping - no such method: "
                        + e.getMessage());
    }
    funcMapper.theMethod = method;
    return funcMapper;
}

  通过反射获取method,并创建ProtectedFunctionMapper对象,将theMethod设置为配置文件中配置的方法,并作为PageContextImpl.proprietaryEvaluate的final ProtectedFunctionMapper functionMap参数传入到proprietaryEvaluate方法中。并设置到ELContextImpl的functionMapper属性中。并通过ExpressionBuilder构建fnMapper对象,如下加粗代码 。

 public ExpressionBuilder(String expression, ELContext ctx)
    throws ELException {
this.expression = expression;

FunctionMapper ctxFn = ctx.getFunctionMapper();
VariableMapper ctxVar = ctx.getVariableMapper();

if (ctxFn != null) {
    this.fnMapper = new FunctionMapperFactory(ctxFn);
}
if (ctxVar != null) {
    this.varMapper = new VariableMapperFactory(ctxVar);
}

  此时再来看visit方法,我相信就简单多了。

在这里插入图片描述

在这里插入图片描述

  接下来,我们进入建构ValueExpressionImpl对象并返回了。而ValueExpressionImpl对象的构建非常简单如下 ,只是将表达式,node , 函数mapper , 变量 Mapper ,期望类型初始化即可。

public ValueExpressionImpl(String expr, Node node, FunctionMapper fnMapper,
        VariableMapper varMapper, Class<?> expectedType) {
    this.expr = expr;
    this.node = node;
    this.fnMapper = fnMapper;
    this.varMapper = varMapper;
    this.expectedType = expectedType;
}

  接下来就是Value值的获取,我们进入getValue()方法。

public Object getValue(ELContext context) throws PropertyNotFoundException,
        ELException {
    EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
            this.varMapper);
    // 调用节点的getValue()方法
    Object value = this.getNode().getValue(ctx);
    if (this.expectedType != null) {
    	// 将获取到的value转化为期望的类型
        return ELSupport.coerceToType(value, this.expectedType);
    }
    return value;
}

  之前一直强调最终EL表达式解析成了一棵语法树,语法树的每一个节点是怎样的呢?显然每一个节点都继承了SimpleNode类,所有节点的类结构如下 。
在这里插入图片描述
  因此需要获取value的值,只需要调用每一个节点的getValue()方法即可。如 之前的MyEL:MyLowerToUpper函数的例子,看他的getValue()如何实现。

public Object getValue(EvaluationContext ctx)
        throws ELException {
    FunctionMapper fnMapper = ctx.getFunctionMapper();
    if (fnMapper == null) {
        throw new ELException(MessageFactory.get("error.fnMapper.null"));
    }
    Method m = fnMapper.resolveFunction(this.prefix, this.localName);
    if (m == null) {
        throw new ELException(MessageFactory.get("error.fnMapper.method",
                this.getOutputName()));
    }

    Class<?>[] paramTypes = m.getParameterTypes();
    Object[] params = null;
    Object result = null;
    int inputParameterCount = this.jjtGetNumChildren();
    int methodParameterCount = paramTypes.length;
    // 如果方法没有参数,则构建空Object数组即可
    if (inputParameterCount == 0 && methodParameterCount == 1 && m.isVarArgs()) {
        params = new Object[] { null };
    } else if (inputParameterCount > 0) {
        params = new Object[methodParameterCount];
        try {
            for (int i = 0; i < methodParameterCount; i++) {
            	// 当方法声明中有可变参数,并且当前参数是方法声明中最后一个参数,也就是可变参数
                if (m.isVarArgs() && i == methodParameterCount - 1) {
                	// 之前在visit方法分析过
                	// 如果有可变参数 并且inputParameterCount < methodParameterCount - 1时,会抛出异常
                	// 因此 inputParameterCount < methodParameterCount 条件等价于
                	// inputParameterCount = methodParameterCount - 1 的条件,在这种情况下
                	// 构建一个空对象数组来填充最后一个可变参数  
                    if (inputParameterCount < methodParameterCount) {
                        params[i] = new Object[] { null };
                    // 如果最后一个参数是数组类型,并且inputParameterCount == methodParameterCount
                    // 直接调用当前Node的第i个子节点,并调用其getValue()方法 
                    } else if (inputParameterCount == methodParameterCount &&
                            paramTypes[i].isArray()) {
                        params[i] = this.jjtGetChild(i).getValue(ctx);
                    } else {
                    	// 如果传的参数多于方法声明参数个数,则将i 之后的所有参数封装成数组
                    	// 从而封装反射请求参数  
                        Object[] varargs =
                                new Object[inputParameterCount - methodParameterCount + 1];
                        Class<?> target = paramTypes[i].getComponentType();
                        for (int j = i; j < inputParameterCount; j++) {
                            varargs[j-i] = this.jjtGetChild(j).getValue(ctx);
                            varargs[j-i] = coerceToType(varargs[j-i], target);
                        }
                        params[i] = varargs;
                    }
                } else {
               		// 调用每一个子节点的getValue()方法获取返回值
                    params[i] = this.jjtGetChild(i).getValue(ctx);
                }
                // 将getValue()获得的值转化为方法参数需要的类型
                params[i] = coerceToType(params[i], paramTypes[i]);
            }
        } catch (ELException ele) {
            throw new ELException(MessageFactory.get("error.function", this
                    .getOutputName()), ele);
        }
    }
    try {
    	// 通过反射调用方法,获取返回值
        result = m.invoke(null, params);
    } catch (IllegalAccessException iae) {
        throw new ELException(MessageFactory.get("error.function", this
                .getOutputName()), iae);
    } catch (InvocationTargetException ite) {
        Throwable cause = ite.getCause();
        if (cause instanceof ThreadDeath) {
            throw (ThreadDeath) cause;
        }
        if (cause instanceof VirtualMachineError) {
            throw (VirtualMachineError) cause;
        }
        throw new ELException(MessageFactory.get("error.function", this
                .getOutputName()), cause);
    }
    return result;
}

  我相信通过这样一分析,是不是原来感觉比较复杂的逻辑,此时变得很清晰了,从根节点调用getValue()的值,而每一个子节点又调用其getValue()值,通过相应的组装,原本复杂的业务逻辑就变得简单了。而每一个节点只需要维护好自己的getValue()方法即可,再复杂的语法结构也会变得有章可循。

  那再来看一个简单的EL表达式,是不是如我所说,写一个表达式${ 1 + 2 * 3 },计算结果
在这里插入图片描述
  node语法树如下所示
在这里插入图片描述

  ${ 1 + 2 * 3 }表达式的解析只需要看AstInteger,AstMult,AstPlus的实现即可。

AstInteger
public final class AstInteger extends SimpleNode {
    public AstInteger(int id) {
        super(id);
    }

    private volatile Number number;

    protected Number getInteger() {
        if (this.number == null) {
            try {
                this.number = Long.valueOf(this.image);
            } catch (ArithmeticException e1) {
                this.number = new BigInteger(this.image);
            }
        }
        return number;
    }

    @Override
    public Class<?> getType(EvaluationContext ctx)
            throws ELException {
        return this.getInteger().getClass();
    }

    @Override
    public Object getValue(EvaluationContext ctx)
            throws ELException {
        return this.getInteger();
    }
}

  是不是原理很简单,将读取到的字符image转化为Number类型即可。

AstMult
public final class AstMult extends ArithmeticNode {
    public AstMult(int id) {
        super(id);
    }

    @Override
    public Object getValue(EvaluationContext ctx)
            throws ELException {
        Object obj0 = this.children[0].getValue(ctx);
        Object obj1 = this.children[1].getValue(ctx);
        return ELArithmetic.multiply(obj0, obj1);
    }
}

  AstMult的解析也很简单,直接获取左右子节点的值再相乘即可。

AstMult
public final class AstPlus extends ArithmeticNode {
    public AstPlus(int id) {
        super(id);
    }

    @Override
    public Object getValue(EvaluationContext ctx)
            throws ELException {
        Object obj0 = this.children[0].getValue(ctx);
        Object obj1 = this.children[1].getValue(ctx);
        return ELArithmetic.add(obj0, obj1);
    }
}

  加法运算符也很简单,获取左右子节点相加即可。

  有了这些理论知识,我们再来看之前的测试用例。

二、EL获取数据(从四大域中获取属性)
EL只能从四大域中获取属性
1、如果没有使用EL的内置对象,则查找数据顺序是依次按照由小到大范围从四大域中查找指定名称的属性值

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <%
        pageContext.setAttribute("name", "linjie");
        request.setAttribute("name", "lucy");
        session.setAttribute("name", "king");
        application.setAttribute("name", "bilibili");
    %>
    name=${name }
</body>
</html>

  此时的node为AstIdentifier节点。
在这里插入图片描述
  我们进入AstIdentifier的getValue()方法 。

public Object getValue(EvaluationContext ctx) throws ELException {
    VariableMapper varMapper = ctx.getVariableMapper();
    // 默认情况下varMapper为null,先不考虑
    if (varMapper != null) {
        ValueExpression expr = varMapper.resolveVariable(this.image);
        if (expr != null) {
            return expr.getValue(ctx.getELContext());
        }
    }
    // 设置 elContext的resolved 属性为false 
    // 如果调用完getValue()方法后仍然为false,将抛出异常
    ctx.setPropertyResolved(false);
    Object result = ctx.getELResolver().getValue(ctx, null, this.image);
    if (!ctx.isPropertyResolved()) {
        throw new PropertyNotFoundException(MessageFactory.get(
                "error.resolver.unhandled.null", this.image));
    }
    return result;
}

  我们之前分析过ElResolver默认为JasperELResolver,因此这里调用 JasperELResolver的getValue()方法 。

public Object getValue(ELContext context, Object base, Object property)
    throws NullPointerException, PropertyNotFoundException, ELException {
    context.setPropertyResolved(false);

    int start;
    Object result = null;

    if (base == null) {
		// appResolversSize 表示我们自己定制的appResolvers 解析器的size 
		// 如果我们没有自己定制appResolvers的话,下面for 循环只会执行一次
		// 如果定制了Resolver,则先调用ImplicitObjectELResolver 解析器
		// 如果没有获取到值,则会调用自己定制的解析器,直到获取到值为止
        int index = 1 + appResolversSize;
        for (int i = 0; i < index; i++) {
        	// 默认会调用ImplicitObjectELResolver的解析器
        	// 如果调用解析器获取到值,则返回结果
            result = resolvers[i].getValue(context, base, property);
            if (context.isPropertyResolved()) {
                return result;
            }
        }
        // 如果调用ImplicitObjectELResolver 和我们定制的解析器后仍然没有获取到值
        // 则越过MapELResolver,ResourceBundleELResolver,ListELResolver , ArrayELResolver,BeanELResolver 解析器,直接调用 ScopedAttributeELResolver 解析器
        start = index + 5;
    } else {
        start = 1;
    }
    
    for (int i = start; i < size; i++) {
    	// 调用ScopedAttributeELResolver解析器
        result = resolvers[i].getValue(context, base, property);
        if (context.isPropertyResolved()) {
            return result;
        }
    }
    return null;
}

  先进入ImplicitObjectELResolver看其值获取方法 。

ImplicitObjectELResolver
private static final String[] SCOPE_NAMES = new String[] {
            "applicationScope", "cookie", "header", "headerValues",
            "initParam", "pageContext", "pageScope", "param", "paramValues",
            "requestScope", "sessionScope" };
            
public Object getValue(ELContext context, Object base, Object property)
        throws NullPointerException, PropertyNotFoundException, ELException {
    if (context == null) {
        throw new NullPointerException();
    }

    if (base == null && property != null) {
        int idx = Arrays.binarySearch(SCOPE_NAMES, property.toString());

        if (idx >= 0) {
            PageContext page = (PageContext) context
                    .getContext(JspContext.class);
            context.setPropertyResolved(true);
            switch (idx) {
            case "applicationScope":
                return ScopeManager.get(page).getApplicationScope();
            case "cookie":
                return ScopeManager.get(page).getCookie();
            case "header":
                return ScopeManager.get(page).getHeader();
            case "headerValues":
                return ScopeManager.get(page).getHeaderValues();
            case "initParam":
                return ScopeManager.get(page).getInitParam();
            case "pageContext":
                return ScopeManager.get(page).getPageContext();
            case "pageScope":
                return ScopeManager.get(page).getPageScope();
            case "param":
                return ScopeManager.get(page).getParam();
            case "paramValues":
                return ScopeManager.get(page).getParamValues();
            case "requestScope":
                return ScopeManager.get(page).getRequestScope();
            case "sessionScope":
                return ScopeManager.get(page).getSessionScope();
            }
        }
    }
    return null;
}

  先通过二分法从SCOPE_NAMES查找到匹配的字符,如果匹配成功则调用相应的方法获取值 。源码到这里,我们可以看另外一个例子。使用EL内置对象,从指定域中获取数据,提高了查找效率

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <%
        pageContext.setAttribute("name", "linjie");
        request.setAttribute("name", "lucy");
        session.setAttribute("name", "king");
        application.setAttribute("name", "bilibili");
    %>
    name=${applicationScope.name }
</body>
</html>

  相信大家看ImplicitObjectELResolver的getValue方法,肯定知道原因了,为什么?因为首先查找的就是applicationScope关键字,但遗憾的是${name}表达式无法从内置对象中获取到值,因此需要继续查找,之前分析过,会先
调用ImplicitObjectELResolver的getValue()方法查找,如果查找不到越过中间的解析器,直接调用ScopedAttributeELResolver查找值。

  除了applicationScope,其他的 “cookie”, “header”, “headerValues”,“initParam”, “pageContext”, “pageScope”, “param”, “paramValues”, “requestScope”, “sessionScope” 使用情况,我们也举几个例子。

  1. param(获取请求中的指定参数)

  其底层实际调用request.getParameter()

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Insert title here</title>
</head>
<body>
<%-- ${pageContext.request.contextPath }代表web应用的根 --%>
<form action="${pageContext.request.contextPath }/show.jsp" method="POST">
      xxx<input type="text" name="name"/><br>
      yyy<input type="text" name="age"/><br>
      <input type="submit" value="点击">
</form>
</body>
</html>

show.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Insert title here</title>
</head>
<body>
name=${param.name }<br>
age=${param.age }<br>
</body>
</html>

  在ImplicitObjectELResolver的getValue()方法如下图所示。

在这里插入图片描述
  进入ScopeManager.get(page).getParam() 方法。

public Map<String,String> getParam() {
    if (this.param == null) {
        this.param = new ScopeMap<String>() {
            @Override
            protected Enumeration<String> getAttributeNames() {
                return page.getRequest().getParameterNames();
            }

            @Override
            protected String getAttribute(String name) {
                return page.getRequest().getParameter(name);
            }
        };
    }
    return this.param;
}

  上面例子中,先返回ScopeMap对象,当获取param.name值时,实际上是调用了MapElResolver的getValue()方法,先来看看ScopeMap类结构

private abstract static class ScopeMap<V> extends AbstractMap<String,V> {
    protected abstract Enumeration<String> getAttributeNames();

    protected abstract V getAttribute(String name);
    ... 

    @Override
    public final V get(Object key) {
        if (key != null) {
            return getAttribute((String) key);
        }
        return null;
    }
    ... 
}

  ScopeMap类结构有一个重要的方法get(),
而MapElResolver的getValue方法()中。实际上是调用的是ScopeMap的get方法,而get方法的内部又调用了ScopeMap的getAttribute()方法,getAttribute()方法实际上又调用了getParam的加粗代码page.getRequest().getParameter(name),现在应该对上面这个例子的源码有所理解吧。

  1. paramValues

获取请求中的指定参数的所以值,其底层实际调用request.getParameterValues()

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%-- ${pageContext.request.contextPath }代表web应用的根 --%>
    <form action="${pageContext.request.contextPath }/show.jsp" method="POST">
        xxx<input type="text" name="name"/><br>
        yyy<input type="text" name="age"/><br>

        爱好:
        <input type="checkbox" name="hobby" value="sleep">睡觉
        <input type="checkbox" name="hobby" value="play">玩
        <input type="checkbox" name="hobby" value="eat">吃
        <input type="submit" value="点击">
    </form>
</body>
</html>


show.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    name=${param.name }<br>
    age=${param.age }<br>


    hobby[0]=${paramValues.hobby[0] }<br>
    hobby[1]=${paramValues.hobby[1] }<br>
</body>
</html>

  客户浏览器显示结果
在这里插入图片描述

在这里插入图片描述

  通过page.getRequest().getParameterValues(name);获得值。
在这里插入图片描述

  1. initParam

  获取初始化参数,其底层调用的是ServletContext.getInitParameter()

web.xml

在这里插入图片描述

index.jsp

在这里插入图片描述
  通过page.getServletContext().getInitParameter(name)获得初始化参数,执行结果。
在这里插入图片描述

ScopedAttributeELResolver
public Object getValue(ELContext context, Object base, Object property)
        throws NullPointerException, PropertyNotFoundException, ELException {
    if (context == null) {
        throw new NullPointerException();
    }

    if (base == null) {
        context.setPropertyResolved(true);
        if (property != null) {
            String key = property.toString();
            PageContext page = (PageContext) context
                    .getContext(JspContext.class);
            return page.findAttribute(key);
        }
    }

    return null;
}

public Object findAttribute(final String name) {
    if (SecurityUtil.isPackageProtectionEnabled()) {
        return AccessController.doPrivileged(
                new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                if (name == null) {
                    throw new NullPointerException(Localizer
                            .getMessage("jsp.error.attribute.null_name"));
                }

                return doFindAttribute(name);
            }
        });
    } else {
        if (name == null) {
            throw new NullPointerException(Localizer
                    .getMessage("jsp.error.attribute.null_name"));
        }

        return  doFindAttribute(name);
    }
}


private Object doFindAttribute(String name) {

    Object o = attributes.get(name);
    if (o != null)
        return o;

    o = request.getAttribute(name);
    if (o != null)
        return o;

    if (session != null) {
        try {
            o = session.getAttribute(name);
        } catch(IllegalStateException ise) {
            // Session has been invalidated.
            // Ignore and fall through to application scope.
        }
        if (o != null)
            return o;
    }

    return context.getAttribute(name);
}

  相信源码看到这里,对在没有设置内置对象的情况下, 查找数据顺序是依次按照由小到大范围从四大域中查找指定名称的属性值,先从pageContext中查找,再从request中查找,再到session中查找,最后才到application中查找。

  接下来看另外一个例子

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Insert title here</title>
</head>
<body>
<%-- ${pageContext.request.contextPath }代表web应用的根 --%>
<form action="${pageContext.request.contextPath }/regster" method="POST">
      xxx<input type="text" name="name"/><br>
      yyy<input type="text" name="age"/><br>
      <input type="submit" value="点击">
</form>
</body>
</html>

  加粗代码就是EL 表达式需要解析的值,先来看看语法树,如下图所示。
在这里插入图片描述
  根节点是AstValue,3个子节点分别为AstIdentifier , DotSuffix , DotSuffix。因此先进入AstValue的getValue()方法。

AstValue
public Object getValue(EvaluationContext ctx) throws ELException {
	// 先获取第0个节点的value
    Object base = this.children[0].getValue(ctx);
    int propCount = this.jjtGetNumChildren();
    int i = 1;
    Object suffix = null;
    ELResolver resolver = ctx.getELResolver();
    // 循环遍历所有后缀子节点的个数,并且以上一个子节点获取的base作为下一个子值来源
    while (base != null && i < propCount) {
        suffix = this.children[i].getValue(ctx);
        // 如果i + 1 子节点是方法参数类型 
        if (i + 1 < propCount &&
                (this.children[i+1] instanceof AstMethodParameters)) {
            AstMethodParameters mps =
                (AstMethodParameters) this.children[i+1];
			// 获取方法参数值
            Object[] paramValues = mps.getParameters(ctx);
            // 调用方法获取方法返回值
            base = resolver.invoke(ctx, base, suffix,
                    getTypesFromValues(paramValues), paramValues);
            i+=2;
        } else {

            if (suffix == null) {
                return null;
            }

            ctx.setPropertyResolved(false);
            // 从base中获取后缀值,因为我们知道resolver默认为JsperELResolver,此时调用JsperELResolver的getValue方法
            base = resolver.getValue(ctx, base, suffix);
            i++;
        }
    }
    if (!ctx.isPropertyResolved()) {
        throw new PropertyNotFoundException(MessageFactory.get(
                "error.resolver.unhandled", base, suffix));
    }
    return base;
}

  之前分析过pageContext为jsp的内置对象。因此从下面代码中获取到pageContext对象 。
在这里插入图片描述
  获取到的pageContext对象如下图所示 。
在这里插入图片描述
  此时的base不再是空 ,因此start从1开始。

在这里插入图片描述
  之前分析过,resolvers = new ELResolver(){ImplicitObjectELResolver,MapELResolver,ResourceBundleELResolver,ListELResolver,ArrayELResolver,BeanELResolver,ScopedAttributeELResolver} ,因此resolvers[1] 是MapELResolver对象,进入MapELResolver的getValue()方法 。

MapELResolver
 public Object getValue(ELContext context, Object base, Object property) {
    if (context == null) {
        throw new NullPointerException();
    }

    if (base instanceof Map<?,?>) {
        context.setPropertyResolved(true);
        return ((Map<?,?>) base).get(property);
    }

    return null;
}

  MapELResolver调用的相关示例

<%@page import="java.util.*"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <%
        Map<String,Object> map=new HashMap<String,Object>();
        map.put("age", 20);
        map.put("name", "xlj");
        pageContext.setAttribute("map", map);
    %>
    name=${map.name }<br>
    age=${map.age }<br>
</body>
</html>

  首先从pageContext中获取map的值。
在这里插入图片描述
  再通过MapELResolver解析器获取name的值。
在这里插入图片描述

  MapELResolver只对base类型为Map的做处理,同理可得到其他解析器的处理规则。

ResourceBundleELResolver
public Object getValue(ELContext context, Object base, Object property) {
    if (context == null) {
        throw new NullPointerException();
    }

    if (base instanceof ResourceBundle) {
        context.setPropertyResolved(true);

        if (property != null) {
            try {
                return ((ResourceBundle) base).getObject(property
                        .toString());
            } catch (MissingResourceException mre) {
                return "???" + property.toString() + "???";
            }
        }
    }

    return null;
}

  看一下ResourceBundle的测试用例。

<%@page import="java.util.*"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Insert title here</title>
</head>
<body>
<%
      ResourceBundle resourceBundle = new ResourceBundle() {
            @Override
            protected Object handleGetObject(String key) {
                  return "我在测试resourceBundle key = " + key;
            }

            @Override
            public Enumeration<String> getKeys() {
                  return null;
            }
      };
      pageContext.setAttribute("resource", resourceBundle);
%>
name=${resource.name }<br>
</body>
</html>

在这里插入图片描述
在这里插入图片描述
  ResourceBundle的getObject()方法如下。

public final Object getObject(String key) {
    Object obj = handleGetObject(key);
    if (obj == null) {
        if (parent != null) {
            obj = parent.getObject(key);
        }
        if (obj == null) {
            throw new MissingResourceException("Can't find resource for bundle "
                                               +this.getClass().getName()
                                               +", key "+key,
                                               this.getClass().getName(),
                                               key);
        }
    }
    return obj;
}



  handleGetObject() 会调用自定义的handleGetObject()方法,因此结果输出如下 。
在这里插入图片描述

ListELResolver
public Object getValue(ELContext context, Object base, Object property) {
    if (context == null) {
        throw new NullPointerException();
    }

    if (base instanceof List<?>) {
        context.setPropertyResolved(true);
        List<?> list = (List<?>) base;
        int idx = coerce(property);
        if (idx < 0 || idx >= list.size()) {
            return null;
        }
        return list.get(idx);
    }

    return null;
}

  看一下list的测试用例。

<%@page import="java.util.*"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <%
        List<String> names=new ArrayList<String>();
        names.add("xlj");
        names.add("lucy");
        pageContext.setAttribute("names", names);
    %>

    <!-- 因为List底层是数组,所以可以这样写 -->
    names[1]=${names[1] }<br>
</body>
</html>

  从内置对象中获取list值。
  
  从list中获取index=1的值。

在这里插入图片描述

ArrayELResolver
public Object getValue(ELContext context, Object base, Object property) {
    if (context == null) {
        throw new NullPointerException();
    }

    if (base != null && base.getClass().isArray()) {
        context.setPropertyResolved(true);
        int idx = coerce(property);
        if (idx < 0 || idx >= Array.getLength(base)) {
            return null;
        }
        return Array.get(base, idx);
    }

    return null;
}

  看一下Array数组的测试用例。

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Insert title here</title>
</head>
<body>
<%
      String[] names={"xlj","lucy","king"};
      pageContext.setAttribute("names", names);
%>
name[1]=${names[1] }<br>

<!-- 若访问的数组元素下标超出了数组下标上限,EL不会抛出越界异常,只是不显示 -->
names[5]=${names[5] }<br>
</body>
</html>

  依然从pageContext中获取数据。
在这里插入图片描述
  从ArrayELResolver的getValue()方法中获取值。
在这里插入图片描述

BeanELResolver
public Object getValue(ELContext context, Object base, Object property) {
    if (context == null) {
        throw new NullPointerException();
    }
    if (base == null || property == null) {
        return null;
    }

    context.setPropertyResolved(true);
    Method m = this.property(context, base, property).read(context);
    try {
        return m.invoke(base, (Object[]) null);
    } catch (InvocationTargetException e) {
        Throwable cause = e.getCause();
        Util.handleThrowable(cause);
        throw new ELException(Util.message(context, "propertyReadError",
                base.getClass().getName(), property.toString()), cause);
    } catch (Exception e) {
        throw new ELException(e);
    }
}

  显然,pageContext对象只能通过BeanELResolver处理,并通过反射调用获取pageContext的request属性值,同样的代码逻辑获取contextPath值 。

  分析了那么多例子,还有一个empty运算符的例子没有分析。

index.jsp

<%@page import="java.util.*"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Insert title here</title>
</head>
<body>
<%
      String name1="8932";
      pageContext.setAttribute("name1", name1);
%>

empty对于null的引用,运算结果为true:
empty name1=${empty name1 }<br>

</body>
</html>

在这里插入图片描述
  先调用Identifier的getValue()获取值再根据不同的数据类型进行空值判断,如String类型就是通过 (String) obj).length() == 0 ,字符串的长度是否为0 来判断字符是否为空。
在这里插入图片描述

  当然呢?getValue中之前还有一种情况,当方法调用时 还有一个例子没有举,通过方法返回值,再调用方法

  1. 测试方法
package com.example.servelettest;//自定义函数
//该类及其函数,需要在扩展名为.tld的xml文件中注册
//tld:tag library definition(标签库定义)
//xml文件是需要约束的,即需要配置文件头部。这个头部约束可以从一下文件中进行复制
//在Tomcat安装目录下:webapps\examples\WEB-INF\jsp2
//文件为:jsp2-example-taglib.tld

//这个.tld的xml文件,需要定义在当前web项目的WEB-INF目录下,在此目录下创建以.tld结尾的xml文件
//将jsp2-example-taglib.tld中头部复制到创建的xml文件中

//再将函数注册,还是在jsp2-example-taglib.tld底部中复制
public class MyEL {

    //字符串小写变大写
    public static TestDto getTestDto() {
        return new TestDto();
    }


}

package com.example.servelettest;

public class TestDto {


    public String getMyUsername(String username) {
        return username;
    }

}

  创建两个实例,MyEL的getTestDto方法返回TestDto对象,TestDto类中提供了getMyUsername()方法。

  在WEB-INF中添加MyEL.tld文件

<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
        version="2.0">

    <!-- 定义标签库信息 -->
    <description>A tag library exercising SimpleTag handlers.</description>
    <tlib-version>1.0</tlib-version>
    <short-name>MyEL</short-name><!-- 标签库名称,一般定义成和文件名一样 -->
    <uri>http://tomcat.apache.org/jsp2-example-taglib</uri>

    <!--  注册函数 -->
    <function>
        <name>getTestDto</name>
        <function-class>com.example.servelettest.MyEL</function-class><!-- 方法得类 -->
        <function-signature>com.example.servelettest.TestDto getTestDto()</function-signature><!-- 方法签名 :需要返回值以及方法名、参数-->
    </function>
</taglib>

  在webapp下创建index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<%@ taglib uri="http://tomcat.apache.org/jsp2-example-taglib" prefix="MyEL" %><!-- tld中的uri和short-name -->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Insert title here</title>
</head>
<body>

${MyEL:getTestDto().getMyUsername("小明") }<br>
</body>
</html>

  先调用MyEL.getTestDto方法,通过返回值,再调用其getMyUsername()方法,并传入参数"小明", 先来看一下${MyEL:getTestDto().getMyUsername(“小明”) } 表达式语法树生成的结构

在这里插入图片描述

在这里插入图片描述
  上面例子中,先通过this.children[0].getValue(ctx)获取TestDto对象,再通过Object[] paramValues = mps.getParameters(ctx)从AstMethodParameters类中获取getUsername()方法的请求参数"小明",再通过resolver.invoke(ctx, base, suffix,
getTypesFromValues(paramValues), paramValues); 代码,通过反射调用TestDto对象的getUsername()方法。
  通过上面这些例子和源码的分析,此时,我相信大家对EL表达式的源码有了新的认识,我们还有最后一个小点分析一下,就是值的转换。

 public static final Object coerceToType(final Object obj,
        final Class<?> type) throws ELException {
    if (type == null || Object.class.equals(type) ||
            (obj != null && type.isAssignableFrom(obj.getClass()))) {
        return obj;
    }
    if (String.class.equals(type)) {
        return coerceToString(obj);
    }
    if (ELArithmetic.isNumberType(type)) {
        return coerceToNumber(obj, type);
    }
    if (Character.class.equals(type) || Character.TYPE == type) {
        return coerceToCharacter(obj);
    }
    if (Boolean.class.equals(type) || Boolean.TYPE == type) {
        return coerceToBoolean(obj);
    }
    if (type.isEnum()) {
        return coerceToEnum(obj, type);
    }

    // new to spec
    if (obj == null)
        return null;
    if (obj instanceof String) {
        PropertyEditor editor = PropertyEditorManager.findEditor(type);
        if (editor == null) {
            if ("".equals(obj)) {
                return null;
            }
            throw new ELException(MessageFactory.get("error.convert", obj,
                    obj.getClass(), type));
        } else {
            try {
                editor.setAsText((String) obj);
                return editor.getValue();
            } catch (RuntimeException e) {
                if ("".equals(obj)) {
                    return null;
                }
                throw new ELException(MessageFactory.get("error.convert",
                        obj, obj.getClass(), type), e);
            }
        }
    }

    // Handle arrays
    if (type.isArray() && obj.getClass().isArray()) {
        return coerceToArray(obj, type);
    }

    throw new ELException(MessageFactory.get("error.convert",
            obj, obj.getClass(), type));
}

  coerceToType方法的应用场景,如从getValue()方法获取到一个字符串类型,但方法的请求参数是int类型,需要将String 类型转化为int类型,就是一个工具类,很好用,感兴趣可以自己去研究一下。

  EL 表达式的源码分析到这里告一段落了,我相信看明白的小伙伴肯定对EL表达式有新的认识,如果你想开发一门脚本语言,EL表达式的源码肯定对你在参考价值,也许有人觉得这些东西对业务没有什么用处 ,同时也那么难懂,又不像Spring 源码那么出名,有什么用呢,如 之前的博客 开发自己的测试框架 就是这种解析方法实现的,如果你想达到语言级的开发的话,这些思想还是有很大用处的,话不多说,下一篇博客研究jsp.文件的解析以及xxx_jsp.java文件生成,jsp 行号和java 行号是怎么对应起来,xxx_jsp.java如何编译,装载等。如果有疑问请留言 。

本文相关的源码地址

https://github.com/quyixiao/tomcat.git

https://github.com/quyixiao/servelet-test

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值