JSP基础
1 JSP(Java Server page)技术
静态HTML文件简洁直观表达网页外观,但无法实现交互;Servlet可以实现动态交互,但开发人员必须通过编写Java代码的方式输出HTML文档,繁琐且难以理解,尤其对于内容庞大且布局复杂的网页。JSP技术集合了HTML和Servlet的优点,大大简化了动态生成网页的工作。
2 JSP本质
JSP本质就是一个Servlet类。当JSP页面被访问时,容器按照以下流程处理客户请求
1)查找与JSP文件对应的Servlet,如果已经存在,就调用它的服务方法。
2)如果与JSP文件对应的Servlet不存在,就解析文件系统中的JSP文件,将它翻译成Servlet源文件,再将源文件编译成.class文件,然后再创建该Servlet对象,初始化,并运行Servlet。
Tomcat将由JSP翻译生成的.java文件以及.java编译之后的.class文件存放于work目录下。通常情况下,开发和调试Web应用阶段,如果修改了JSP文件,Tomcat会重新翻译JSP,编译生成的新文件(一般容器会自动检测更新,继而自动生成新的Servlet源文件并进行编译,然后再运行新生成的Servlet)。覆盖work目录下的原来旧文件。如果浏览器仍然看到的是旧网页,可手工删除work目录下相关信息,确保Tomcat重新编译修改后的JSP文件。
index.jsp文件生成的index_jsp.java文件如下。
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent { private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private javax.el.ExpressionFactory _el_expressionfactory; private org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public void _jspInit() { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); } public void _jspDestroy() { }
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null; final javax.servlet.ServletContext application; final javax.servlet.ServletConfig config; javax.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; javax.servlet.jsp.JspWriter _jspx_out = null; javax.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html;charset=ISO-8859-1"); 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.write('\r'); out.write('\n');
String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
out.write("\r\n"); out.write("\r\n"); out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n"); out.write("<html>\r\n"); out.write(" <head>\r\n"); out.write(" <base href=\""); out.print(basePath); out.write("\">\r\n"); out.write(" \r\n"); out.write(" <title>My JSP 'index.jsp' starting page</title>\r\n"); out.write("\t<meta http-equiv=\"pragma\" content=\"no-cache\">\r\n"); out.write("\t<meta http-equiv=\"cache-control\" content=\"no-cache\">\r\n"); out.write("\t<meta http-equiv=\"expires\" content=\"0\"> \r\n"); out.write("\t<meta http-equiv=\"keywords\" content=\"keyword1,keyword2,keyword3\">\r\n"); out.write("\t<meta http-equiv=\"description\" content=\"This is my page\">\r\n"); out.write("\t<!--\r\n"); out.write("\t<link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\">\r\n"); out.write("\t-->\r\n"); out.write(" </head>\r\n"); out.write(" \r\n"); out.write(" <body>\r\n"); out.write(" This is my JSP page. <br>\r\n"); out.write(" </body>\r\n"); out.write("</html>\r\n"); } catch (java.lang.Throwable t) { if (!(t instanceof javax.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { out.clearBuffer(); } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } } |
3 JSP语法
JSP由html(*.html,*.htm一般前端设计人员设计),Java程序片段(JavaScriptlet也叫Java脚本), JSP标记组成。
JSP虽然是一个Servlet,但有着不同于Java编程语言的专门的语法,该语法特点尽可能地用标记取代Java程序代码,使整个JSP文件在形式上不像Java程序,而像标记文档。
JSP语法组成:HTML文本,JSP指令,JSP声明, Java程序片段,Java表达式,JSP隐含对象,JSP注释。
Ø HTML(查看Servlet类对应的代码)
JSP本身是将其他语法表达嵌入到HTML中。JSP中HTML文本称为模板文本,最终会在对应Servlet类的service方法中,由out.write()原样输出。模板文本还可以为纯文本。可通过设置<%@page contentType=”text/plain”%>标识JSP模板文本为纯文本。
Ø JSP声明(查看Servlet类对应的代码)
JSP声明在<%! %>中定义,用于声明与JSP对应的Servlet类的成员变量和方法。
案例演示:
注意:每个JSP声明只在当前JSP文件有效,如果希望在多个JSP文件中都包
含这些声明,可以把这些声明语句写到一个单独的JSP文件中,然后在其他JSP
文件中用include指令将该文件包含即可。
Ø Java程序片段(查看Servlet类对应的代码)
JSP文件中,可以在<% %>标记间直接嵌入Java程序代码。
如果在page指令中没有指定method属性,嵌入的Java代码默认在与JSP对应的Servlet类的service方法中。
案例:
Ø jsp表达式(查看Servlet类对应的代码)
Java表达式标记为:<%= %>。等同于out.write
在JSP中使用该标记,它能把表达式的值输出到网页上。表达式中的int或float类型的值都自动转换成字符串进行输出。
Ø JSP指令(Directive) (查看Servlet类对应的代码)
JSP指令语法格式: <%@ 指令名 attribute=”value”…%> 用来设置和整个JSP网页相关的属性,比如网页的编码方式和脚本语言等。一般都会把JSP指令放到JSP文件的开头(不是必须的)。
JSP常用的三大指令为page, include, taglib。
² page指令
最常用的指令,涉及的属性也很多。在JSP页面中,指令都可以多次出现
1)page指令的language属性
<%@ page language=”java”%>,指定文件中的程序代码所使用的编码语言,目前java是唯一的有效值和默认值。该指令作用于整个文件。多次使用该指令,只有第一次使用有效。
2)page指令的import属性
<%@ page import=”java.io.*,java.util.*”%>
或
<%@ page import=”java.io.*”%>
<%@ page import=”java.net.*”%>
指定导入的java软件包名或类名列表,该列表使用逗号分隔。可多次使用该指令导入不同的软件包。import属性是唯一可以重复出现的属性。
对应Servlet中,import语句。
3)page指令的contentType和pageEncoding属性
<%@ page contentType="text/html;charset=utf-8"%>对应Servlet中response.setContentType("text/html; charset=utf-8")用于设置响应字符流的编码和MIME响应头。
<%@ page pageEncoding=”utf-8”%>指定当前JSP页面的编码。该编码给服务器看,服务器需要知道当前JSP使用的编码,不然服务器无法正确把JSP编译成java文件。
如果两个都不出现,默认值为ISO-8859-1。该编码无法显示中文,需设置这两个属性。这两个设置其中一个,另一个值与出现的值相同。
当多次出现该指令,第一次有效。
4)page指令的errorPage和isErrorPage属性
当一个JSP页面出错后,Tomcat会响应500错误页面。为了更给用户更友好的错误界面,此时可以使用page指令errorPage自定义错误页面。<%@ page errorPage=”error.jsp”%>,如果当前页面出错,会自动请求转发到error.jsp页面。
error.jsp中可使用<%@ page isErrorPage=”true”%>表示该页面为专门处理异常的页面。设置后,可以在error.jsp中使用一个exception内置对象,其他页面不能使用该内置对象。
web.xml中配置错误页面:
除了使用page指令配置错误页面,还可以配置web.xml,指定错误处理。此种方式与page指令无关。
<web-app> <error-page> <error-code>500</error-code> <location>/err500.jsp</location> </error-page> <error-page> <error-code>404</error-code> <location>/err404.jsp</location> </error-page> <!-- JSP如果抛出为配置的excpetion-type指定类型的异常时,会转到服务器端错误error500.jsp中 --> <error-page> <exception-type>java.lang.RuntimeException</exception-type> <location>/error.jsp</location> </error-page> </web-app> |
5)page指令的session属性
<%@ page session=”true”%>
指定JSP页是否使用Session,默认为true。
6)page指令的method属性
<%@ page method=”doPost”%>
指定Java程序片段所属的方法名。如果不指定,默认<% %>中的代码存放在service方法中。当多次使用该指令时,第一次有效。该属性值包括:service, doGet, doPost
² include(查看Servlet类对应的代码)
JSP可通过include指令包含其他文件内容。该种包含叫静态包含,目的就是把多个JSP或HTML文件合并成一个JSP文件。
include指令语法格式:
<%@ include file=”目标组件的绝对URL或相对URL”%>
使用场景: 如果多数网页包含相同内容,可把相同内容单独放到一个文件中,其他JSP文件通过include指令将这个文件包含进来。提高代码复用,提高开发效率,便于维护。
本质上,include指令包含(静态包含),是在JSP翻译成Servlet前,将多个页面合并成一个文件,翻译成一个Servlet。
案例:
² taglib(查看Servlet类对应的代码)
JSP使用第三方的标签库时,使用taglib指令导包。以后讲解
Ø JSP隐含对象(查看Servlet类对应的代码)(记住每个对象,每个对象类型,作用)
JSP中包含九大内置对象(JSP中无需创建即可使用的对象)。
² request
request隐含对象的类型javax.servlet.HttpServletRequest。常用
² response
response隐含对象的类型javax.servlet.HttpServletResponse。常用
² pageContext(仅仅在当前页面有效)
pageContext隐含对象的类型javax.servlet.jsp.PageContext。常用,可以获得当前页面的上下文
² application
application隐含对象的类型javax.servlet.ServletContext。常用
² out
out隐含对象的类型javax.servlet.jsp.JspWriter。等同于response.getWriter(),向客户端发送信息。常用
² config
config隐含对象的类型javax.servlet.ServletConfig。不常用,当前servlet对应的配置信息
² page
相当于Java中的this,当前JSP本身。不常用
² session
session隐含对象的类型javax.servlet.http.HttpSession。常用,后面讲。只有在<%@page session=”true”%>的JSP中可用。默认为true。
² exception
exception隐含对象的类型java.lang.Exception。只有在<%@ page isErrorPage=”true”%>的JSP中可以使用。
JSP对应的源码:
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { //隐含对象,全部都是service方法的局部变量 final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null; final javax.servlet.ServletContext application; final javax.servlet.ServletConfig config; javax.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; javax.servlet.jsp.JspWriter _jspx_out = null; javax.servlet.jsp.PageContext _jspx_page_context = null;
try {//头信息设置 response.setContentType("text/html;charset=UTF-8"); 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; //从此开始,上面已经定义好了隐含对象在JSP编写代码时,相当于放在隐含对象 //定义的下面,当然可以自动使用隐含对象了。 //JSP页面内容显示,所有的HTML代码都以out.write()输出。 } } |
Ø JSP注释(查看Servlet类对应的代码)
JSP注释:<%-- --%>,也可在<% %>内使用Java注释方式。
<!-- HTML注释, 浏览器端不显示,解析HTML代码时,过滤 -->
<body>
<%-- 表示JSP注释,将JSP翻译成Servlet类是,就过滤了。 --%>
<%
//可用 编译后,.class文件中不包含
/* 可用 */
%>
</body>
4 JSP生命周期
JSP生命周期包含以下阶段:
1) 解析阶段:Servlet容器解析JSP文件的代码,如果有语法错误,就会向客户端返回错误信息
2) 翻译阶段:Servlet容器把JSP文件翻译成Servlet源文件(.java文件就是一个Servlet类)
3) 编译阶段:Servlet容器编译Servlet源文件,生成Servlet类(.class)
4) 初始化阶段:加载与JSP对应的Servlet类,创建实例,并调用初始化方法
5) 运行时阶段:调用与JSP对应的Servlet实例的服务方法
6) 销毁阶段:调用JSP对应的Servlet实例的销毁方法,再销毁Servlet实例。
注意:JSP生命周期中,解析,翻译,编译是区别于Servlet的,这三个阶段在以下场合会执行。
a) JSP文件被客户端首次请求访问。
b) JSP文件更新
c) JSP对应的Servlet类的类文件被手工删除
JSP相关接口及方法分析:
JSP对应的Servlet类实现了javax.servlet.jsp.JspPage接口,而JspPage接口继承自javax.servlet.Servlet接口。在JspPage接口中定义了jspInit,jspDestroy,作用与Servlet接口的init, destroy方法相同。在开发自己的JSP文件时,可以重写jspInit, jspDestroy方法。与JSP对应的Servlet类的_jspService()方法则由Servlet容器根据JSP源文件自动生成。
案例分析:以下代码是否正确?
<html><head><title>life.jsp</title></head><body> <%! File tempDir = null; publicvoid jspInit(){ tempDir = (File)application.getAttribute("javax.servlet.context.tempdir"); } %> 工作目录: <%= tempDir.getPath() %> </body></html> |
分析:容器在编译时会产生错误。因为application变量是JSP对应的_jspService方法中定义的局部变量,其他任何方法中无法调用。此时,可以在jspInit方法中,通过this.getServletConfig().getServletContext()方法得到ServletContext对象。
a)请求转发
JSP和Servlet一样,也可以进行请求转发。
JSP请求转发语法: <jsp:forward page=”转发的目标组件相对或绝对URL”/>
注意:
<%! %>,<% %>,<%= %>这 三种形式叫做JSP脚本元素(scripting element)。把<jsp:forward >,<jsp:include >,<jsp:useBean >等jsp前缀开头的称为JSP动作标签,用于简化Java脚本。
Servlet请求转发的特点同样适用于JSP。即:JSP源组件和目标组件共享HttpServletRequest和HttpServletResponse对象,JSP源组件中的所有输出数据都不会发送到客户端。有一点需要注意:servlet请求转发forward方法之后的代码会被执行,而JSP源组件<jsp:forward>标签之后的代码不会执行。原因在于
该动作标签对应的servlet源文件中的程序代码为:
if(true){
_jspx_page_context.forward(“target.jsp”);
return;//因为有return,所以之后的代码不会被执行。
}
转发源组件可以通过<jsp:param>标签向转发目标组件传递额外的请求参数。在目标组件,使用getParameter方法
2 包含
JSP中可使用include指令和include动作标签实现包含
include指令语法:此种形式叫静态包含
<%@ include file=”包含的目标组件的相对URL或绝对URL”%>
include动作标签语法:此种形式叫动态包含
<jsp:include page=” 包含的目标组件的相对URL或绝对URL”/>
Ø 静态包含
案例
静态包含按照以下流程响应客户端请求:
1)解析a.jsp。当解析到<%@ include file=”b.jsp” %>时,将b.jsp所有源代码融合到a.jsp中。合并后a.jsp代码如下:
<h1>including before</h1> <% int var = 1; request.setAttribute("username", "Tom"); %> <h1>output from target</h1> var = <%=var%> <br/> username=<%=request.getAttribute("username") %> <h1>include directive after...</h1> |
2)将合并后的JSP源代码翻译为Servlet源文件,再把它翻译为Servlet类。
3)初始化a.jsp对应的Servlet,再运行它的service
注意:
1-静态包含发生在解析JSP源组件阶段,被包含的目标文件中的内容,被原封不动地添加到JSP源组件中,Servlet容器再对JSP源组件进行翻译和编译。
2-静态包含的目标组件可以为HTML或JSP文件,但不允许为Servlet。如果目标组件为JSP文件,则该目标JSP文件可以访问源组件中定义的局部变量,因为JSP源组件和JSP目标组件对应同一个Servlet。
3- 实际开发中,为了区分JSP源组件及被静态包含的JSP或HTML目标组件,一般会将被静态包含的JSP文件用”.jspf”作为扩展名,被静态包含的HTML文件用”.htmf”作为扩展名;或者被静态包含的JSP或HTML文件都用“.inc”作为文件扩展名。
注意点:
静态包含,目标文件如果为html,html中如果包含中,则一直会显示乱码。解决方案:
可以在自己项目的web.xml中配置如下信息。
<jsp-config>
<jsp-property-group>
<url-pattern>*.html</url-pattern>
<page-encoding>utf-8</page-encoding>
</jsp-property-group>
</jsp-config>
Ø 动态包含
动态包含:目标组件和源组件分别对应不同的Servlet。与Servlet中学习的dispatcher.include()方法一样。
动态包含运行流程:
1)解析a.jsp,将其翻译为Servlet源文件。<jsp:include page=””>被翻译成
xxx.runtime.JspRuntimeLibrary.include(req,resp,”b.jsp”,out,false);
2)把Servlet源文件编译为Servlet类。
3)初始化与a.jsp对应的Servlet对象,再运行它的service方法
4)a.jsp对应的Servlet方法会调用 xxx.runtime.JspRuntimeLibrary.include(req,resp,”b.jsp”,out,false);
方法,当Servlet容器执行该方法时,会解析b.jsp,把b.jsp翻译为Servlet源文件,再编译成Servlet类,生成Servlet对象,初始化该对象并调用它service方法。
5)当Servlet容器执行完xxx.runtime.JspRuntimeLibrary.include(req,resp,”b.jsp”,out,false);
方法后,继续执行a.jsp代表的Servlet服务方法中的后续代码。
注意:
1- 动态包含发生在运行JSP源组件阶段,动态包含的目标组件可以为HTML文件,JSP文件或Servlet。如果目标组件为JSP,Servlet容器会在运行JSP源组件的过程中,运行与JSP目标组件对应的Servlet服务方法。JSP目标组件生成的响应结果被包含到JSP源组件的响应结果中。
2- jsp:include标签还有一个flush属性,可选值为true和false。如果为true,就表示源组件在包含目标组件前,先把已经生成的响应正文提交给客户。默认为false
3静态包含和动态包含练习:
静态包含通常用来包含不会发生变化的网页内容(HTML),而动态包含通常用来包含会发生变化的网页内容。做如下两个内容。home.jsp, product.jsp 如图
页面布局分析:除了内容部分不同,其他都相同。
改进一:以上代码有大量冗余,增加开发成本与维护成本。此时可以将两个jsp文件中相同部分放在单独的JSP或html文件中,然后在home.jsp,product.jsp将相同部分通过静态或动态包含进来。
为了解决以上问题,分析代码结构:
改进二:以上代码中home.jsp,product.jsp依然包含很多重复代码。根据所学知识,可将home.jsp,product.jsp相同的部分抽取出来
1 JSP异常处理
JSP提供了一种异常处理机制:可以在当前JSP中通过指令<%@ page errorPage=”error.jsp” %>,指定一个专门处理异常的页面。如果JSP在运行时抛出异常,则交给error.jsp处理该异常。但前提是error.jsp页面必须通过如下指令:<%@ page isErrorPage=”true” %>声明自己是异常处理页面,此时,在error.jsp页面中,可直接访问exception隐含对象,获得当前异常的详细信息。
注意:抛出异常的JSP文件与处理异常的JSP网页之间是请求转发关系。因此可以共享请求范围内的共享数据。
2 发布JSP
jsp也可以向servlet一样,在web.xml中配置<servlet><servlet-mapping>,给JSP映射URL。可通过url访问JSP文件。
案例:
web.xml <servlet> <servlet-name>sum</servlet-name> <jsp-file>/sum.jsp</jsp-file> </servlet> <servlet-mapping> <servlet-name>sum</servlet-name> <url-pattern>/sum</url-pattern> </servlet-mapping> |
3PageContext抽象类
javax.servlet.jsp.PageContext.PageContext类型的对象由Servlet容器创建,JSP文件中可以直接通过隐含对象pageContext使用。该类提供了很多实用的方法。应用场合主要在JSP文件中的Java程序片段,JSP文件中的自定义标签处理类。
PageContext类方法可分为以下几种:
1) 向各种范围内存取属性的方法(page, request, session, application)
getAttribute(String name):返回页面范围内的特定属性值
getAttribute(String name, int scope):返回参数scope指定范围内的特定属性值
setAttribute(String name, Object obj, int scope):向参数scope指定的范围内存放属性。
removeAttribute(String name, int scope):从参数scope指定的范围内删除特定属性。
findAttribute(String name):依次从页面范围,请求范围,回话范围,web应用范围内寻找参数name指定的属性。如果找到,就立即返回该属性的值;如果所有的范围内都不存在该属性,返回null。
int getAttributeScope(String name):返回指定的属性所属的范围,如果在所有的范围内都不存在该属性,就返回0。
注意:
scope参数指定属性的范围,为PageContext类的4个静态常量。
PageContext.PAGE_SCOPE:实际值为1,表示页面范围。
PageContext.REQUEST_SCOPE:实际值为2,表示请求范围
PageContext.SESSION_SCOPE:实际值为3,表示会话范围
PageContext.APPLICATION_SCOPE:实际值为4,表示Web应用范围。
PageContext 类可通过如下方法获得 Servlet 容器提供的 ServletContext , HttpSession , ServletRequest , ServletResponse 等对象
getPage() :返回与当前JSP对应的Servlet实例 page
getRequest():返回ServletRequest对象 request
getResponse():返回ServletResponse对象 response
getServletConfig():返回ServletConfig对象 config
getServletContext():返回ServletContext对象 application
getSession():返回HttpSession对象 session
getOut():返回一个用于输出响应正文的JspWriter对象。 out
注意:
在JSP的Java程序片段中,可以直接通过隐含对象application, request, response,out等使用,而不需要上述方法再获得。但在自定义的JSP标签处理类中,无法使用这些隐含对象,此时就需要使用PageContext类相关方法得到以上对象了。
3) 请求转发和包含的方法
forward(String relativeUrlPath): 用于将请求转发给其他Web组件
include(String relativeUrlPath):用于包含其他Web组件
注意:
JSP 文件中有专门的 JSP 指令 <%@include file=””%> 或动作标签 <jsp:include page=””> 进行请求转发和包含操作,而在自定义 JSP 标签的处理类中,无法使用 JSP 这些指令或动作。此时需要使用 PageContext 类的方法完成请