一、引子
我们知道一般网站的每个页面都拥有相同的页眉(如网站的logo)和页脚文件(如版权说明),而且这两个部分的内容一般是很少发生变动的。为此,我们就需要在每个页面都反复的编写同样的页眉和页脚,想想也够无聊的。既然都是重复的内容,那我们是不是可以将这些重复性的内容放到单独的一个文件(一般称为模板文件)中,然后在其他页面引入这个文件呢?答案是可以的。
在Jsp中就有一个“包含”的处理机制,目的就是用于将一些模板文件内容包含到当前页面,然后和当前页面内容一起作为响应输出。
二、Jsp中的两种包含机制:include指令(<%@ include %>)和include动作(<jsp:include >)。下面我们就来具体分析下这两种机制的作用及区别。
1、本质区别
1)include指令:该指令属于编译指令,发生在Jsp转换为Servlet的阶段。简单来说,就是在转换阶段把被包含的文件内容复制(原封不动的)到当前页面中,然后和当前页面一起编译生成一个Servlet文件。
由于容器是把两个文件合并后在执行编译,所以编译后只会生成一个class文件(main_jsp.class)。因此,不管是主文件或被包含文件发生改变,容器都需要重新编译主文件。一般使用include指令包含静态的资源文件。
我们知道,不使用包含指令时,我们需要手工地将重复的页眉(或页脚)内容复制到每一个页面中。不过有了include指令,就相当于把复制的工作交给了容器。我们要做的就是在页面中使用include指令告诉(指示)容器处理被包含的文件,剩下的工作(复制并编译等)将由容器全权负责。
2)include动作:该动作属于运行时方法调用,发生在运行Servlet生成响应的阶段。简单来说,就是在运行Servlet时插入被包含文件的响应内容。<jsp:include>动作的关键在于,容器需要根据页面(page)属性创建一个RequestDispatcher,并应用include()方法,把被包含文件的响应插到当前响应中,然后一起输出。
使用include动作时,容器会分别对主文件和被包含文件进行单独的编译,所以编译后会产生两个class文件:main_jsp.class和header_jsp.class。另外,被包含文件的编译工作是在主文件运行时(执行到主文件中的include()方法(容器根据include动作生成的方法调用))发生的。因此,被包含文件改变时,只需重新编译被包含文件即可,而无需重新编译主文件。一般使用include动作包含经常需要变动的文件。
2、语法(假设需要在main.jsp中包含header.jsp页面)
1)include指令:<%@ include file="header.jsp" %>
file属性:指定被包含的文件。该属性不支持任何表达式,也不允许在指令中传递参数。
2)include动作:<jsp:include page="header.jsp" />
page属性:指定了被包含页面的路径,可以是一个代表相对路径的表达式,也可以使用<jsp:param>来传递参数给被包含的页面。
3、处理流程(假设需要在main.jsp中包含header.jsp页面)
1)include指令:容器要做很多工作(转换),不过也只是针对第一个请求而已。
a)客户请求main.jsp页面。
b)容器接受到客户请求,查找到main.jsp页面,并开始执行转换工作。
c)容器发现include指令,则合并header.jsp的源代码到当前页面,然后一起转换为main Servlet文件。
d)容器把转换后的main Servlet源文件(.java)编译为字节码文件(.class)。
e)以上步骤只发生一次(第一次请求main.jsp的时候),除非man.jsp或header.jsp发生改变。
f) 容器加载编译后的字节码(.class)文件,并完成初始化工作。
g)容器为请求创建(分配)一个新的线程,并调用_jspService()方法处理请求,并最终返回响应。
2)include动作:容器不用做什么转换工作,但对应每个请求却要处理更多的事情。
a)客户请求main.jsp页面。
b)容器接受到客户请求,查找到main.jsp页面,并开始执行转换工作。
c)容器发现include动作,并利用它在main Servlet代码中插入一个方法调用(include,见后面实例代码),这会在运行main Servlet时动态地将header.jsp的响应与main.jsp的响应合并。
d)容器把转换后的main Servlet源文件(.java)编译为字节码文件(.class)。
e)以上步骤只发生一次(第一次请求main.jsp的时候),除非man.jsp发生改变。
f)容器加载编译后的字节码(.class)文件,并完成初始化工作。
g)容器为请求创建(分配)一个新的线程,并调用_jspService()方法处理请求。
h)容器在main Servlet中调用一个方法(如include())完成动态包含,即将main Servlet和header Servlet生成的响应合并,然后返回响应。(该步骤中,header.jsp会在某个时刻被容器执行转换、编译、加载及初始化。)
4、使用注意
1)include指令:由于是在编译时包含源文件,所以被包含页面可以包含可能影响主页面的JSP构造,比如属性、方法的定义和文档类型的设定等,不过要避免声明相同的变量或方法,否则会产生编译错误。
2)include动作:由于是在运行时包含响应内容,所以被包含页面不能使用任何有可能影响主页面的JSP构造,比如文档类型设定等。另外,被包含的页面不能包含开始和结束HTML及BODY标签。
3)被包含的页面不能修改响应状态码或者设置首部(可能不会发生错误,只是达不到预期的结果)。
5、实例代码(特别关注使用include和include动作生成的Servlet文件区别)
1)被包含页面:header.jsp(简单起见,就打印一句话)。
<strong>This is header.jsp!</strong><br>
2)main.jsp文件(使用include指令)
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<html>
<head>
<title>main.jsp</title>
</head>
<body>
<%@ include file="header.jsp" %>
This is main.jsp<br>
</body>
</html>
转换后只生成一个main_jsp.java文件,_jspService()中关键代码。
//设置响应内容类型及编码
response.setContentType("text/html;charset=utf-8");
out.write("\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" <title>main.jsp</title>\r\n");
out.write(" </head>\r\n");
out.write(" <body>\r\n");
out.write(" ");
out.write("\r\n");
//只是将header.jsp的内容原封不动的输出
out.write("<strong>This is header.jsp!</strong><br>\r\n");
out.write("\r\n");
out.write(" This is main.jsp<br>\r\n");
out.write(" </body>\r\n");
out.write("</html>\r\n");
3)main.jsp文件(使用include动作)
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<html>
<head>
<title>main.jsp</title>
</head>
<body>
<jsp:include page="header.jsp" />
This is main.jsp<br>
</body>
</html>
转换后会生成两个java文件:header_jsp.java和main_jsp.java。
header_jsp.java中_jspService()的关键代码:(没什么特别的,只是简单的输出)
//设置响应内容类型及编码
response.setContentType("text/html");
out.write("\r\n");
out.write("<strong>This is header.jsp!</strong><br>\r\n");
main_jsp.java中_jspService()的关键代码:(注意容器调用的include()方法)
//设置响应内容类型及编码
response.setContentType("text/html;charset=utf-8");
out.write("\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" <title>main.jsp</title>\r\n");
out.write(" </head>\r\n");
out.write(" <body>\r\n");
out.write(" ");
//关键点:运行时,容器会调用include()方法实现动态包含(顺便注意下传入的参数有什么作用)
org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "header.jsp", out, false);
out.write("\r\n");
out.write(" This is main.jsp<br>\r\n");
out.write(" </body>\r\n");
out.write("</html>\r\n");