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

Jsp 与 Servlet的编译过程、原理、区别及使用
一、 编译过程 每一个JSP页面都会被Web容器编译成一个Java类,供 web容器调用,并且生成HTML页面回馈给用户。而了解其中的编译方法和规则,对我们学习JSP是非常有好处的,可以说学习好了这个编译原理,就已经学习好了大部分的 JSP知识,剩下的工作就只剩下熟记一些tablib和反复应用以使自己更加熟练而已了。

JSP会被编译成 jsp名称_jsp.java文件放Tomcat/work/Catalina/localhost/项目名称/org/apache/jsp/目录下
然后编译成 jsp名称_jsp.class文件
jsp = java + html
servlet = java + out.print(html)

在第一次请求web服务时会执行如下过程:
1.客户端发送请求给web容器
2.web容器将jsp首先转译成servlet源代码
3.web容器将servlet源代码编译成.class 文件
4.web容器执行.class 文件
5.web容器将结果响应给客户端
所以web第一次为请求提供服务比较慢,从第二次请求开始会省略2、3两个步骤。

编译原理
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:
[java]?view plaincopy
<%??
????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页面一旦写好, 表达式的数目和内容是确定的,
因此是可以编译为静态变量的.

为什么要编