深入理解Jsp中的两种包含机制——include指令和include动作

一、引子

    我们知道一般网站的每个页面都拥有相同的页眉(如网站的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");

猜你喜欢

转载自JokerLinisty.iteye.com/blog/2194968