日期:2014-05-16  浏览次数:20394 次

jsp编译过程
j2ee规范中对jsp的编译有个规范:第一步,先编译出来一个xml文件, 第二部再从这个xml文件编译为一个java文件
例如: test.jsp
    <%!
        int a = 1;
        private String sayHello(){return "hello";}
    %>
    <%
        int a = 1;
    %>
    <h1>Hello World</h1> 
第一步,先编译为一个xml文件,结果如下
    <jsp:declare>
    int a = 1;
    private String sayHello(){return "hello";}
    </jsp:declare>
    <jsp:scriptlet>
    int a = 1;
    </jsp:scriptlet>
    <h1>Hello World</h1>
第二步,再编译为一个java文件, 大致结果如下
    public class _xxx_test{
        int a = 1;
        private String sayHello(){return "hello";}

        public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException{

            JspWriter out = xxxx.getWriter();
            // 创建其他的隐含对象

            int a = 1;
            out.write("<h1>Hello World</h1>");

            // 释放资源
        }
    }
从中可以看出编译过程, 编译器依次读入文本, 遇到<%@就认为这是个jsp指令, 指令是对编译和执行这个jsp生效的.
当遇到<%!它的时候就认为这是个声明, 其中的内容会直接生成为类的类属性或者类方法, 这个看里面是怎么写的,
例如: int a = 1; 就认为这是个类属性.

当遇到<%它的时候就认为这是个脚本, 会被放置到默认的方法里面的.

以上是jsp的编译过程, 还没有说对标签怎么编译, 后面再说.

有个问题, 当编译器遇到<%的时候,会依次读入后续内容直到遇到%>, 如果里面的java代码里面包含了个字符串,这个字符串的内容是%>,怎么办?
我知道的是像tomcat是不会处理这种情况的,也就是说jsp的编译器并不做语法检查, 只解析字符串, 上面的这种情况编译出来的结果就是错的了,下一步再编译为class文件的时候就会报未结束的字符常量. 例如:
<%
    String s = "test%>"
%>
编译出来的结果大致如下:
    public class _xxx_test{
        public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException{
            JspWriter out = xxxx.getWriter();

            // 创建其他的隐含对象

            String s = "test
            out.write("\"\r\n%>");

            // 释放资源
            }
    }

j2ee规范还定义了jsp可以使用xml语法编写, 因为jsp是先编译为xml, 其实<%也是先编译成了<jsp:scriptlet>因此下面的两个文件是等效的:
文件1:
<%
    int a = 1;
%>

文件2:
<jsp:scriptlet>int a = 1;</jsp:scriptlet>

不过对于规范,不同的容器在实现的时候并不一定会按照规范来做,我知道的是tomcat是按照这个来做的,并且我记得在tomcat的早期版本中还能在work目录中找到对应的xml文件.
但是websphere是不支持的,不知道现在的版本支不支持, resin好像也不支持, 也就是说在websphere中, <%必须写成<%, 不能用<jsp:script>
websphere并没有先编译为xml, 再编译为java

以上的编译过程对于编码来说是很简单的,如果不编译为xml文件,它简单到只用正则就能搞定.

EL表达式
对于el表达式的支持也很简单, 遇到${, 就开始读入, 直到遇到}, 将其中的内容生成为一个表达式对象, 直接调用该表达式的write方法即可, 例如:
abc${user.name}123

编译结果大致如下:
    public class _xxx_test{
        public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException{
            JspWriter out = xxxx.getWriter();
            ExprEnv exprEnv = xxx.create();

            out.write("abc");
            org.xxx.xxx.Expr _expr_xxx = xxx.createExpr("${user.name}");
            _expr_xxx.write(out, exprEnv);
            out.write("123\r\n");
        }
    }

不同的容器在实现的时候有所不同, 例如resin, 会将所有的表达式编译为类的静态变量, 以提升性能. 因为一个jsp页面一旦写好, 表达式的数目和内容是确定的,
因此是可以编译为静态变量的.

为什么要编译为调用表达式的write方法, 而不是编译为out.write(_expr_xxx.getValue()), 我认为其中一个原因是为了表达式做null处理,任何一个表达式如果返回会空, 那么写到页面上都应该是"", 而不应该是"null"。out.write默认会将null对象转为"null"字符串写入, 如果编译为out.write(_expr_xxx.getValue()),就得 out.write((_expr_xxx.getValue() != null ? _expr_xxx.getValue() : ""));很显然这样是影响性能的, 因为如果返回结果不为null的话对表达式可能会计算两次.如果不这样做,就需要重新定义变量, 为了变量不冲突,每个地方编译器都要生成一个新的变量名, 导致最终生成的文件较大.

tag编译
对tag的编译略微麻烦,但也不复杂,这需要对源文件做html解析,但是跟一个完整的html解析器比起来,对tag的解析相对来说简单多了
只需要在遇到'<'字符的时候读出来节点名,然后在当前应用支持的标签库中去查找对应的标签类, 如果