08.JSP技术
一、什么是JSP
JSP(Java Server Pages)是JavaWeb服务器端的动态资源。它与html页面的作用是相同的,显示数据和获取数据。JSP文件的扩展名是.jsp。
JSP = html + Java代码片段 + JSP动态标签
HTML代码用来显示网页中静态内容,Java代码用来显示网页中的动态内容。
二、JSP的作用
- Servlet:
- 缺点:不适合设置HTML响应体,需要大量的response.getWriter().print("<html>")
- 优点:动态资源,可以编程。
- HTML:
- 缺点:HTML是静态页面,不能包含动态信息
- 优点:不用为输出HTML而发愁
- JSP(Java Server Pages):
- 优点:在原有HTML的基础上添加JAVA脚本,构成jsp页面。
三、JSP和Servlet的分工
- JSP:
- 作为请求发起页面,例如显示表单、超链接。
- 作为请求结束页面,例如显示数据。
- Servlet:
- 作为请求中处理数据的环节。
【案例】两个整数加法计算
在form页面输入两个整数,提交后由AServlet进行计算,然后将结果显示到result页面。
创建一个名为HelloJSP的Dynamic Web Project,
在WebContent目录下创建一个名为plus的文件夹,在该文件夹中创建两个JSP文件:form.jsp和result.jsp,
在src目录下创建一个名为com.sdbi.servlet的包,在该包下创建一个类AServlet.java。
(1)form.jsp文件,具体代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>表单</title> </head> <body> <form action="../AServlet" method="POST"> 整数1:<input type="text" name="num1" /><br /> 整数2:<input type="text" name="num2" /><br /> <input type="submit" value="提交" /> </form> </body> </html>
(2)result.jsp文件,具体代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>结果</title> </head> <body> 结果是:<%=request.getAttribute("result") %> </body> </html>
(3)AServlet.java文件,具体代码如下:
@WebServlet("/AServlet") public class AServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取参数 String s1 = request.getParameter("num1"); String s2 = request.getParameter("num2"); // 转换成int类型 int num1 = Integer.parseInt(s1); int num2 = Integer.parseInt(s2); // 运算 int sum = num1 + num2; // 把结果保存到request域中 request.setAttribute("result", sum); // 转发到result.jsp request.getRequestDispatcher("/plus/result.jsp").forward(request, response); } }
四、JSP的原理
1、JSP是特殊的Servlet
JSP是一种特殊的Servlet,某个JSP页面就是一个Servlet类的对象。
当JSP页面首次被访问时,发生如下事情:
(1)JSP容器(Tomcat)会先把JSP文件编译成Java文件(实现Servlet接口的类);
(2)把.java编译成.class;
(3)创建该类对象;
(4)调用它的service()方法。
如果第二次请求同一JSP时,直接调用service()方法。
虽然在web.xml文件中没有与JSP相关的配置,但Web服务器仍然可以根据URL找到对应的JSP文件。
这是因为在Tomcat服务器的web.xml(Tomcat安装目录下\conf\web.xml)文件中实现了JSP的相关配置,具体如下:
从配置信息可以看出,以.jsp为扩展名的URL访问请求都是由org.apache.jasper.servlet.JspServlet处理,所以,Tomcat中的JSP引擎就是这个Servlet程序,该Servlet程序实现了对所有JSP页面的解析。
JSP文件也可以像Servlet程序一样,在web.xml文件中进行注册和映射虚拟路径。
注册JSP页面的方式与Servlet类似,只需将<servlet-class>元素修改为<jsp-file>元素即可。
例如,要映射/plus/form.jsp的虚拟访问路径,需要在web.xml配置中配置如下信息:
<servlet> <servlet-name>FormJspServlet</servlet-name> <jsp-file>/plus/form.jsp</jsp-file> </servlet> <servlet-mapping> <servlet-name>FormJspServlet</servlet-name> <url-pattern>/form</url-pattern> </servlet-mapping>
其中,<jsp-file>元素表示JSP文件,它表示的路径必须以“/”开头,这个“/”表示JSP文件所在的Web应用程序的根目录。
2、JSP生成Servlet存放目录
JSP生成的Servlet存放在Tomcat安装目录的work目录下,我们打开看看其中的内容,在JSP中的静态信息(例如<html>等)在Servlet中都是使用out.write()完成打印。这些静态信息都是作为字符串输出给了客户端。
五、JSP语法
1、JSP脚本
JSP脚本就是Java代码片段,它分为三种:
<%...%>:JSP脚本:Java代码片段,用于定义0~N条Java语句,方法内能写什么,它就可以放什么。
<%=…%>:JSP表达式:用于输出(常用),用于输出一个表达式(或变量)的值。response.getWriter().print( ... ); 这里参数能放什么,它就可以放什么。
<%!...%>:JSP声明:Java中类成员的声明,用来创建类的成员变量和成员方法(基本不用,但容易被考到),例如,成员变量和方法。类体中可以放什么,它就可以放什么。
【案例】演示JSP中的Java代码片段
在WebContent目录下创建一个JSP文件:index.jsp。
<body> <% int a = 10; //定义变量 %> <% out.print(a++); //输出变量 %> <br /> <%=a%> <br /> <%!int a = 100;%> <% out.print(a++); %> <br /> <% out.print(this.a++); %> <br /> <%!private String hello = "world"; public String sayHello() { return hello; }%> <%=sayHello()%> </body>
在这基础上再来创建一个表格:
<table border="1" align="center" width="60%"> <tr align="center"> <td>姓名</td> <td>年龄</td> </tr> <% for (int i = 0; i < 10; i++) { %> <tr align="center"> <td>张三</td> <td>36</td> </tr> <% } %> </table>
我们到Tomcat安装目录下的\work\Catalina\localhost\HelloJSP\org\apache\jsp中找一下index.jsp翻译生成的Servlet,对比看一下。
会发现:
在JSP中的<%...%>Java代码片段会原封不动的翻译到生成的Java类中;
JSP中的标签会以字符串形式通过out.write()方法输出;
JSP中的<%=…%>Java表达式会以变量形式通过out.print()方法输出;
JSP中的<%!...%>成员定义会在Java中变为Servlet类的成员变量和成员方法。
2、JSP注释
(1)JSP注释<%-- … --%>:我们现在已经知道JSP是需要先编译成.java,再编译成.class的。其中<%-- … --%>中的内容在JSP翻译成.java时会被忽略的,即JSP注释,里面的内容不会被发送到客户端。
(2)HTML注释<!-- … -->:也可以在JSP页面中使用HTML注释,但这个注释在JSP编译成的.java中是存在的,它不会被忽略,而且会被发送到客户端浏览器。但是在浏览器显示服务器发送过来的HTML时,因为<!-- … -->是HTML的注释,所以浏览器是不会显示它的。但是我们可以在浏览器中通过右键查看源代码,可以看到HTML注释中的内容。
六、JSP指令
1、JSP指令概述
JSP指令的格式:<%@指令名 attr1="" attr2="" %>,一般都会把JSP指令放到JSP文件的最上方,但这不是必须的。
JSP中有三大指令:page、include、taglib
最为常用,属性最多,也最为复杂的就是page指令了。
2、page指令
page指令是最为常用的指令,也是属性最多的指令。
page指令没有必须属性,都是可选属性。例如<%@page %>,没有给出任何属性也是可以的!
在JSP页面中,任何指令都可以重复出现。
<%@ page language=”java”%>
<%@ page import=”java.util.*”%>
<%@ page pageEncoding=”utf-8”%>
这也是可以的。
(1)page指令的pageEncoding和contentType(重点)
我们来看一下JSP文件的第一行:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
pageEncoding指定当前JSP页面的编码。
这个编码是给服务器看的,服务器需要知道当前JSP使用的编码,不然服务器无法正确把JSP编译成java文件。
所以这个编码只需要与真实的页面编码一致即可。
在Eclipse中,在JSP文件上点击右键,选择属性就可以看到当前JSP页面的编码了。
如果我们希望Eclipse在自动创建JSP页面时,使用UTF-8作为编码,我们可以在Window > Preferences > Web > JSP Files中将Encoding设置为“ISO 10464/Unicode(UTF-8)”即可。
contentType属性与response.setContentType()方法的作用相同。
它会完成两项工作,一是设置响应字符流的编码,二是设置content-type响应头。
例如:<%@ page contentType="text/html;charset=utf-8"%>,它会在翻译的Servlet类中出现response.setContentType("text/html;charset=utf-8")。
pageEncoding和contentType这两个属性的关系:
- 如果两个属性只提供一个,那么另一个的默认值为设置那一个。
- 如果两个属性都没有设置,那么默认为ISO-8859-1。
无论是page指令的pageEncoding还是contentType,它们的默认值都是ISO-8859-1,我们知道ISO-8859-1是无法显示中文的,所以JSP页面中存在中文的话,一定要设置这两个属性。
(2)page指令的import属性
import是page指令中一个很特别的属性。
import属性值对应Servlet中的import语句。
import属性值可以使逗号:
<%@ page import="java.net.*,java.util.*,java.sql.*"%>
import属性是唯一可以重复出现的属性:
<%@ page import="java.util.*" import="java.net.*" import="java.sql.*"%>
但是,我们一般会使用多个page指令来导入多个包:
<%@ page import="java.util.*"%> <%@ page import="java.net.*"%> <%@ page import="java.text.*"%>
(3)page指令的errorPage和isErrorPage
- errorPage:当前页面如果抛出异常,那么要转发到哪一个页面,由errorPage来指定。
我们知道,在一个JSP页面出错后,Tomcat会响应给用户错误信息(500页面)。如果你不希望Tomcat给用户输出错误信息,那么可以使用page指令的errorPage来指定错误页。也就是自定义错误页面,例如:<%@ page errorPage="xxx.jsp"%>。这时,在当前JSP页面出现错误时,会请求转发到xxx.jsp页面。我们来测试一下。
创建a.jsp文件,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"% errorPage="errorPage.jsp"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% int i = 10 / 0; %> </body> </html>
创建errorPage.jsp文件,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>出错啦!</h1> </body> </html>
发布到Tomcat,浏览器访问http://localhost:8080/HelloJSP/a.jsp,出现如下画面:
在上面代码中,a.jsp抛出异常后,会请求转发到errorPage.jsp。在浏览器的地址栏中还是a.jsp,因为是请求转发。
而且客户端浏览器收到的响应码为200,表示请求成功。
- isErrorPage:指定当前页面是否为处理错误的页面。当该属性为true时,这个页面会设置状态码为500,而且这个页面可以使用9大内置对象中的exception隐式对象。
上例中,如果希望客户端得到500,那么就需要指定errorPage.jsp为错误页面,并且我们可以输出异常的内容。如果没有将isErrorPage属性设置为true的话,exception隐式对象是不可以调用的。修改errorPage.jsp文件,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"% isErrorPage="true"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <% exception.printStackTrace(response.getWriter()); %> <h1>出错啦!</h1> </body> </html>
【注意】IE会在状态码为500时,并且响应正文的长度小于等于512B时不给予显示。而是显示“网站无法显示该页面”字样。这时,我们可以到IE选项 à 高级 à 取消“显示友好HTTP错误消息”的勾选。或者是需要添加一些响应内容即可,例如上例中的errorPage.jsp中想响应中输出异常的内容,这样IE也可以正常显示了。
(4)web.xml中配置错误页面
不只可以通过JSP的page指令的errorPage属性来配置错误页面,还可以在web.xml文件中指定错误页面。这种方式其实与page指令无关,在这里跟着一起讲一下。
我们在web.xml增加如下代码:
<error-page> <error-code>404</error-code> <location>/error404.jsp</location> </error-page> <error-page> <error-code>500</error-code> <location>/error500.jsp</location> </error-page>
然后,我们在a.jsp文件的page指令中去掉errorPage属性。然后自定义两个错误页面error404.jsp和error500.jsp。
这时,我们访问http://localhost:8080/HelloJSP/a.jsp会转发至error500.jsp页面,我们如果访问一个不存在的资源,会转发至error404.jsp页面。
如果我们在web.xml文件中在添加如下代码:
<error-page> <exception-type>java.lang.RuntimeException</exception-type> <location>/errorPage.jsp</location> </error-page>
这是就会根据<exception-type>更精确的捕获异常类型来实现错误页面的转发。
<error-page>有两种使用方式:
- <error-code>和<location>子元素;
- <exception-type>和<location>子元素;
其中<error-code>是指定响应码;<location>指定转发的页面;<exception-type>是指定抛出的异常类型。
在上例中:
- 当出现404时,会跳转到error404.jsp页面;
- 当出现RuntimeException异常时,会跳转到errorPage.jsp页面;
- 当出现非RuntimeException的异常时,会跳转到error500.jsp页面。
这种方式会在控制台看到异常信息,而使用page指令时不会在控制台打印异常信息。
(5)page指令的autoFlush和buffer
buffer表示当前JSP的输出流(out隐藏对象)的缓冲区大小,默认为8kb。
autoFlush表示在out对象的缓冲区满时如何处理:
- 当autoFlush为true时,表示缓冲区满时把缓冲区数据输出到客户端,authFlush的默认值为true;
- 当authFlush为false时,表示缓冲区满时,抛出异常。
这两个属性一般我们也不会去特意设置,都是保留默认值。
(6)page指令的isELIgnored
以后我们会讲解EL表达式语言,page指令的isElIgnored属性表示当前JSP页面是否忽略EL表达式,默认值为false,表示不忽略(即支持EL表达式)。
(7)page指令的其他属性(基本不会用)
- language:指定当前JSP编译后的语言类型,默认值为Java。
- info:JSP说明性信息。
- isThreadSafe:当前的JSP是否支持并发访问,默认值为false。为true时,JSP生成的Servlet会去实现一个过时的接口SingleThreadModel,这时JSP就只能处理单线程的访问。
- session:当前JSP页面是否支持session内置对象,默认值为true,表示当前JSP页面可以使用session对象,如果为false表示当前JSP页面不能使用session对象。
- extends:指定当前JSP页面生成的Servlet的父类。
3、include指令 -- 静态包含
与RequestDispatcher的include()方法(请求包含)的功能相似。但是,区别是:
- <%@include%> 它是在JSP翻译成Java文件时完成的。它们共同生成一个Java(就是一个Servlet)文件,然后再编译一个class。
- RequestDispatcher的include()是一个方法,包含和被包含的是两个Servlet,即两个.class。它只是把响应的内容在运行时合并了。
作用:把页面分解了,使用包含的方式组合在一起,这样一个页面中不变的部分,就是一个独立JSP,而我们只需要处理其他变化的页面。
我们来测试一下,创建两个JSP页面:hello.jsp和world.jsp
在hello.jsp中定义一个变量name,另外通过include指令包含world.jsp页面,具体代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>hello.jsp</title> </head> <body> <h1>hello.jsp</h1> <% String name = "zhangsan"; %> <%@include file="world.jsp"%> </body> </html>
在world.jsp中通过out输出变量name的值,具体代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>world.jsp</title> </head> <body> <h1>world.jsp</h1> <% out.print("Hello, " + name); %> </body> </html>
发布运行hello.jsp,可以看到如下画面。查看生成的.java和.class文件,只有hello.jsp被翻译。
在Eclipse中,world.jsp页面会报语法错误,可不去理会,因为Eclipse想把world.jsp单独翻译成一个Servlet,但是变量name又没有定义,所以会报语法错误。
但是我们不单独执行world.jsp页面,不会翻译这个页面,因此这个错误可以不去理会。
4、taglib指令 -- 导入标签库
用来在当前JSP页面中导入第三方的标签库,它有两个属性:
- prefix:指定标签库在本页面中的前缀!由我们自己来起名称。
- uri: 指定标签库的位置。需要先把第三方标签库所需jar包放到类路径中。
例如:
<%@taglib prefix="s" uri="/struts-tags"%>
前缀的用法 <s:text>
七、JSP九大内置对象
1、什么是JSP九大内置对象
在JSP中无需创建就可以使用的9个对象,它们是:
- out(JspWriter):等同与response.getWriter(),用来向客户端发送文本数据;
- config(ServletConfig):对应Servlet中的ServletConfig;
- page(Object):指当前JSP页面对象,但是,我们一般使用this对象而不用page对象,因为page是this对象向上转型得到的,page对象是Object类型的,它的方法很少;
- pageContext(PageContext):页面上下文对象;
- exception(Throwable):只有在错误页面中可以使用这个对象;
- request(HttpServletRequest):即HttpServletRequest类的对象;
- response(HttpServletResponse):即HttpServletResponse类的对象;
- application(ServletContext):即ServletContext类的对象;
- session(HttpSession):即HttpSession类的对象,不是每个JSP页面中都可以使用,如果在某个JSP页面中设置<%@page session="false"%>,说明这个页面不能使用session。
在这9个对象中有很多是极少会被使用的,例如:config、page、exception基本不会使用。
在这9个对象中有两个对象不是每个JSP页面都可以使用的:exception、session。
2、pageContext
(1)域对象
pageContext指一个JSP页面。这个域是在当前JSP页面和当前JSP页面中使用的标签之间共享数据。
四大域范围:
pageContext < HttpServletRequest(request) < HttpSession(session) < ServletContext(application)
域对象的方法:
- void setAttribute(String name, Object value)
- Object getAttrbiute(String name)
- void removeAttribute(String name)
- Enumeration<String> getAttributeNames()
(2)代理其他域对象
还可以使用pageContext来代理其它3个域对象的功能,也就是说可以使用pageContext向request、session、application对象中存取数据,具体方法:
- void setAttribute(String name, Object value, int scope)
- Object getAttribute(String name, int scope)
- void removeAttribute(String name, int scope)
这三个方法都多了一个参数int scope,通过这个参数,指定要代理其他的域,这里可以传入pageContext 中代表域的常量:
- PageContext.APPLICATION_SCOPE :4
- PageContext.SESSION_SCOPE :3
- PageContext.REQUEST_SCOPE :2
- PageContext.PAGE_SCOPE(默认值):1
例如:
pageContext.setAttribute("xxx", "XXX", PageContext.SESSION_SCOPE); // 在Session域中保存数据
(3)全域查找
我们还可以使用findAttribute()方法进行全域查找,从小域到大域开始搜索,如果搜索到就直接获取该值,如果所有域中都找不到,返回一个null。
例如:
pageContext.findAttribute("xxx"); //从小到大,依次查找
(4)获取其他8个内置对象:
可以通过一个pageContext对象获取其他所有内置对象,即一个当九个。
pageContext获取其他八个对象的方法有:
- JspWriter getOut():获取out内置对象;
- ServletConfig getServletConfig():获取config内置对象;
- Object getPage():获取page内置对象;
- ServletRequest getRequest():获取request内置对象;
- ServletResponse getResponse():获取response内置对象;
- HttpSession getSession():获取session内置对象;
- ServletContext getServletContext():获取application内置对象;
- Exception getException():获取exception内置对象;
八、JSP动作标签
动作标签的作用是用来简化Java脚本的。用来替代一部分Java脚本,使非Java开发人员也可以向JSP中添加动态信息。
JSP的动作标签,与HTML提供的标签有本质的区别。
- 动作标签是由Tomcat(服务器)来解释执行,它与Java代码一样,都在服务器端执行的;
- HTML标签由浏览器来执行。
1、<jsp:include>
动态包含,它与RequestDispatcher的include方法是一样的,一个是在Servlet中使用,一个是在JSP中使用。
<%@include>和<jsp:include>有什么不同?
我们来写程序看一下<jsp:include>的效果。
在WebContent下创建一个文件夹jspinclude,在里面创建两个JSP文件:b.jsp和c.jsp,在b.jsp中动态包含c.jsp。
b.jsp部分代码如下:
<body> <h1>b.jsp</h1> <jsp:include page="c.jsp"></jsp:include> </body>
c.jsp部分代码如下:
<body> <h1>c.jsp</h1> </body>
发布运行b.jsp,查看生成的.java和.class文件。
你会发现b.jsp和c.jsp两个JSP文件都被翻译成了.java文件,并且被编译。动态包含的原理:
这时候我们再来思考一下,如果在b.jsp中定义了一个变量name,在c.jsp中使用这个变量,这时程序能不能运行?
答案是:不能!因为c.jsp也会被编译为.class,这时就会报服务器错误。
2、<jsp:forward>
动态转发,它与RequestDispatcher的forward方法是一样的,一个是在Servlet中使用,一个是在JSP中使用。
在WebContent下创建一个文件夹jspforward,在里面也创建两个JSP文件:b.jsp和c.jsp,在b.jsp中动态包含c.jsp。
b.jsp部分代码如下:
<body> <h1>b.jsp</h1> <jsp:forward page="c.jsp"></jsp:forward> </body>
c.jsp部分代码如下:
<body> <h1>c.jsp</h1> </body>
发布运行b.jsp,查看生成的.java和.class文件。
你会发现,只显示c.jsp中的内容,b.jsp中的内容没有显示,因为转发是“留头不留体”。
3、<jsp:param>
用作为<jsp:include>和<jsp:forward>的子标签,用来给转发或包含的页面传递参数。
修改上面的b.jsp代码:
<body> <h1>b.jsp</h1> <jsp:forward page="c.jsp"> <jsp:param value="admin" name="username" /> <jsp:param value="123" name="password" /> </jsp:forward> </body>
修改c.jsp代码:
<body> <h1>c.jsp</h1> <% String username = request.getParameter("username"); String password = request.getParameter("password"); out.print(username + ", " + password); %> </body>
发布运行b.jsp。