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的解析相对来说简单多了
只需要在遇到'<'字符的时候读出来节点名,然后在当前应用支持的标签库中去查找对应的标签类, 如果没查到,就按照上面的继续编译为out.write("<");
否则, 读入所有的属性, 创建一个标签实例, 然后根据定义的属性和标签中定义的属性,依次调用对应的setter方法, 例如:
<c:if test="${user.name == 'tom'}"><h1>a</h1></c:if>
编译结果大致为:
Expr expr_0 = xxx.createExpr("${user.name == 'tom'}");
Tag _tag_0 = new xxx.xxx.IfTag();
_tag_0.setter(...);
int _tag_flag_0 = _tag_0.doStartTag();
if(_tag_flag_0 != SKIP_BODY)
{
while(true)
{
// doInitBody, doBody等
_tag_flag_0 = _tag_0.doEndTag();
// doAfterBody等
if(_tag_flag_0 != EVAL_BODY_AGAIN)
{
break;
}
}
}
上面是一个标签运行的标准流程, 事实上对于不同的容器,编译结果区别很大,例如resin, 实际编译结果大致如下:
Expr expr_0 = xxx.createExpr("${user.name == 'tom'}");
if(expr_0.getBoolean())
{
}
很简单的
补充:Web开发 , Jsp ,