声明:原创作品,转载时请注明文章来自SAP师太技术博客:www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.cnblogs.com/jiangzhengjun/p/4289323.html
JSP. 80
JSP源码生成... 81
将JSP页面配置成Servlet 84
JSP基础语法... 84
JSP模板元素... 84
JSP表达式... 84
JSP脚本... 84
JSP声明... 85
EL表达式... 85
JSP注释... 85
JSP指令... 85
page指令... 86
include指令... 90
JSP标签 (动作)... 91
<jsp:include>标签... 91
<jsp:forward>标签... 92
<jsp:param>标签... 92
<jsp:plugin>标签... 93
脚本元素标签... 93
指令标签... 93
<jsp:text>标签... 93
out对象... 94
pageContext对象... 98
获取其他JSP内置对象... 99
引入和跳转到其他资源... 99
访问各个域范围中的属性... 100
pushBody方法与popBody方法... 101
JSP页面中的转义... 103
JSP中文乱码问题... 104
JSP排错... 105
JavaBean在JSP中的应用... 107
<jsp:useBean>标签... 108
<jsp:setProperty>标签... 109
<jsp:getProperty>标签... 110
Other 110
Servlet的自启动... 112
Servlet的自动重新加载
JSP
conf\web.xml:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
...
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
org.apache.jasper.servlet.JspServlet就是Jsp引擎,它是一个Servlet程序,所以*.jsp结尾的请求都会交给这个Servlet处理。
一个JSP页面只在第一次被访问时才需要被翻译成Servlet程序,对于该JSP页面的后续访问,Web容器将直接调用其翻译成的Servlet程序。在JSP页面每次被访问时,Jsp引擎默认都会检测该Jsp文件和编译成的Servlet类的最后更改时间,如果Jsp文件自上次编译以后又发生了修改,Jsp引擎将重新编译该JSP文件。
在产品发布时,应该禁止Jsp引擎自动检测JSP页面是否修改:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>development</param-name>
<param-value>false</param-value>
</init-param>
...
<load-on-startup>3</load-on-startup>
</servlet>
当然,如果你删除Jsp所对应的Servlet的Class文件,则还是会自动重新编译的。
JSP源码生成
所有编译出来的Class类都是org.apache.jasper.runtime.HttpJspBase的子类,而org.apache.jasper.runtime.HttpJspBase又是HttpServlet的子类。
HttpJspBase为抽象类,其_jspService(HttpServletRequest request, HttpServletResponse response) 为抽象方法,而我们的Jsp页面所生成的类实现了该方法。
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private static java.util.List _jspx_dependants;
public Object getDependants() {
return _jspx_dependants;
}
public void _jspService(HttpServletRequest request,
HttpServletResponse response) throws java.io.IOException,
ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try {
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.print("Hello jsp!");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)) {
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null)
_jspx_page_context.handlePageException(t);
}
} finally {
if (_jspxFactory != null)
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {
protected HttpJspBase() {}
//final方法,不能被重写
public final void init(ServletConfig config) throws ServletException {
super.init(config);
jspInit();
_jspInit();
}
public String getServletInfo() {
return Localizer.getMessage("jsp.engine.info");
}
//final方法,不能被重写
public final void destroy() {
jspDestroy();
_jspDestroy();
}
/**
* Entry point into service.
* final方法,不能被重写
*/
public final void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
_jspService(request, response);
}
public void jspInit() {//用户可以重写
}
public void _jspInit() {//由引擎生产并使用
}
public void jspDestroy() {//用户可以重写
}
protected void _jspDestroy() {//由引擎生产并使用
}
//由引擎生产并使用
public abstract void _jspService(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException;
}
将JSP页面配置成Servlet
不只是Servlet类才能配置成Servlet应用的,Jsp文件与可以:
<servlet>
<servlet-name>jspservlet</servlet-name>
<jsp-file>/test.jsp</jsp-file>
</servlet>
<servlet-mapping>
<servlet-name>jspservlet</servlet-name>
<url-pattern>/jspservlet</url-pattern>
</servlet-mapping>
这样我们就可以通过 http://localhost:8080/myapp/jspservlet 来访问这个页面了。
JSP基础语法
JSP模板元素
JSP页面中的静态HTML内容称之为JSP模板元素。它定义了网页的基本骨架,即页面结构与外观。之所以可以作为模式,因为HTML内容是静态的,不会变化,而不会变化的东西自然就是架子。
注,这些静态内容生成的代码为:out.write("<a href=\"\"></a>\r\n");
JSP表达式
<%= "hi" %>
生成的代码:out.print("hi");
注,out.write是java.io.Writer类中的方法,而 out.print 则为javax.servlet.jsp.JspWriter中定义的方法,并且JspWriter为Writer的子类。
JSP脚本
嵌套在 <% 和 %> 之间的一条或多条Java程序代码。Jsp脚本中的Java代码将被原封不动地搬到Jsp页面翻译成的Servlet的 _jspService 方法中。
JSP声明
JSP页面中的脚本、表达式、模板元素等都将转换成 Servlet 的 _jspService 方法中的程序代码。
JSP声明将Java代码封装在<%! 和 %> 之中。JSP声明可以用于定义JSP页面转换成的Servlet程序的静态代码块、成员变量和方法,并且JSP声明中的Java代码只能是静态代码块、变量和方法的定义。
由于JSP内置对象的作用范围仅限于Servlet的 _jspService 方法,所以在JSP声明中不能使用这些内置对象。
EL表达式
EL(Expression Language)表达式语言是JSP2.0中新增加的一种可简化JSP开发的技术。
基本语法格式为“${表达式}”,它可以出现在JSP自定标签和标准标签的属性值中,其计算结果将作为标签的属性值或属性值的一部分;EL表达式也可以出现在模板元素中,其计算结果将插入进当前的输出流中。
<%= request.getParameter("user") %> 相应的EL表达式为:${ param.user }
<% CustomerBean custormBean = (CustomerBean)pageContext.findAttribute("customerBean"); %>
<%= custormBean.getAddress().getCountry() %> 相应的EL表达式为 ${ custormBean.address.country }
比如通过 ${cookie.user} 来访问名称为 user 的Cookie信息。
如果表达式的计算结果为 null,JSP表达式将输出内容为“null”,而EL表达试会转换成空字符串。
JSP注释
<%-- JSP注释信息 --%>
JSP引擎在将JSP页面翻译成Servlet程序时,忽略JSP页面中被注释的内容。
<!-- HTML注释信息 --%>
JSP指令
指令并不直接产生任何可见输出,而只是告诉引擎如何处理JSP页面中的其余部分。
<%@ 指令 属性名= "值" %>
JSP2.0中定义了page、include、taglib 三种指令。
如果要在一个JSP页面中设置同一条的多个属性,可以使用多条指令语句单独设置每个属性,也可以使用同一条指令语句设置该指令的多个属性。如下两种等效:
第一种方式:
<% page contentType= "text/html;charset=gb2312" %>
<% page import= "java.util.Date" %>
第二种方式:
<% page contentType= "text/html;charset=gb2312" import= "java.util.Date" %>
page指令
JSP2.0规范中定义的page指令完整语法如下:
<%@ page
[ language= "java" ]
[ extends= "package.class" ]
[ import= "{package.class | package.*} , ..." ]
[ session= "true | false" ]
[ buffer= "none | 8kb | size kb" ]
[ autoFlush= "true | false" ]
[ isThreadSafe= "true | false" ]
[ info= "text" ]
[ errorPage= "relative_url" ]
[ isErrorPage= "true | false" ]
[ contentType= "mimeType [;charset=characterSet ]" | "text/html, charset=ISO-8859-1" ]
[ pageEncoding= "characterSet | ISO-8859-1" ]
[ isELIgnored= "true | false" ]
%>
l extends:指定JSP页面翻译成Servlet所继承的父类,一般不要设置这个属性,而是让JSP引擎自行处理就够了。
l import:多个包是使用逗号分隔。
l session:指定在JSP页面翻译 成Servlet中是否预先创建好session这个内置对象,也就是指定JSP脚本元素中是否可以使用session内置对象。如果为false,则生成的Servlet类文件中不包含如下两条语句:
HttpSession session = null;
session = pageContext.getSession();
如果为true,且容器中已经存在当前客户端的会话对象,则让session变量绑定到这个存在的会话对象上,否则新建一个。
注,虽然在JSP页面里没有启用session,但你还是可以这样来使用session的 ,如 :
HttpSession session1 = pageContext.getSession();
如果某个JSP页面中没有使用Session隐式对象,那么最好是将该页面的page指令的session属性设置为false。例如,有的客户端只是不经意地访问了Web站点上的某个JSP页面,而没有访问其他任何页面就离开了,如果Web站点不需要对这样的访客进行会话跟踪,Web容器就不必为这样的客户端创建对应的Session对象。如果没有将这个JSP页面的Page指令的session属性设置为false,Web容器将为这样的客户端创建Session对象,这就造成了内存资源浪费。
l buffer:指定out内置对象的缓冲区大小,默认为8kb,如果将buffer属性设置为none,则out对象不使用缓冲区。
l autoFlush:用于设置当out隐式对象的缓冲区己满时,是将其中的内容刷新到客户端,还是抛出缓冲区溢出的异常。默认为true,表明缓冲区己满时刷新其中的内容到客户端。如果将buffer属性设置为了none,那么就不能把autoFlush属性设置为false,因为将buffer属性设置为none,这就相当于缓冲区总是满的情况,而将autoFlush属性设置为false,则表明缓冲区己满时抛出异常,这就产生了冲突。
l isThreadSafe:默认为true,表示用户已经考虑过了编程安全问题,比如使用同步访问资源,则在生成Servlet时,不需要实现SingleThreadModel接口。如果为false时,则生成的Servlet文件会实现SingleThreadModel接口,并且在同一时只允许一个用户访问。
l info:将一个文本字符串定义为JSP页面翻译成的Servlet的描述信息,容器可以通过Servlet.getServletInfo方法获得该文本字符串。
<%@ page info="登录页面"%>
<%=this.getServletInfo()%>
页面上输出:登录页面
l errorPage:用于设置另外一个JSP页面来处理当前JSP页面发生的异常,如果当前JSP页面内产生了未被捕获的异常,则跳转到另外那个JSP页面去处理。
也可以在web.xml文件中使用<error-page>元素来为整个Web应用设置错误处理页面,其中的<exception-type>子元素指定异常类的完全限定名,<location>元素指定以“/”开头的错误处理页面的路径,路径不带应用目录。如果设置了某个JSP页面的errorPage属性,那么在web.xml文件中设置的异常错误处理将不对该页面起作用。
l isErrorPage:只有当属性为true时,当前页面才能被用做异常处理页面,脚本元素中才可以使用exception内置对象。
=============== testError.jsp===============
<%@ page errorPage="dealError.jsp"%>
<%
try{
out.println("before exception!");
int x = 1/0;
out.println("after exception!");
}catch(Exception e){
throw new Exception("find error - ",e);
}
%>
=============== dealError.jsp===============
<%@ page isErrorPage="true" import="java.io.PrintWriter"%>
<%
out.println("output of dealError!");
exception.printStackTrace(new PrintWriter(out));
%>
=============== 输出===============
output of dealError!
java.lang.Exception: find error -
at org.apache.jsp.testError_jsp._jspService(testError_jsp.java:50)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:369)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:308)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:259)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:172)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:108)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:174)
at org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:843)
at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:640)
at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1293)
at java.lang.Thread.run(Thread.java:595)
Caused by: java.lang.ArithmeticException: / by zero
at org.apache.jsp.testError_jsp._jspService(testError_jsp.java:47)
... 18 more
在web.xml文件中设置全局错误处理二种方式:
<error-page>
<exception-type>java.lang.NullPointerException</exception-type>
<location>/error.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/404.jsp</location>
</error-page>
由于Servlet的service方法只声明抛出ServletExcpetion和IOException异常类型,另外,由于在方法中抛出运行异常时不必在方法定义中显示声明,所以,在Servet的service方法中可以抛出如下异常(这也是<exception-type>里能捕获到的所有异常类型):
1) RuntimeException或Error及其子类
2) ServletException及其子类
3) IOException及其子类
如果Servlet的service方法中发生了除上面这些类型之外的其他异常,service方法内部必须自行进行捕获处理,否则,Servlet无法编译。由于Servlet程序中永远不可能抛出除上面这些类型之外的其他异常,Web容器也就不可能捕获到其他类型的异常(比如
<%
Class.forName("aaaaaaaaaaaa");
%>
<error-page>
<exception-type>java.lang.ClassNotFoundException</exception-type>
<location>/dealError.jsp</location>
</error-page>
运行时,/dealError.jsp 捕获不到ClassNotFoundException类型的异常。但如果在页面开头加上<%@ page errorPage="dealError.jsp"%>时,/dealError.jsp 就可以捕获到。
如果将异常重新包装成运行时异常(除了可以包装成RuntimeException外,上面提交到三种异常都可以,但不能包装成其他类型的非捕获异常,否则生成的Servlet源文件不能编译,因为_jspService方法只声明可以抛出IOException与ServletException以及运行时异常),则可以被web.xml中设置的错误页面所捕获,如:
<%
try{
Class.forName("aaaaaaaaaaaa");
}catch(ClassNotFoundException e){
throw new RuntimeException(e);
}
%>
<error-page>
<exception-type>java.lang.RuntimeException</exception-type>
<location>/dealError.jsp</location>
</error-page>
现在/dealError.jsp错误页面可以捕获到异常了。
),所以,在<exception-type>元素中只有设置上面这些异常类型才有意义。
l contentType:用于设置响应正文的MIME类型(即指定Content-Type响应头的值)和说明JSP文件上的文本内容的字符集编码。默认的MIME类型为text/html,默认字符集为ISO-8859-1。
在生成Servlet源文件时,JSP引擎会根据该属性生成相应的调用response.setContentType方法的语句。另外一个次要的作用是,在页面中没有通过其他方式指定JSP源文件的字符编码时,还具有说明JSP源文件的字符编码的作用。
l pageEncoding:指定JSP源文件中的字符所使用的字符集编码。如果设置了pageEncoding属性,contentType属性就不再具有说明JSP源文件的字符集编码的作用了。
如果JSP页面中没有设置contenType属性,那么pageEncoding属性还具有指定Servlet运行时输出给客户的响应正文的字符集编码的作用,即让JSP引擎在JSP页面所翻译成的Servlet源文件中生成相应的response.setContentType语句:
<%@ page pageEncoding="GBK"%>
生成:response.setContentType("text/html;charset=GBK");
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="GBK" %>
生成:response.setContentType("text/html; charset=UTF-8");
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="GBK" %>
<%
response.setContentType("text/html; charset=GB2312");
response.setCharacterEncoding("iso8859-1");
%>
生成的Servlet的_jspService方法中会有以下三条语句:
response.setContentType("text/html; charset=UTF-8");
response.setContentType("text/html; charset=GB2312");
response.setCharacterEncoding("iso8859-1");
l isELIgnored:指定当前JSP页面中是否支持EL表达式。如果Web应用程序使用的是遵循Servlet2.3规范及更低版本的web.xml文件,那么该isELIgnored属性的默认认值为true,表明JSP页面默认不支持EL表达式。如果Web应用程序使用的是遵循Servlet2.4规范及更高版本的web.xml文件,那么该isELIgnored属性的默认值为fate,表明JSP页面默认支持EL表达式。
<%@ page isELIgnored="true" %>
${param.name}
生成:out.write("${param.name}\r\n");
如果将上面的修改成false,则生成以下语句:
out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${param.name}", java.lang.String.class, (PageContext)_jspx_page_context, null, false));
web.xml文件所遵循的Servlet规范版本可以从web.xml文件看出:
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
include指令
<%@ include finle= "xxx" %>
用于通知JSP引擎在翻译当前JSP页面时将其他文件中的内容合并进当前JSP页转换成的Servlet源文件中,它是一种静态引入。当前JSP页面与静态引入的页面结合为一个Servlet。
注意点:
l 引入的文件必须遵循JSP语法,与文件的扩展名没有关系。
l 被引入的文件中的指令会被合入到当前JSP页面中(除了pageEncoding属性外),所以,除了import(因为它本身可以重复)和pageEncoding属性之外,千万不要在两个文件中对page指令的同一个属性设置不同的值(当然值相同是可以的)。注,使用include指令包含的文件编码会有各自的JSP源文件编码方式,被包含的源文件的编码方式与调用者没有关系,所以他们的编码方式是独立的。
l 除了指令元素外,被引入的其他元素都转换成Java源码,然后插入到 include 指令所在的当前JSP页面中的位置。
l 引入的过程是在各自的运行空间进行,不是将被引入的源文件合并后再翻译成Servlet文件的,对被引入的文件是一边翻译一边合并到当前JSP所生成的Servlet源文件中。当前JSP页面的源文件与被引入文件的源文件可以采用不同的字符集编码,而这种源文件的编码一般使用pageEncoding属性来指定各自的编码,因为在两个文件中不可能对同一属性contentType设置成不同的值,除非它们的值一样(注,值比较时会区分大小写,所在要完全一样)。所以指定被引入的文件的编码存储格式时一般使用pageEncoding即可。
l 引入文件后,在生成的源码中我们可以看到增加了如下语句:
private static java.util.List _jspx_dependants;
static {
_jspx_dependants = new java.util.ArrayList(1);
_jspx_dependants.add("/included.jsp");
}
_jspx_dependants集合存储了当前JSP页面所依赖的文件。
l 这里的file属性如果是以“/”开始,则是表示相对于当前Web应用目录(即会在它前面加上应用目录);但如果不是以“/”开头,这时它是相对于文件(file),而不是相对于页面(page),这也是这个属性被命名为file的原因。
假如myapp应用目录下有一个a.jsp文件,其一般的访问路径形式为 http://localhost:8080/myapp/a.jsp ,在a.jsp页面中使用如下语句引入 b.jsp 文件:
<%@ include file="b.jsp" %>
如果即使你现将a.jsp页面映射成Servlet形式 http://localhost:8080/myapp/dir1/a.html ,由于include指令的file属性设置值是相对于a.jsp文件本身路径,而不是相对于http://localhost:8080/myapp/dir1这个路径的,它还是相对于http://localhost:8080/myapp路径,所以还是没有问题。
JSP标签 (动作)
<jsp:include>标签
<jsp:include page="relativeURL | <%=expression%>" flush="true|false"/>
flush属性指定在插入其他资源的输出内容时,是否选将当前JSP页面的已输出的内容刷新到客户端,默认是“false”.
生成的Servlet代码:
org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "included.jsp", out, true);
与include指令比较:
(1) <jsp:include>标签是在当前JSP页面的执行期间插入被引入资源的输出内容(结果),当前JSP页面与被动态引入的资源是两个彼此独立的执行实体,被动态引入的资源必须是一个能独立被Web容器调用和执行的资源。而前面讲解的include指令只能引入遵循JSP格式的文件,被引入文件与当前JSP文件共同合并翻译成一个Servlet的源文件。
(2) <jsp:include>标签的作用与运行原理类似RequestDispatcher.include方法,被引入页面不能改变响应状态码,也不能设置响应头,与此相关的语句的执行结果将被忽略。include指令没有这方面的限制。
(3) <jsp:include>标签的执行效率要比include指令稍徽差一点,但它的灵活性却要好得多。
(4) <jsp:include>标签对JSP引擎翻译JSP页面的过程没有影响,它是在JSP页面的执行期间才被调用,因此不会影响两个页面的编译,例如,一个页面的page指令不影响另外一个页面。由于include指令是在JSP引擎翻译JSP页面的过程中被解释处理的,所以它对JSP引擎翻译JSP页面的过程起作用,如果多个JSP页面中都要用到一些相同的声明,那么就可以把这些声明语句放在一个单独的文件中编写,然后在每个JSP页面中使用
include指令将那个文件包含进来。
(5)因为include指令引入的文件被合并翻译到当前JSP页面中,向被引入的文件传递参数信息的做法根本就没有任何意义,它们将被JSP引擎忽略。而<jsp:include>标签可以传递参数信息和接受通过表达式动态产生的被引入资源的名称,所以,如果要用参数来控制引入的结果或动态生成引入的资源名,只能使用<jsp:include>标签。
(6) <jsp:include>标签的page属性的设置值必须使用相对路径,如果以“/”开头,表示相对于当前Web应用程序的根目录(注意不是站点根目录),否则,表示相对于当前页面(而不是相对于文件的,这也是这个属性被命名为page的原因)。
注,<jsp:include>不会出现因同时调用了 response.getOutputStream与response.getWriter而产生异常,这与pageContext.include一样不会抛异常,但与 RequestDispatcher.include不一样。
<jsp:forward>标签
<jsp:forward page="relativeURL | <%=expression%>" />
<空格><jsp:forward page="/test.html" /><空格>
生成代码如下:
out.write(" ");
if (true) {
_jspx_page_context.forward("/test.html");
return;
}
out.write(" ");
所以 <jsp:forward>不会出现 因同时调用了 response.getOutputStream与response.getWriter而产生异常,因为他们不可能都调用。
<jsp:param>标签
为<jsp:include>和<jsp:forward>标签动态的传递参数。
<jsp:include page="relativeURL | <%=expression%>" >
<jsp:param name="parameterName" value="parameterValue | <%=expression%>" />
</jsp:include>
<jsp:forward page="relativeURL | <%=expression%>" >
<jsp:param name="parameterName" value="parameterValue | <%=expression%>" />
</jsp: forward >
<jsp:plugin>标签
脚本元素标签
JSP2.0规范中定义了一些标签来替代JSP页面中的脚本片断、JSP声明、JSP表达式,以便采用XML语法格式来定义JSP脚本:
<% code %>可替代为 <jsp:scriptlet> code </jsp:scriptlet>
<%! code %>可替代为 <jsp:declaration> code </jsp:declaration>
<%= expression %>可替代为 <jsp:expression> expression </jsp:expression>
指令标签
JSP2.0规范中定义了<jsp:directive.directiveType>标签来替代<%@ directive … %>语句,以便采用XML语法格式来定义JSP指定:
语法:
<jsp:directive.directiveType attribute="value" />
<%@ page import="java.util.*" %> 可替换为 <jsp:directive.page import="java.util.* " />
<%@ include file="test.html" %> 可替换为 <jsp:directive.include file="test.jsp" />
<jsp:text>标签
JSP 2.0规范中定义了一个<jsp:text>标签,可以将JSP页面中的模板内容封装在这个标签当中,嵌套在<jsp:text>标签中的内容将被作为模板内容输出给客户端。<jsp:text>标签中不能嵌套任何形式的子标签,如果输出给客户端的内容中包含HTML标签或XML语法规定的特殊字符,应将这些内容封装在CDATA区中,或用预定义实体替代特殊字符。
==========================综合实例==============================
<jsp:directive.page import="java.util.Date" />
<jsp:declaration>int count = 1;</jsp:declaration>
<jsp:scriptlet>String currentTime = new Date().toString();</jsp:scriptlet>
<jsp:text>
<![CDATA[
<html>
<head>
<title>a simple jsp</title>
</head>
<body>
current time is <i><u>
]]>
</jsp:text>
<jsp:expression>currentTime</jsp:expression>
<jsp:text>
<![CDATA[
</u></i>
</body>
</html>
]]>
</jsp:text>
注意,上面生成的Servlet源码与以前写作方式没有区别。
out对象
JSP页面里的 out 内置对象是通过调用paggeContext内置对象的getOut方法获取的(JspWriter out = pageContext.getOut();),其作用与ServletResponse.getWriter方法返回的 java.io.PrintWriter 对象非常相似(实质上最后out对象要去调用response.getWriter方法返回的PrintWriter对象上的writer()方法将out缓冲区的数据写入到Servlet引擎的缓冲区),但它们是不同的类型对象。JSP页面中的out内置对应的类型为JspWriter,JspWriter(JspWriter是java.io.Writer的子类)相当于一种带缓冲功能的 PrintWriter,可以通过设置JSP页面的page指令的buffer属性可以调整它的缓存大小,甚至关闭它的缓存。JS页面中的out内置对象相当于插入到ServletResponse.getWriter方法返回的PrintWriter对象前面的缓冲包装类对象,只有向out对象中写入了内容,满足如下任何一个条件时,out对象才去调用ServletResponse.getWriter方法,并通该方法返回的PrintWriter对象将out对象缓冲区中的内容真正写入到Servlet引擎提供的缓冲区中:
l 设置page指令的buffer属性关闭了out了对象的缓冲功能。
l 写入到out对象中的内容充满了out对象的缓冲区。
l 整个JSP页面结束。
JSP页面的out内置对象中的缓冲区与Servlet引擎提供的缓冲区之间的工作关系下如图:
<%
out.println("firs line<br>");//out带缓冲输出
response.getWriter().println("secode line<br>");//PrintWriter不带缓冲输出
%>
页面上显示:
second line
first line
从上面输出的结果可以看出,尽管out.println语句位于response.getWriter().println()语句之前,但它输出的内容却位于后者输出的内容之后。这是因为out.println()语句只是把内容写入到了out对象的缓冲区中,直到整个JSP页面结束时,out 对象才把它的缓冲区里面的内容真正写入到Servlet引擎提供的缓冲区中,而response.getWriter().println()语句则是直接把内容写入到了Servlet引擎提供的缓冲区中。
=====================实例 1========================
<空格><%
ServletOutputStream sos = response.getOutputStream();
sos.println("xxx");
%>
注意,上面的“<%”前有其他一些字符,比如空格,即“<%”不是JSP页面的第一个字符。运行时页面抛出异常:org.apache.jasper.JasperException: getOutputStream() has already been called for this response
位于Jsp脚本元素之外的任何字符文本,在Jsp页面所翻译成的Servlet源文件中都要被转换成以这些字符文本作为参致的out.write语句输出到客户端,即使在JSP脚本元素之外输入的是一个回车符,它也将被转换成一条out.write("\r\n")语句。由于只要向out对象中写入了内容,它就会在Jsp页面结束时(因为缓存的原因)去调用response.getWriter方法,这就与程序中调用的response.getOutputStream方法发生了冲突,所以出现异常信息。如果在“<%”之前或“%>”没有输出任何内容,即没有向out对象中写入任何内容时,在isp页面结束时将不会调用respoose.getWriter方法。可见,只要在脚本片断和指令元素之外没有任何空格和换行字符,那么,在Jsp页面中还是可以调用rsponse.getOutputStrem方法的。
=====================实例 2========================
<%@ page import="java.util.*" %><回车换行>
<%
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
Enumeration values = request.getHeaders(headerName);
while (values.hasMoreElements()) {
System.out.println(headerName + " = " + values.nextElement());
}
}
RequestDispatcher rd = application.getRequestDispatcher("/test.html");
rd.forward(request,response);
%>
注,JSP脚本元素“<%”前特意打了一个空格。运行时,浏览器抛出异常:org.apache.jasper.JasperException: getOutputStream() has already been called for this response
因为客户端对静态HTML文件和图片的访问请求都是由Tomcat的缺省Servlet来处理的,缺省Servlet首先检查是否已经调用过当前HttpServletResponse对象的getWriter方法返回了PrintWriter对象,如果已经调用,则使用getWriter方法返回的PrintWriter对象输出静态HTML文件中的内容,否则,缺省Servlet调用getOutputStream方法返回的ServletOutputStream对象来将静态HTML文件中的内容按字节流的形式原封不动地输出到客户端。上面的JSP文件将请求转发给test.html页面时,还没有真正调用过ServletResponse.getWriter方法,缺省Servlet将调用ServletResponse.getOutputStream方法。缺省Servlet处理完后,程序又回到JSP页面中继续执行,因为JSP页面在跳转前(跳转后输出也是一样,反正直到JSP页面结束时out对象才调用response.getWriter方法向Servlet引擎输出)已向out对象中写入了内容(有一个回车换行需要输出),所以,在JSP页面结束时又将调用ServletResponse.getWriter方法,这就造成了冲突。
如果在上面JSP页面跳转前增加如下语句:
response.getWriter();
然后使用浏览器重新访问页面,就可以正确地显示内容了,这是因为默认Servlet此时也将调用ServletResponse.getWriter方法返回PrintWriter对象来输出静态页面test.html文件的内容。
JSP规范禁止在JSP页面中直接调用ServletResponse.getWriter返回PrintWriter对象和调用ServletResponse.getOutputStream返回ServletOutputStream对象,就是为了避免发生类似上面的实验中所出现的各种上问题。
注意 ,将该实例的中的 rd.forward 修改为 rd. include 还是会有同样的异常,因为这个include是属性动态包含,它像forward一样执行完后又回到原JSP页面再继承执行,所以就会有同样的问题。
如果你将上面JSP里的产生问题的回车换行去掉,使用telnet访问,查看响应头消息如下:
GET /myapp/test.jsp HTTP/1.1
Host:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=5BB22DC968C3A3FDDEDD34A1BAA54C9C; Path=/myapp
Accept-Ranges: bytes
ETag: W/"3-1279902318000"
Last-Modified: Fri, 23 Jul 2010 16:25:18 GMT
Content-Type: text/html
Content-Length: 3
Date: Sat, 24 Jul 2010 02:40:20 GMT
xxx
--end
从上面的输出可以看出缺少Servlet在输出静态内容时,会在响应头中加上 Last-Modified 字段(不通过转发,直接访问静态内容时也会自动加上这个头),表示test.html文件最近的修改时间,浏览器缓存了test.html页面内容,当它再次访问上面JSP页面时,将发送 if-modified-since 请求头询问服务器是否可以使用缓存,尽管这时你又将那个回车换行给加上,但此时刷新页面不会出现错误,就因为回传了这个头的原因,在跳转时会将请求转换发给缺少Servlet,由于test.thml 文件未发生改变,缺少Servlet将回复浏览器一个 304(No Modified)状态码表示可以使用缓存,而不向浏览器回送任何实体内容,也就不会调用ServletResponse.getOutputStream方法了,所以现在即使修改错了,也不会出现错误。下面是重新刷新页面时JSP页面里打出的请求头信息,我们可以看以确实上传了修改时间:
if-modified-since = Fri, 23 Jul 2010 16:25:18 GMT
if-none-match = W/"3-1279902318000"
此时为了重现以前的错误,可以删除浏览器产生的临时文件或者在test.html中加上空格以造成文件被修改的假象。
GET /myapp/test.jsp HTTP/1.1
Host:
if-none-match: W/"4-1279940968000"
if-modified-since: Sat, 24 Jul 2010 03:09:28 GMT
HTTP/1.1 304 Not Modified
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=689DCE564A678003C493D44E69641E7F; Path=/myapp
ETag: W/"4-1279940968000"
Date: Sat, 24 Jul 2010 03:18:47 GMT
只有pageContext.include不会抛出“getOutputStream() has already been called for this response”,其他方式的跳转与包含都可能抛出这样的异常。
<%
/*
当 pageContext.include 调用缺省Serlvet的serveResource()方法时
try {
ostream = response.getOutputStream();//1
} catch (IllegalStateException e) {
// If it fails, we try to get a Writer instead if we‘re
// trying to serve a text file
if ( (contentType == null)
|| (contentType.startsWith("text"))
|| (contentType.endsWith("xml")) ) {
writer = response.getWriter();
} else {
throw e;
}
}
当调用代码第 1行时,response.getOutputStream()默认就是抛出IllegalStateException异常,
所以pageContext.include方法固定使用 response.getWriter()返回的PrintWriter来输出静态
页面内容,所以即使后面还有输出也不会抛异常。
*/
pageContext.include("/test.html");
/*
该语句会调用缺省Servlet时,因为在这之后没有调用过response.getWriter()方法,所以缺省
Servlet里又会使用response.getOutputStream()返回的ServletOutputStream对象来输出静态
内容。又因该JSP页面末还要输出其他内容,所以生成的Servlet源文件中会使用out对象输出页面
末的内容,这里会有如下三行:
out.write(‘a‘);
out.write(‘\r‘);
out.write(‘\n‘);
所以在页面结束时,out对象会调用 response.getWriter()将out缓存中的内容输出到Servlet引擎
缓冲区。这里就会因为即调用了response.getOutputStream又调用了 response.getWriter() ,
所以抛出异常。 下面所以语句都会抛出异常,原因与这个相同。
*/
//application.getRequestDispatcher("/test.html").include(request,response);
//request.getRequestDispatcher("/test.html").include(request,response);
//pageContext.forward("/test.html");
//application.getRequestDispatcher("/test.html").forward(request,response);
//request.getRequestDispatcher("/test.html").forward(request,response);
%>a
pageContext对象
pageContext内置对象是javax.servlet.jsp.PageContext类的实例对象,且是javax.servlet.jsp.JspContext的子类。它封闭了当前JSP页面的运行信息,并提供了返回JSP页面的其他内置对象的方法。pageContext充分期间是通过调用JspFactory.getPageContext方法返回的:
public abstract PageContext getPageContext(Servlet servlet,
ServletRequest request,
ServletResponse response,
String errorPageURL,
boolean needsSession,
int buffer,
boolean autoflush);
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
后四个参数是JSP引擎根据JSP页面中的page指令设置的属性来生成的,因此,pageContext对象也封装了部分page指令的属性设置。
如果一个JSP调用了一个普通Java类,而这个普通Java类要访问JSP页面中的多个内置对象,只面要将pageContext传递给那个普通Java类,在那个类中就可以访问和调用其他内置对象的功能了。PageContext类中也提供了一些方法来调用其他内置对象的方法,以便直接可以调用这些方法完成一些常见和得要功能,而不需要先获得内置对象后再调用内置的相应方法,如,pageContext.forward()方法即可以将请求转发到另外一个资源。
PageContext类中定义了一个setAttribute方法来将对象存储进pageContext对象内部的一个HashMap对象中,同时也定义了一个getAttribute方法来检索存储在该HashMap对象中的对象,以便在JSP页面与它调用的普通Java类之间传递对象信息。Pagecontext类除了可以存储和检索自身中的属性对象外,还定义了可以存储和检索其他域范围内的属性对象的方法。
获取其他JSP内置对象
PageContext pageContext
HttpSession session PageContext.getSession()
ServletContext application PageContext.getServletContext()
ServletConfig config PageContext.getServletConfig()
JspWriter out PageContext.getOut()
HttpJspBase page PageContext.getPage()
Exception exception PageContext.getException()
HttpServletRequest request PageContext.getRequest()
HttpServletResponse response PageContext.getResponse()
引入和跳转到其他资源
PageContext类中定义了一个forward方法和现从个include方法来分别简化和替代 RequestDispatcher.forward方法和RequestDispatcher.include方法的调用,它们的完整定义语法如下:
public void forward(String relativeUrlPath)
public void include(String relativeUrlPath)
public void include(String relativeUrlPath, boolean flush)
这里的路径如果以“/”开头,则表示相对于Web应用目录,即会加上应用目录,如 “/test.html”则为“/myapp/test.html”,如果不以“/”开头,则是相对于当前JSP所映射到的访问路径(同一个JSP文件可以被映射成各种不同的访问路径,这里的相对路径是相对于当前的访问路径,而不是相对于JSP文件的目录路径)。
第一个inicude方法在引入资源前先将out对象中的内容刷新到Servlet引擎提供的缓冲区中;第二个inicude方法则通过一个参数来决定在引入资源前是否刷新out对象中的内容。forward方法在将请求转发给其他资源之前,先清空out对象中已写入的内容,然后调用RequestDispatcher.forward方法执行请求转发。
==================实验====================
<空格><%
pageContext.forward("/test.html");
%>
运行时,不会像使用application.getRequestDispatcher("/test.html"). forward(request,response);那样抛出异常,因为pageContext.forward调用前,会先清空out对象的缓冲区,所以在JSP结束时也不会去调用 response.getWriter 方法将out缓冲区的内容输出到Servlet缓冲区(缺省Servlet调用的是 response.getOutputStream 方法输出静态页面内容的)。但如果将空格加到Jsp末,则会抛出异常:
<%
pageContext.forward("/test.html");
%><空格>
如果在跳转前加上 response.getWriter(),则又不会抛出异常,因为此时缺省Servlet在输出静态内容时也是使用response.getWriter方法返回的PrintWriter来输出的:
<%
response.getWriter();
pageContext.forward("/test.html");
%><空格>
注,使用pageContext.include 时不管脚本外前后是否有其他输出都不会抛异常,这与使用 RequestDispatcher.include不太一样。
访问各个域范围中的属性
在application, session, request, pageContext对象中都可以调用setAttribute方法和getAttribute方法来设置和检索属于各自域范围内的属性,在这4个对象中设置的属性对象的作用域是不同的。存储在application对象中的属性可以被同一个Web应用程序中的所有Servlet和JSP页面访问;存储在session对象中的属性可以被属于同一个会话的所有Servlet和JSP页面访问;存储在request对象中的属性可以被属于同一个请求的所有Servlet和JSP页面访问,例如,使用PageContext.forward和PageContext.include方法连接起来的多个Servlet和JSP页面;存储在pageContext对象中的属性仅可以被当前JSP页面的当前响应过程中调用的各个组件访问,例如,正在响应当前请求的JSP页面和它调用的各个自定义标签类。application, session, request, pageContext对象中都提供了管理各自域范围内的属性的方法.PageContext类中还提供了对各个域范围的属性进行统一管理的方法,以简化对各个域范围内属性的访问。
l setAttribute
void setAttribute(String name, Object value)
void setAttribute(String name, Object value, int scope)
第一个setAttribute方法将属性值存放在page范围的域对象中。如果PageContext对象中已经存在指定名称的属性,则覆盖原来的属性。如果传递给setAttribute方法的属性值对象为null,则删除指定名称的属性,这时的效果等同于removeAttribute方法。第二个setAttribute方法用于将一个对象与一个名称关联后存储进scope参数指定的域范围中,域范围用PageContext类中定义的4个整数常量来表示,它们分别是:APPLICATION_SCOPE,SESSION_SCOPE,REQUEST_SCOPE,PAGE_SCOPE
l getAttribute
void getAttribute(String name)
void getAttribute(String name, int scope)
第一个getAttribute方法从page范围的域对象中返回指定名称的属性对象;第二个getAttribute方法用于从scope参数指定的域中返回。
l removeAttribute
void removeAttribute(String name)
void removeAttribute(String name, int scope)
第一个removeAttribute方法从当前PageContext对象中删除指定名称的属性对象;第二个getAttribute方法用于从scope参数指定的域中删除。
l findAttribute
findAttribute方法用于依次从page、request、session、application 4个域范围中查找某个指定的属性,找到后返回,如果4个域中都没有找到则返回null。
pushBody方法与popBody方法
PageContext类中有一对方法,即pushBody方法和popBody方法来管理嵌套的JspWriter流,以便支持扩展标签的功能。PageContext对象内部使用一个“out”属性来指向一个JspWriter对象,PageContext.getOut()方法的返回值就是“out”属性所指向的JspWriter对象,在第一次调用pushBody方法前,“out”属性指向JSP页面的内置out对象,pushBody方法用于返回一个新产生的BodyContent对象,并让PageContext对象中的“out”属性指向这个新产生的BodyContent对象和将“out”属性原来指向的JspWtiter对象保存起来,此后的PageContext.getOut()方法返回的结果将是pushBody方法内产生的那个BodyContent对象。BodyContent是JspWriter的一个子类,它提供了比JspWriter更多的功能,例如,将写入到它里面的内容转换成一个字符串、清空写入到它里面的内容,以及将它里面的内容写入到另外一个JspWriter对象中。对于每一个pushBody方法调用,最后都要调用一个对应的popBody方法来恢复pushBody方法调用前的状态,popBody方法将PageContext对象中的"out"属性重新指向上次的pushBody方法保存起来的JspWriter对象,并返回这个JspWriter对象。
JSP开发人员通常不必调用pushBody与PopBody方法,而是由JSP引擎将JSP页面翻译成Servlett源程序时生成调用这两个方法的代码,但是,了解这两个方法后将有助于理解带标签体的自定义标签的工作方式。
<%@ page contentType="text/html;charset=gb2312"
import="javax.servlet.jsp.tagext.BodyContent"%>
<%
JspWriter anotherOut = pageContext.getOut();
if (anotherOut == out) {
out
.println("1. 初始的pageContext.getOut方法的返回值"
+ "就是隐式out对象。<br>");
}
BodyContent out1 = pageContext.pushBody();
JspWriter out2 = pageContext.getOut();
if (out1 == out2) {
out.println("2. pageContext.pushBody方法与它后面调用的"
+ "pageContext.getOut方法的返回值相同,");
}
if (out2 != out) {
out.println("这个返回值不等于隐式out对象,");
out2.println("此时写入到pageContext.getOut方法返回的"
+ "JspWriter对象中的内容不会发送给客户端,但是以后可以"
+ "将这些内容直接写入到另外一个字符输出流对象中,或者"
+ "将这些内容以字符串返回后进行其他方式的处理!<br>");
/*BodyContent.getEnclosingWriter方法的返回值就是调用pushBody
方法前的PageContext对象中的“out”属性指向的JspWriter对象,
此时out2.getEnclosingWriter方法的返回值就是隐式out对象,
下面的代码并没有按常理来编写,就是为了让读者更好地理解out1、
out2、out2.getEnclosingWriter方法的返回值、out等几者间的关系。 */
out1.writeOut(((BodyContent) out2).getEnclosingWriter());
out.println("将上面内容以字符串返回后再输出一遍:" + out1.getString());
}
JspWriter out3 = pageContext.popBody();
JspWriter out4 = pageContext.getOut();
if (out3 == out4) {
out.println("3. pageContext.popBody方法与它后面调用的"
+ "pageContext.getOut方法的返回值相同,");
}
if (out4 == out) {
out.println("这个返回值又等于隐式out对象了。<br>");
}
%>
输出:
1.初始的pageContext.getOut方法的返回值就是隐式out对象。
2. pageContext.pushBody方法与它后面调用的pageContext.getOut方法的返回值相同,这个返回值不等于隐式out对象,此时写入到pageContext.getOut方法返回的JspWriter对象中的内容不会发送给客户端,但是以后可以将这些内容直接写入到另外一个字符输出流对象中,或者将这些内容以字符串返回后进行其他方式的处理!
将上面内容以字符串返回后再输出一遍:此时写入到pageContext.getOut方法返回的JspWriter对象中的内容不会发送给客户端,但是以后可以将这些内容直接写入到另外一个字符输出流对象中,或者将这些内容以字符串返回后进行其他方式的处理!
3. pageContext.popBody方法与它后面调用的pageContext.getOut方法的返回值相同,这个返回值又等于隐式out对象了。
JSP页面中的转义
EL表达式的中的转义:
“${”、“}”不需要转义,在字符串中它们不会被看作是EL表达式的起始与结尾。如 ${‘${‘}、${"${"}、${‘}‘}、${"}"}都是合法的。
JSP脚本元素内的转义:
<% out.println("use <% and %\>"); %> ,输出 use <% and %>。
JSP模板元素内的转义:
use <\% and %> ,输出 use <% and %>。
为了让JSP引擎不要把JSP模板元素内的字符序列“${”当作EL表达式的开始,在支持EL表达式的JSP页面中,“$”可以使用“\$”来表示,但不是必须的(因为单个“$”不是EL表达的开始标识,所以可以不必要)。但如果表达字面意义上的“${”时,就必须使用“\${”。如果要表达字面意义上的“\$”,则必须使用“\\$”。
JSP标签的属性内转义:
如果使用单引号将属性值部分引起来,那么属性值中的单引号要用“\‘”表示。
如果使用双引号将属性值部分引起来,那么属性值中的双引号要用“\"”表示。
反斜杠要用“\\”表示。
在支持EL表达式的JSP页面中,“$”可以使用“\$”来表示。
“<%”要用“<\%”表示,“%>”要用“%\>”表示。
JSP中文乱码问题
JSP引擎将JSP源文件翻译成Sevlet源文件时默认采用的UTF-8编码,即生成的Servlet源文件是以UTF-8编码格式存储的。
如果JSP源文件中没有指定编码格式,即没有指定 pageEncoding 属性,也没有指定 ContentType 属性,此时JSP引擎在将JSP源文件以字节流读取到内存后再以ISO8859-1的编码方式将字节流转换成Unicode字符。
page指令的contentType属性除了具有指定响应正文的字符集编码的作用外,它还具有说明JSP源文件的字符集编码的作用。JSP规范建议尽量不要使用Page指令的contentType属性来说明JSP源文件的字符集编码,而是使用Page指令的pageEncoding属性或在部署描述符中说明JSP源文件的字符集编码。如果设置了page指令的pageEncoding属性,page指令的contentType属性就不再具有说明JSP源文件的字符集编码的作用了。
JSP2.0中支持在web.xml配置文件中设置JSP源文件的编码格式,这样就不需要在每个JSP页面里通过指令的属性来设置了:
<jsp-config>
<jsp-property-group>
<url-pattern>/jsp/*</url-pattern>
<page-encoding>GB2312</page-encoding>
</jsp-property-group>
</jsp-config>
当JSP引擎要确定某个JSP文件的字符集编码时,首先在Web应用程序的部署描述符中查找是否有与该JSP文件匹配的<page-encoding>元素设置,如果没有找到则在JSP源文件中查找page指令的pageEncoding属性。如果没有找到<page-encoding>元素设置和page指令的pageEncoding属性,JSP引擎接着在page指令的contentType属性中查找字符集编码,如果还没找到则使用默认的ISO8859-1作为JSP源文件的字符集编码。可以同时设置<page-encoding>元素和pageEncoding属性,但两者的设置值必须相同。
尽管page指令的contentType,属性和pageEncoding属性都可以说明JSP源文件的字符集编码,但是,JSP引擎必须只有读取到这样的page指令后才能知道JSP源文件的字符集编码,在读取到这些page指令之前,JSP引擎是无法知道JSP源文件的字符集编码的。由于英文ASCII码字符在UTF-8编码和各个国家的本地字符集编码中的值完全一样,所以JSP引擎总是先按UTF-8编码来读取JSP源文件中的内容,这就要求设置page指令的contentType属性和pageEncoding属性的语句之前不能有非UTF-8编码的字符,最好是在JSP源文件的开始处设置page指令的contentType属性和pageEncoding属性.由于英文ASCII码字符的UTF-16编码和EBCDIC编码与它们的UTF-8编码并不相同(不只占用一个字节),所以,JSP源文件也不能使用UTF-16编码和EBCDIC编码。但可以是GBK、GB2312、ISO8859-1等这些编码,因为这些编码与ASCII码也是完全兼容的,而UTF-8与是与ASCII码完成兼容,所以在寻找pageEncoding、contentType属性时以UTF-8编码方式是完全没有问题的。
page指令的contentType属性除了具有ServletResponse.setContentType方法的作用外,还还具有说明JSP源文件的字符集编码的作用,而ServletResponse.setContentType只影响向浏览器输出编码方式,而不会影响JSP源文件本身字符编码。
<jsp:param>标签传递中文参数问题
=================jspparam.jsp=====================
<%@ page pageEncoding="GB2312"%>
<jsp:forward page="getparam.jsp?country1=中国">
<jsp:param name="country2" value="中国" />
</jsp:forward>
=================getparam.jsp=====================
<%
String country1 = request.getParameter("country1");
String country2 = request.getParameter("country2");
%>
country1:<%= country1 %><br>
country2:<%= country2 %><br>
jspparam.jsp页面中跳转语句生成的Servlet源码如下:
if (true) {
_jspx_page_context.forward("getparam.jsp?country1=中国"
+ (("getparam.jsp?country1=中国").indexOf(‘?‘) > 0 ? ‘&‘
: ‘?‘)
+ org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode(
"country2", request.getCharacterEncoding())
+ "="
+ org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode(
"中国", request.getCharacterEncoding()));
return;
}
从上面的生成的代码中可以看到,直接增加在<jsp:forward>标签转发的URL地址后面的参教在被翻译成的Serlvet源文件中保持不变,而使用<jsp:param>标签增加的参数被进行了URL编码处理,显然.当要向<jsp:include>和<jsp:forward>标签引入或将请求转发给的资源传递参数时,最好是使用<jsp:param>标签,而不要直接将参数增加到资源路径的后面。从上面的代码中还可以看到,在对<jsp:param>标签传递的参数进行URL编码时,JSP引擎选择的字符集编码为request.getCharacterEncoding()方法的返回值。所以要解决乱码的根本问题要在jspparam.jsp页面中加上:<%request.setCharacterEncoding("gb2312"); %>语句,在进行URL编码处理前设置好request.getCharacterEncoding。
JSP排错
只要一个JSP页面被成功地翻译和编译成Servlet字节码后,如果以后修改了JSP页面,并且其中的错误导致JSP引擎无法重新翻译和编译成Servlet字节吗,JSP引擎并不会删除上次编译成功的那个旧的Servlet字节码文件。
当客户端访问一个JSP页面时JSP引擎将对相应的JSP文件进行修改检查,如果发生过修改则重新翻译和编译该JSP文件。在JSP引擎对一个JSP页面执行这祥的检查后的一个时间段内,如果客户端又访问该JSP页面,JSP引擎将不再进行修改检查,而是直接查找该JSP页面生成的Servlet字节码,无论新旧,只要找到JSP引擎就调用该Servlet字节码对客户端进行响应。当修改一个JSP页面出现错误后后,不要快速连续刷新浏览器对它的访问,而是每次等特一会后再进行刷新,就能查看到其真实的错误信息。
JSP一般有以下三类问题
1、 JSP页面本身语法错误,导致不能生产Servlet源文件:
<%String str = null; %>
<%=str.length()
2、 JSP页面没有语法问题,但翻译成Servlet类文件时出现了Java语法问题:
<%String str = null %>
<%=str.length()%>
3、 JSP语法与Java语法都没有问题,而是在运行时出现了异常:
JavaBean在JSP中的应用
JavaBean特点:
1、 必须有一个公共的默认构造函数。
2、 属性通过遵循某种规范的公共方法暴露给外部。
属性名的字母必须小写,但它们的在存取方法中首字母一定要大写。
简单属性:
如果是布尔类型,则读取方法也可以使用 is 开头。另外 属性名与方法名后部分不一定要相当,也可以是不相干的两个名字,但一般相同最好。
Indexed属性:
指数组类型的属性。对属性的操作有两种重载形式:一个是对整个数组进行赋值,另外一个则是对数组中的每个元素进行赋值。
对于JSP页面来说,只要一个类具有一个公共的、无参数的构造方法,就可以把这个类当作JavaBean来使用,如果类中有不接受任何参数的getter方法或只接受一个参数的setter方法,就可以把前缀“get”或“set”后面的部分当着一个属性名来引用,例如,JDK自带的java.util.Date类具有一个公共的、无参数的构造方法,其中还包含一个如下定义的方式:
Public void setTime(long time)
因此,在JSP页面可以把java.util.Date类当做一个JavaBean来看待,且认为该JavaBean包含一个名为time的属性。
<jsp:useBean id="currentDate" class="java.util.Date"></jsp:useBean>
<jsp:setProperty property="time" name="currentDate" value="1234567"/>
<jsp:getProperty property="time" name="currentDate"/>
<%= currentDate.getTime() %>
synchronized (_jspx_page_context) {
currentDate = (java.util.Date) _jspx_page_context.getAttribute("currentDate", PageContext.PAGE_SCOPE);
if (currentDate == null){
currentDate = new java.util.Date();
_jspx_page_context.setAttribute("currentDate", currentDate, PageContext.PAGE_SCOPE);
}
}
org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper(_jspx_page_context.findAttribute("currentDate"), "time", "1234567", null, null, false);
out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString((((java.util.Date)_jspx_page_context.findAttribute("currentDate")).getTime())));
<jsp:useBean>标签
用于在某个指定的域范围(application、session、request、page等)中查找一个指定名称的JavaBean对象,如果存在则直接返回该JavaBean对象引用,如果不存在则实例化一个新的JavaBean对象,并将它按指定的名称存储在指定的域范围中。常见语法如下:
<jsp:useBean id="beanInstanceName" class="package.class" scope="page|request|session|application" />
除了id, class和scope属性之外,<jsp:useBeen>标签还有其他两个属性:type和beanName。
.type属性
用于指定JavaBeaa实例对象的引用变量的类型,它必须是JavaBean对象的类名称、超类名称和所实现的接口名称中的一个。type属性的默认值为class属性的设置值,当JSP引擎将<jsp:useBean>标签翻译成Servlet程序中的Java代码时,它将使用type属性值作为JavaBeaa对象的引用变量的类型。
.beanName属性(即可以指向类,也可以指定包含序列化bean对象的文件)
用于指定JavaBean的名称,这个名称不是JavaBean实例对象的引用名称,而是JavaBeans规范中定义的一种名称,它将被作为传递给java.beans.Beans类的instantiate方法的参数,例如,a.b.c,a.b.c可以代表一个类的完整名称,也可以代表a/b/c.ser这样的资源文件,java.beans.Beans类的instantiate方法可以从这个资源文件中产生出JavaBean的实例对象。beanName属性值还可以通过一个表达式来产生。
<jsp:useBean>标签的完整语法如下:
<jsp:useBean id="beanInstanceName" scope="page|request|session|application"
{
class="package.class" |
type="package.class" |
class="package.class" type="package.class" |
beanName="{package.class | <%=expression%>}" type="package.class"
}/>
<jsp:uaeBeao>标签首先在指定的域范围中查找指定名称的JavaBean实例对象,如果能够找到,这时候只需要为<jsp:useBean>标签设置type属性或class属性,以便JSP引擎在JSP页面所翻译成的Servlet源文件中设置该JavaBean对象的引用变量的类型。如果在指定的域范围中查找不到指定名称的JavaBean实例对象,那么必须设置class属性或beanName属性来指定产生JavaBean实例对象的类名或资源名,class属性和beanName属性不能同时设置。因为type属性的默认值为class属性的设置值,所以设置class属性时可以省略type属性,而设置beanName属性时必须同时设里type属性。
<jsp:useBean>标签除了可以按照空元素的方式进行使用外,它还可以按容器元素的格式进行使用,如下所示:
<jsp:useBean ...>
Body
</jsp:useBean>
这种格式被称之为带标签体的<jsp:useBean>标签,Body部分的内容只在<jsp:useBean>标签创建JavaBean的实例对象时才执行。也就是说,如果在<jsp:useBean>标签的scope属性指定的域范围中存在id属性所指定的JavaBean对象,<jsp:useBean>的开始标签和结束标签之间的Body内容将被忽略,否则,Body部分的内容将被执行。
由于带包名的类无法调用不带包名的类,且JSP页面所翻译成Servlet程序都带有包名,所以,JavaBean必须带有包名,不能用默认包名,否则不能访问。
<jsp:setProperty>标签
用于设置JavaBean对象的属性,其使用语法如下:
<jsp:setProperty name="beanInstanceName"
{
property="propertyName" value="{string | <%= expression %>}" |
property="propertyName" [param="parameterName"] |
property="*"
}/>
.name属性是必不可少的,它用于指定JavaBean实例对象的名称,其值应与<jsp:useBean>标签的id属性值相同。
.property属性是必不可少的,它用于指定JavaBean实例对象的属性名。
.value属性是可选的,它用于指定JavaBean实例对象的某个属性的值。value属性的设置值可以是一个字符串,也可以是一个表达式。如果value属性的设置值是字符串,那么它将被自动转换成所要设置的JavaBean属性的类型。例如,通过Boolean.valueOf方法将“true”转换成Boolean和boolean类型,通过lntege.valueOf方法将 “123”转换成Integer和int类型。如果value属性的设置值是一个表达式,那么该表达式的结果类型必须与所要设置的JavaBean属性的类型一致(注,使用表达式很有用,因为如果Bean的某个属性不是一个字符串、基本类型及包装类型外,则要使用这种方式赋值,因为使用value方式赋值时不能)。
.pararm属性是可选的,它用于将一个请求参数的值赋值给JavaBean实例对象的某个属性,它可以将作为字符串类型返回的请求参数值自动转换成要设置的JavaBean属性的类型。如果当前请求消息中没有Param属性所指定的请求参数,那么<jsp:setproperty>标签什么事情也不做,它不会将null值斌给JavaBean属性,所设置的JavaBean属性仍将等于其原来的初始值,以保证JavaBean属性的初始值不被破坏。value和pamm属性不能同时使用,只能使用其中任意一个。
<jsp:setProperty name="beanInstanceName" property="propertyName"/>
这种形式将JavaBean实例对象的某个属性值设置为与该属性“propertyName”同名的请求参数值,它等效于param属性的设置值与property属性值相同时的情况。也就是说,如果<jsp:setProperty>标签的param属性和value属性都没有设置,则表示将当前要设里的JavaBean实例对象的属性设置为与该属性同名的请求参数值。
<jsp:setProperty name="beanInstanceName" property="*" />
这种形式用于对JavaBean实例对象中的多个属性进行赋值,它表示将请求消息中的参数逐一与JavaBean实例对象中的属性进行比较,如果找到同名的属性,则将该参数值赋给该属性。如果在请求消息中不存在与JavaBean中的某个属性同名的参数,那么JavaBean的这个属性将不会被赋值。
<jsp:getProperty>标签
用于读取JavaBean对象的属性。
<jsp:getProperty name="beanInstanceName" property="PropertyName" />
name属性值要与<jsp:useBean>标签的id属性值相同,如果属性值为null,则结果为“null”字符串。
Other
request.getInputStream() -> ServletInputStream
request.getReader() -> BufferedReader
response.getOutputStream() -> ServletOutputStream
response.getWriter() -> PrintWriter
response.getCharacterEncoding(charset)
request.getCharacterEncoding(charset)
response.setCharacterEncoding(charset)
request.setCharacterEncoding(charset)
jsp内置对象out.print()、out.println()输出到客户端的效果是一样的,都不会在页面上显示换行,如果要显示换行效果,请输出"<br>",但println()有点不一样,就是在生成HTML源码时,在输出后再输出回车换行,源码会换行:out.newLine()=out.println()=out.write("\r\n") 在输出源码时都会回车换行。
使用base href 指定静态页面上的相对超链接地址(不以“/”开头的地址)的基准地址:
写<%=request.getContextPath() %>太麻烦,可以在每一个jsp文件顶部加入以下代码
<% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %><base href="<%=basePath%>">
因为一个静态页面可以以多种途经去访问,比如通过RequestDispatcher.forward方式、直接在地址栏输入静态页面地址进行访问、将静态页面映射成一个Servlet再访问,此时地址栏中的地址是不一样的,但此静态页面里的相对路径资源所在的路径是固定的,所以此时使用<base>标签设置一个基准点,不管你以任种形式访问,都不会影响页面中超链接的相对地址。
Eclipse插件安装
将所有的插件用一个外部目录存放起来,假如是D:\plug-in,将上面所示的插件目录文件全部拷贝到该目录下,比如Tomcat插件,此时的文件路径就是D:\plug-in\tomcat_plug\eclipse\plugins\ com.sysdeo.eclipse.tomcat_3.1.0.beta(请注意,方法四一定要严格这样的目录路径放置文件)。然后在$ Eclipse_Home$下新建一个links目录,并在links目录下建立关联文件,假如是tomcat.link,在建立的关联文件中加入如下语句:
path=D:\\plug-in\\tomcat_plug
也可以写成下面的形式
path=D:/plug-in/tomcat_plug
还可以写成相对路径的形式
就是重启Eclipse,在Dos窗口下进入Eclipse安装目录,键入命令eclipse -clean,回车,或者进入$Eclipse_Home$/configuration目录,删除org.eclipse.update后再重新启动Eclipse。
创建Tomcat源码工程
SVN插件地址: http://subclipse.tigris.org,在主页上点击左则 “Download and Install”,就可以看到各个Eclipse版本对应的 SVN 插件在线更新地址。
进入 http://tomcat.apache.org/ 网站找Tomcat源码所在的SVN库地址,在左则点击“SVN Repositories”,就可以找到Tomcat源码库的SVN地址。通过SVN插件连上库,在库中的 tags 选择要下载的版本。比如我这里选择的是 tags/TOMCAT_5_5_30,再一路next,中间会让你新建项目,此进创建一个Java项目即可。等下载完后,将工程目录下的 eclipse.classpath 里的内容复制到 .classpath 中。最后一步是下载工程所依赖的包,我们打开 build/build.properties.default 文件,修改 base.path 用来存储下载的依赖库,最后执行 build.xml 文件中的 download 任务开始下载所依赖的包,
Tomcat远程调试
修改catalina.bat 中,并在启动Java虚拟机之前加上:
SET CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8787
访问WEB-INF下的网页或jsp文件
怎么样在JSP页面或Servlet里访问WEB-INF下的网页或jsp文件呢?因为应用服务器会禁止掉浏览器直接对WEB-INF目录里的文件进行访问。解决方案是通过通过容器内部跳转:
request.getRequestDispatcher("/WEB-INF/xx.jsp").forward(request, response);
第二种方案:
在web.xml文件添加:
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
现在我们如果访问这些html文件时,它们会被转换成xx_html.java类似的Servlet文件。
注意,WEB-INF可以通过forward的方式来访问,但不能通过redirect方式来访问。
Servlet的自启动
web.xml文件中的<servlet>元素可以嵌套一个名为<load-on-startup>的子元素,这个子元素用于指定Servlet被装载的时机和顺序。如果<load-on-startup>元素中的内容被设置为0或一个正整数,它指定该Servlet应该在Web应用程序启动时就被实例化和调用它的init()方法,且这个数字越小,Servlet被装载的时间也越早,相同数字的Servlet之间的装载顺序由Servlet引擎自己决定。如果<load-on-startup>元素中的内容被设置成了一个负整数,则由Servlet引擎自己决定在什么时候装载该Servlet。
Servlet的自动重新加载
在Servlet的整个生命周期内,Servlet只被初初始化一次,它的init方法也中被调用一次,但service方法会被调用多次。对于每次的请求访问,Servlet引擎都会创建一个新的HttpServletRequest请求对象和HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法。
如果编程人员对某个已被装载的Servlet进行了修改,除非重新启动Tomcat,否则,客户端以后访问到的Servlet实例对象还是修改前的Servlet版本。但Tomcat提供了是否自动重新装载被修改的Servlet的配置选项。在server.xml文件,可以使用<Context>元素有一个reloadable属性,当它为true时,Tomcat将监视该Web应用程序的 WEB-INF/classes和/WEB-INF/lib目录下的类是否发生了改变,然后自动重新装载那些发生了改变的类,默认值为false。