第六章使用JSP

JSP/Servlet学习笔记

使用JSP

从JSP到Servlet

  • 在Servlet中编写HTML实在太麻烦了,应该使用JSP(JavaServer Pages)。尽管JSP中可以直接编写HTML,使用了指示、声明、脚本等许多元素来堆砌各种功能,但是JSP最后还是会成为Servlet。只要对Servlet的各种功能及特性有所了解,编写JSP时就不会被这些元素所迷惑。
  • 将介绍JSP的生命周期,了解各种元素的作用和使用方式,以及一些元素与Servlet中各对象的对应。
JSP生命周期
  • JSP与Servlet是一体的两面。基本上Servlet能实现的功能,使用JSP也都能做得到,因为JSP最后还是会被容器转译为Servlet源代码、自动编译成为.class文件、载入.class文件,然后生成Servlet对象。
<%--
  Created by IntelliJ IDEA.
  User: lancibe
  Date: 2021/4/2
  Time: 下午4:10
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello Servlet</title>
</head>
<body>
    <h1>Hello JSP!</h1>
</body>
</html>
  • JSP网页最后还是会转化成Servlet,在第一次请求JSP时,容器会进行转译、编译与加载的操作,所以第一次请求JSP页面会慢许多才会得到相应。
  • 转译后的class文件我们需注意_jspInit()、_jspDestroy()与_jspService()三个方法。
  • 在编写Servlet时,可以重新定义init()方法作Servlet的初始化,重新定义destroy()进行Servlet销毁前的收尾工作。JSP在转译为Servlet并载入容器生成对象之后,会调用_jspInit()方法进行初始化工作,而销毁前是调用_jspDestroy()方法进行善后。在Servlet中,每个请求到来时,容器会调用service()方法,而在JSP转译为Servlet后,请求的到来则是调用_jspService()方法。
  • Servlet的init()中调用了jspInit()与_jspInit(),前者是转译后的Servlet重新定义,如果想要在JSP网页载入执行时做些初始化操作,则需要重新定义jspInit()方法。同样地,Servlet的destroy()中调用了jspDestroy()和_jspDestroy()方法。
  • 当请求到来而容器调用了service()方法时,其中又调用了_jspService()方法,也因此在JSP转译后的Servlet源代码中,会看到定义的代码是转译在_jspService()中。
Servlet和JSP的简单转换
  • Servlet与JSP是一体的两面,JSP会转换为Servlet,Servlet可实现的功能也可以用JSP来实现,通常JSP会作为画面显示用。将用一个显示画面的Servlet,将其转换为JSP,从中了解各元素的对照。
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class ListBookmark extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>");
        out.println("<html>");
        out.println("<head>");
        out.println("<meta content='text/html;charset=UTF-8; http-equiv='content-type'>");
        out.println("<title>查看在线书签</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<table style='text-align:left;width:100%;' border='0'>");
        out.println("<tbody>");
        out.println("<tr>");
        out.println("<td style='background-color:rgb(51.255,255);'>网页</td>");
        out.println("<td style='background-color:rgb(51.255,255);'>分类</td>");
        out.println("</tr>");
        BookmarkService bookmarkService = (BookmarkService)getServletContext().getAttribute("bookmarkService");
        for(Bookmark bookmark : bookmarkService.getBookmarks())
        {
            out.println("<tr>");
            out.println("<td><a href='http://"+bookmark.getUrl()+"'>"+bookmark.getTitle() + "</a></td>");
            out.println("<td>"+bookmark.getCategory()+"</td>");
            out.println("</tr>");
        }
        out.println("</tbody>");
        out.println("</table>");
        out.println("</body>");
        out.println("</html>");
        out.close();
    }
}
  • 可以创建一个文件,后缀为.jsp,首先把doGet()所有代码粘贴上去,接着看到第一行:
resp.setContentType("text/html;charset=UTF-8");
  • 这可以使用JSP的指示(Directive)元素在JSP页面第一行写下:
<%@ page contentType="text/html" pageEncoding="UTF-8" language="java" %>
  • 这告诉容器在将JSP转换为Servlet时,使用UTF-8读取.jsp转译为.java,然后编译时使用UTF-8,并设置内容类型为text/html.
  • 接着看到下面这行:
PrintWriter out = resp.getWriter();
  • 可以直接删除,且原先out.println()的部分,都可以仅保留字符串值,也就是修改如下:
<%--
  Created by IntelliJ IDEA.
  User: lancibe
  Date: 2021/4/6
  Time: 下午3:52
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html" pageEncoding="UTF-8" language="java" %>
<%@page import="ListBookmark, java.util.*"%>
<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>
<html>
    <head>
        <meta http-equiv='Content-Type' content='text/html;charset=UTF-8'>
        <title>查看在线书签</title>
    </head>
<body>
    <table style='text-align:left;width:100%;' border='0'>
        <tbody>
            <tr>
                <td style='background-color:rgb(51.255,255);'>网页</td>
                <td style='background-color:rgb(51.255,255);'>分类</td>
            </tr>
            <%
                BookmarkService bookmarkService = (BookmarkService)application.getAttribute("bookmarkService");
                for(Bookmark bookmark : bookmarkService.getBookmarks()) {
            %>
            <tr>
                <td><a href='http://<%=bookmark.getUrl()%>'>
                    <%=bookmark.getTitle()%>></a></td>
                <td><%=bookmark.getCategory%>></td>
            </tr>
            <%
                }
            %>

        </tbody>
    </table>
</body>
</html>
指示元素
  • JSP指示元素的主要目的,在于指示容器将JSP转译为Servlet源代码时,一些必须遵守的信息。指示元素的语法如下所示:
<%@ 指示类型 [属性="值"]* %>
  • 在JSP中有三种常用的指示类型:page、include和taglib。page指示类型告知容器如何转译目前的JSP网页。include指示类型告知容器将别的JSP页面包括进来进行转译。taglib指示类型告知容器如何转译这个页面中的标签库。
  • 指示元素中可以有多对的属性、值,必要时,同一个指示类型可以用数个指示元素来设置。直接以实际例子来说明比较清楚。首先说明page指示类型:
<%--
  Created by IntelliJ IDEA.
  User: lancibe
  Date: 2021/4/7
  Time: 下午3:47
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.Date" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>Page 指示元素</title>
</head>
<body>
    <h1>现在时间:<%= new Date()%></h1>
</body>
</html>
  • 输出结果如下:
现在时间:Wed Apr 07 15:49:32 GMT+08:00 2021
  • 可以在同一个import属性中,使用逗号分隔数个import的内容:
<%@page import="java.util.Date,javax.*" %>
  • page指示类型的contentType属性告知容器转移JSP时,必须使用HttpServletRequest的setContentType(),调用方法时传入的参数就是contentType的属性值。pageEncoding属性则告知这个JSP网页中的文字编码,以及内容类型附加的charset设置。

  • page指示类型的可以设置的属性有下:

  • page指示类型常用的属性有主要有:import、contentType、pageEncoding

  • import属性:可以在同一个import属性中,使用逗号分隔数个import的内容。

  • contentType属性:用来告知响应给浏览器的文件类型。

  • pageEncoding属性:用来设置转译、编译时使用的字符编码。

  • page指示类型其他属性

  • info属性:用于设置目前JSP页面的基本信息,这个信息最后会转换为Servlet程序中使用getServletInfo()所取得的信息。

  • autoFlush属性:用于设置输出串流是否要自动清除,默认是true。如果设置为false,而缓冲区满了却还没调用flush()将数据送出至客户端,则会产生异常。

  • buffer属性:用于设置至客户端的输出串流缓冲区大小,设置时必须指定单位,例如buffer=“16kb”。默认是8kb。

  • errorPage属性:用于设置当JSP执行错误而产生异常时,该转发哪一个页面处理这个异常。

  • isErrorPage属性:设置JSP页面是否为处理异常的页面,这个属性要与errorPage配合使用。

  • extends属性:用来指定JSP网页转译为Servlet程序之后,该继承哪一个类。以Tomcat为例,默认是继承自HttpJspBase(HttpJspBase又继承自HttpServlet)。基本上几乎不会使用到这个属性。

  • language属性:指定容器使用哪种语言的语法来转译JSP网页,言下之意是JSP基本上可使用其他语言来转译,不过事实上目前只能使用Java的语法(默认使用java)。

  • session属性:设置是否在转译后的Servlet源代码中具有创建HttpSession对象的语句。默认是true,若某些页面不需作进程跟踪,设成false可以增加一些效能。

  • isELIgnored属性:设置JSP网页中是否忽略表达式语言(Expression Language),默认是false,如果设置为true,则不转译表达式语言。这个设置会覆盖web.xml中的<el-ignored>设置。

  • isThreadSafe属性:告知容器编写JSP时是否注意到线程安全,默认值是true。如果设置为false,则转译之后的Servlet会实现SingleThreadModel接口,每次请求时将创建一个Servlet实例来服务请求。虽然可以避免线程安全问题,这会引起性能问题,极度不建议设置为false。

  • 接着介绍include指示类型,它用来告知容器包括另一个网页的内容进行转译。

<%--
  Created by IntelliJ IDEA.
  User: lancibe
  Date: 2021/4/7
  Time: 下午3:58
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html" pageEncoding="UTF-8" language="java" %>
<%@ include file="header.jsp"%>
    <h1>include 示范</h1>
<%@ include file="foot.jsp"%>
  • 上面的程序在第一次执行时,会把header.jsp和foot.jsp的内容包括进来转译。假设两个文件的内容为:
<%@ page pageEncoding="UTF-8" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>示范开头</title>
</head>
<body>
<%@ page pageEncoding="UTF-8" %>
</body>
</html>
  • 这是一种静态的包括方式(所以只生成一个Servlet)。之后会介绍<jsp:include>标签的使用,则是运行时动态包括别的网页执行流程进行响应的方式
  • 可以在web.xml中统一默认的网页编码、内容类型、缓冲区大小等。
    <jsp-config>
        <jsp-property-group>
            <url-pattern>*.jsp</url-pattern>
            <page-encoding>UTF-8</page-encoding>
            <default-content-type>text/html</default-content-type>
            <buffer>16kb</buffer>
        </jsp-property-group>
    </jsp-config>
声明、Scriptlet与表达式元素
  • JSP网页会编译为Servlet类,转译后的Servlet类应该包括哪些类成员、方法声明或那些语句,在编写JSP时,可以使用声明(Declaration)元素、Scriptlet元素及表达式(Expression)元素来指定。
  • 首先看声明元素的语法:
<%! 类成员声明或方法声明 %>
  • 举例来说,如果在JSP中编写以下片段:
<%!
    String name = "lancibe";
	String password = "123456";
	boolean checkUser(String name, String password){
        return this.name.equals(name) && this.password.equals(password);
    }
%>
  • 则转译后的Servlet代码,将会有以下内容:
public final class index_jsp extends xxx implements xxx{
    String name = "lancibe";
	String password = "123456";
	boolean checkUser(String name, String password){
        return this.name.equals(name) && this.password.equals(password);
    }
}
  • **在使用<%!与%>声明变量时,必须小心数据共享与线程安全的问题。**容器默认会使用同一个Servlet实例来服务不同用户的请求,每个请求是一个线程,而<%!与%>间声明的变量对应至类变量成员,因此会有线程共享访问的问题。
  • 如果有一些初始化操作,想要在JSP载入时进行,则可以重新定义jspInit()方法,同样的也可以重写jspDestroy()定义结尾动作。定义它们时,就是要在<%!与%>之间进行。
  • 再看Scriptlet元素,其语法为:
<% Java语句 %>
  • 发现和前面声明变量的区别是没有感叹号。在声明元素中可以编写Java语句,就如同在Java方法中编写语句一样。事实上,<%与%>之间所包括的内容,将被转译为Servlet源代码_jspService()方法中的内容。举例来说:
<%
	String name = req.getParameter("name");
	String password = req.getParameter("password");
	if(checkUser(name, password)){
%>
<h1>登陆成功</h1>
<%
	}else{
%>
<h1>登录失败</h1>
<%
	}
%>
  • 直接在JSP中编写的HTML,都会变成out对象所输出的内容。Scriptlet出现的顺序,也就是在转译为Servlet后,语句出现在_jspService()中的顺序。
  • 再看表达式元素,语法如下:
<%= Java表达式 %>
  • 可以在表达式元素中编写Java表达式,表达式的运算结果将直接输出为网页的一部分。例如之前看到的范例中,使用到一段表达式元素:
现在时间:<%= new Date() %>
  • 注意,表达式元素中不用加分好。这个表达式元素在转译为Servlet之后,会在_jspService()中产生如下语句:
out.print(new Date());
  • 简单地说,表达式元素的表达式,会直接转译为out对象输出时的指定内容(这也是为什么表达式元素中不用加上分号的原因)。
  • 下面这个范例综合了以上的说明,实现了一个简单的登录程序,其中使用了声明元素、Scriptlet元素与表达式元素。
<%--
  Created by IntelliJ IDEA.
  User: lancibe
  Date: 2021/4/7
  Time: 下午4:30
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html" pageEncoding="UTF-8" language="java" %>
<%!
    //使用声明元素声明类成员
    String name = "lancibe";
    String password = "123456";

    boolean checkUser(String name, String password)
    {
        return this.name.equals(name) && this.password.equals(password);
    }
%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>登录界面</title>
</head>
<body>
<%
    //使用Scriptlet编写Java代码段
    String name = request.getParameter("name");
    String password = request.getParameter("password");
    if(checkUser(name, password)){
%>
    <h1><%= name %>登陆成功</h1> <%--使用表达式元素输出运算结果--%>
<%
    }else{
%>
    <h1>登录失败</h1>
<%
    }
%>

</body>
</html>
  • 在浏览器中输入http://localhost:8080/chapter6/login.jsp?name=lancibe&password=123456时,输出结果为:
lancibe登陆成功
  • <%和%>在JSP中会用来作为一些元素的开头与结尾符号,因此如果想要在JSP网页中输出这两个符号,可以使用&lt;%,和%&gt;或使用%\>
  • 如果想禁用JSP上的Scriptlet,则可以在web.xml中设置。
    <jsp-config>
        <jsp-property-group>
            <url-pattern>*.jsp</url-pattern>
            <scriptint-invalid>true</scriptint-invalid>
        </jsp-property-group>
    </jsp-config>
  • 会想禁用Scriptlet的情况,是在不想让Java代码与HTML代码混合的时候,一个网页通过适当的规划,切割业务逻辑与呈现逻辑的话,JSP网页可以通过标准标签、EL或JSTL自定义标签等,消除网页上的Scriptlet。
注释元素
  • 可以在声明内部使用//单行注释/*多行注释*/
  • 也可以使用HTML的注释方式<!-- 网页注释 -->,这并不是JSP的注释,因此转译为Servlet之后,只是产生了一行语句:out.write("<!-- 网页注释 -->"),所以这个注释文字也会输出至浏览器成为HTML注释,在查看HTML源代码时,就可以看到注释文字。
  • JSP有一个专用的注释,即<%-- JSP注释 --%>容器在转译JSP至Servlet时,会忽略这种方式注释的文字,也不会输出至浏览器。
隐式对象
  • 在之前的范例中,在Scriptlet曾使用过out与request等字眼,然后直接操作一些方法。像这样的字眼,在转移为Servlet之后,会直接对应与_jspService()中的某个局部变量,他们通常被称为隐式对象(implicit Object)或隐式变量(implicit variable)。
  • JSP支持的九大隐式对象如下:

隐式对象只能在<%和%>之间,或是<%=和%>之间使用,因为正如先前所提,隐式对象在转译为Servlet后,是_jspService()中的局部变量,无法在<%!与%>之间使用隐式对象。

  • 大部分隐式对象,在转译后所对应的Servlet相关对象,先前讲解Servlet的文件都做过说明。page隐式对象则是对应于转译后Java类的this对象,主要是让不熟悉Java的网页设计师,在比较时可以用比较直觉的page名称来存取。exception隐式对象将在之后谈到JSP错误处理时再加以说明。
  • 至于out、pageContext、exception这些隐式对象,转译后的类型可能是第一次看到,以下先针对这些隐式对象进行说明。
  • out隐式对象不直接对应于先前说明Servlet中,由HttpServletResponse取得的PrintWriter对象。out隐式对象在转译之后,会对应于javax.servlet.jsp.JspWriter类的实例,JspWriter则直接继承java.io.Writer类。JspWriter主要模拟了BufferedWriter与PrintWriter的功能。
  • JspWriter在内部也是使用PrintWriter来进行输出,但JspWriter具有缓冲区功能。当使用JspWriter的print()或pringln()进行响应输出时,如果JSP页面没有缓冲,则直接创建PrintWriter来输出响应,如果JSP页面有缓冲,则只有在清除(flush)缓冲区时,才会真正创建PrintWriter对象进行输出。
  • 对页面进行缓冲处理,表示在缓冲区已满的时候,可能有两种处理方式:
    • 累计缓冲区的容量后再一次输出响应,所以缓冲区满了就直接清除。
    • 也许想控制输出的量在缓冲区容量之内,所以缓冲区满了表示有错误,此时要抛出异常。
  • 在编写JSP页面时,可以通过page指示元素的buffer属性来设置缓冲区的大小,默认值是8kb。缓冲区满了之后采取哪种行为,则是用autoFlush属性决定,默认是true,表示满了就清除。如果设置为false,则要自行调用JspWriter的flush()方法来清除缓冲区,如果缓冲区满了还没有调用flush()将数据送出至客户端,调用println()时将会抛出IOException。
  • 接着说明pageContext隐式对象。该隐式对象转译后对应于javax.servlet.jsp.PageContext类型的对象,这个对象将所有JSP页面的信息封装起来,转译后的Servlet可以通过pageContext来取得所有JSP页面信息。例如在转译后的Servlet代码中,要取得对应JSP页面的ServletContext、ServletConfig、HttpSession与JspWriter对象时,通过以下的代码来取得。
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
  • 所有的隐式对象都可以通过pageContext来取得。pageContext还可以用来设置页面范围属性。在先前的文件中,我们知道Servlet可以设置属性的对象有HttpServletRequest、HttpSession和ServletContext,可以分别用来设置请求范围、回话范围与应用程序范围属性。而pageContext可以用来设置页面范围属性,同样是使用setAttribute()、getAttribute()与removeAttribute()来设置。页面范围属性表示作用范围仅限同一页面中。
  • 可以通过pageContext来设置四种范围属性,而不用使用个别的pageContext、request、session、application来设置。以pageContext提供单一的API来管理属性作用范围,可以使用以下方法来设置:
getAttribute(String name, int scope);
setAttribute(String name, Object value, int scope);
removeAttribute(String name, int scope);
  • 其中scope可以使用下面常数来设置:pageContext.PAGE_SCOPE、pageContext.REQUEST_SCOPE、pageContext.SESSION_SCOPE、pageContext.APPLICATION_SCOPE。分别表示页面、请求、会话与应用程序范围。
  • 当不知道属性的范围时,可以使用pageContext的findAttribute()方法来找出属性,只需指定属性名称即可。该方法会依次从页面、请求、会话、应用程序范围寻找。
Object attr = pageContext.findAttribute("attr");
错误处理
  • 刚开始编写JSP时,总会被JSP调试信息困扰,如果初学者不了解JSP与Servlet之间的运作关系,看到的只是一堆转译、编译、甚至执行时的异常信息,这些信息虽然包括详细的错误信息,但对于初学者而言在阅读上并不友好、不易理解。其实,只要了解JSP与Servlet之间的运作关系,并了解Java编译信息及异常处理,要了解在编写JSP网页时,因错误而产生的错误报告页面就不是难事。
  • JSP终究会转译为Servlet,所以错误可能发生在三个时候。
  • JSP转换为Servlet源代码时,如果在JSP页面中编写了一些错误语法,而使得容器在转译JSP时不知道该怎么将那些语法转译为Servlet的.java文件,就会发生错误。容器通常会提示无法转译的原因。确定是否为这类错误的一个原则,就是查看反白区段,通常会告知语法不合法的信息。
  • Servlet源代码进行编译时,如果JSP语法没有问题,则容器可以将JSP转译为Servlet的.java程序,接着就会尝试将.java编译为.class文件。如果此时编译器因为某个原因无法完成编译,则会出现编译错误。例如,JSP中使用了某些类,但部署至服务器时,忘了将相关的类也部署上去,使得初次请求JSP时,虽然转译可以完成,但是编译时就会出错。Tomcat通常会提示 Unable to compile 之类的信息。
  • Servlet载入容器进行服务但发生运行时错误时。如果Servlet进行编译成功,接下来就开始载入运行并开始执行,但仍有可能在运行时因找不到某个资源、程序逻辑上的问题导致错误。例如最常见的NullPointerException。Tomcat会提示 An exception occurred processing JSP page
  • 可以自定义运行时异常发生时的处理页面,只要使用page指示元素时,设置errorPage属性来指定错误处理的JSP页面。
<%--
  Created by IntelliJ IDEA.
  User: lancibe
  Date: 2021/4/8
  Time: 上午10:29
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" errorPage="error.jsp" language="java" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>加法网页</title>
</head>
<body>
<%
    String a = request.getParameter("a");
    String b = request.getParameter("b");
    out.println("a + b = " + (Integer.parseInt(a) + Integer.parseInt(b)));
%>

</body>
</html>
  • 这是一个简单的加法网页,从请求参数中取得a与b的值后进行相加。如果有错误时,想要直接转发至error.jsp显示错误,则在JSP页面中将isErrorPage属性改为true即可。
<%@ page import="java.io.PrintWriter" %><%--
  Created by IntelliJ IDEA.
  User: lancibe
  Date: 2021/4/8
  Time: 上午10:42
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" isErrorPage="true" language="java" %>
<html>
<head>
    <title>错误</title>
</head>
<body>
    <h1>网页发生错误:</h1><%= exception %>
    <h2>显示异常堆栈跟踪</h2>
<%
    exception.printStackTrace(new PrintWriter(out));
%>
</body>
</html>
  • exception对象是JSP的隐式对象,由add.jsp抛出的异常对象信息就包括在exception中,而且只有isErrorPage设置为true的页面才可以使用exception隐式对象。在这个error.jsp中的标题上,只是简单地显示exception调用toString()之后的信息,也就是<%=exception%>显示的内容;另外也可以将异常堆栈跟踪显示出来。
  • 如果在存取应用程序的时候发生了异常或错误,而没有在Servlet/JSP中处理这个异常或错误,则最后会由容器加以处理,一般容器就是直接显示异常信息与堆栈跟踪信息。如果希望容器发现这类异常或错误时,可以自动转发至某个URL,则可以在web.xml中使用<error-page>进行设置。
  • 例如,想要在容器收到某个类型的异常对象时进行转发,则可以在<error-page>中使用<exception-type>指定。
<error-page>
	<exception-type>java.lang.NullPointerException</exception-type>
    <location>/report.view</location>
</error-page>
  • 如果指定的是JSP页面,则该页面必须设置isErrorPage属性为true,这才可以使用exception隐式对象。
  • 如果想要基于HTTP错误状态吗转发至处理页面,则是搭配<error-code>来设置。
<error-page>
	<error-code>404</error-code>
    <location>/404.jsp</location>
</error-page>
  • 这个设置,在自行使用HttpServletResponse的sendError()送出错误状态码时也有效。

标准标签

  • 在JSP的规范中提供了一些标准标签(standard tag),所有容器都支持这些标签,可以协助编写JSP时减少Scriptlet的使用。所有标准标签都使用jsp:做前置。
<jsp:include>、<jsp:forward>
  • Include指示元素可以在JSP转译为Servlet时,将另一个JSP包括进来进行转移的动作,这是静态的包括了另一个JSP页面,也就是被包括的JSP与原JSP合并在一起,转移为一个Servlet类,无法在运行时依照条件动态地调整想要包括的JSP页面。
  • 如果想要在运行时,依条件动态地调整想要包括的JSP页面,则可以使用<jsp:include>标签。例如:
<jsp:include page = "add.jsp">
	<jsp:param name="a" value = "1" />
    <jsp:param name="b" value = "2" />
</jsp:include>
  • 指定了动态包括add.jsp时所要给该页面的请求参数。如果在JSP页面中包括以上的标签,则会将add.jsp动态包含进来。目前的页面会自己生成一个Servlet类,而被包括的add.jsp也会自己独自生成一个Servlet类。事实上,目前页面转译而成的Servlet中,会取得RequestDispatcher对象,并执行include()方法,也就是将请求时转交给另一个Servlet,,而后再回到目前的Servlet。
  • 如果想将请求转发给另一个JSP页面处理,则可以使用<jsp:forward>,使用方法和前面一样。
  • 这两个标签,在转译为Servlet源代码之后,底层也是取得RequestDispatcher对象,并执行对应的forward()或include()方法,因此在使用时作用和注意事项和前面介绍的方法相同。

pageContext隐式对象也有forward()和include()方法,使用时机是方便在Scriptlet中编写。

<jsp:useBean>、<jsp:setProperty>和<jsp:getProperty>简介
  • <jsp:useBean>标签是用来搭配JavaBean元件的标准标签,这里指的JavaBean并非桌面系统或EJB中的JavaBean元件,而是只要满足下面条件的纯粹Java对象:
    • 必须实现java.io.Serializable接口
    • 没有public的类变量
    • 具有无参数构造器
    • 具有public的设置方法与取值方法(Setter&Getter)
  • 以下的类就是一个JavaBean元件
import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public boolean isValid(){
        return "lancibe".equals(name) && "123456".equals(password);
    }
}
  • 使用<jsp:useBean>来使用这个JavaBean,并使用<jsp:setProperty>和<jsp:getProperty>来对JavaBean进行设值与取值的操作。
<%--
  Created by IntelliJ IDEA.
  user.User: lancibe
  Date: 2021/4/8
  Time: 上午11:16
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html" pageEncoding="UTF-8" %>
<jsp:useBean id="user" class="user.User"/>
<jsp:setProperty name="user" property="*"/>
<!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>
<%
    if(user.isValid()) {
%>
    <h1><jsp:getProperty name="user" property="name"/>登陆成功</h1>
<%
    } else {
%>
    <h1>登录失败</h1>
<%
    }
%>
</body>
</html>
  • <jsp:useBean>标签用来取得或创建JavaBean。id属性用于指定JavaBean实例的参考名称,之后在使用<jsp:setProperty>或<jsp:getProperty>标签时,就可以根据这个名称来取得所创建的JavaBean名称。class属性用于指定实例化哪一个类。scope指定可先查找看看哪个属性范围是否有JavaBean的属性存在。

注意:使用JavaBean时必须让类位于一个包内,否则会出现无法找到类的错误。

  • 由于使用<jsp:userBean>时,指定了id属性为user名称,因此在接下来的页面中若有Scriptlet,也可以使用user名称来操作JavaBean实例。程序中调用了isValid()方法,看看用户的名称以及密码是否正确。如果正确,<jsp:getProperty>指定property属性为name以取得JavaBean中存储的用户名称,并显示登陆成功字样。
深入<jsp:useBean>、<jsp:setProperty>与<jsp:getProperty>
  • JSP网页最终将转换成Servlet,所谓的JavaBean,实际上也是Servlet中的一个对象实例。当使用<jsp:userBean>时,实际上就是在声明一个JavaBean对象,id属性即是用以指定参考名称与属性名称,而class属性则是类型名称。例如,若在JSP页面中编写以下内容:
<jsp:useBean id="user" class="user.User" />
  • 实际在转译为Servlet后,会产生以下代码段:
user.User = null;
synchronized(request) {
    user = (user.User) _jspx_page_context.getAttribute(
    	"user", PageContext.PAGE_SCOPE);
    if(user == null){
        user = new user.User();
        _jspx_page_context.setAttribute(
        	"user", user, PageContext.PAGE_SCOPE);
    }
}
  • 其中_jspx_page_context引用至PageContext对象,也就是说,使用<jsp:useBean>标签时,会在属性范围中寻找有无id名称指定的属性。如果找到就直接使用,没有找到就创建新对象。
  • 可以在使用<jsp:useBean>标签时,使用scope属性指定存储的属性范围,可以指定的值有page(默认)、request、session与application。使用后会在Servletr中有:SESSION_SCOPE的PageContext类变量。
  • 如果想指定声明JavaBean时的类型,可以使用type属性,这样转译后在声明user时,将会将getAttribute方法的返回值强转为该类型。
  • type属性的设置可以是一个抽象类,也可以是一个接口。如果只设置type而没有设置class属性,则必须确定在某个属性范围中已经存在所要的对象,否则会抛出 InstantiationException异常。
  • 标签的作用是减少JSP中Scriptlet的使用,所以反过来说,如果发现JSP中有Scriptlet,编写的是从某个属性范围中取得对象,则可以思考是否可以用<jsp:useBean>来消除Scriptlet的使用。同样的,Scriptlet中设值、取值的动作也可以分别使用另两个标签来代替。
Model 1
  • Model 2架构的流程与实现基本样貌。浏览器发送HTTP请求进入HTTP服务器中的Web容器,Servlet收集请求信息,并使用JSP处理结果画面,Servlet与JSP之间使用POJO(Plain Old Java Object),也就是纯粹的Java对象,封装应用程序状态或业务逻辑,JSP再响应浏览器。
  • 然而使用Model 2架构,代表了更多的请求转发流程控制、更多的元件设计和更多的代码。对于中小型应用程序来说,前期必须花费更多的时间与设计成本,在开发上不见得多划算。
  • 在前面示范的登录程序中,使用JSP结合JavaBean,其实就是俗称Model 1架构的一个简单范例。
  • 在Model 1架构上,用户会直接请求某个JSP页面(而非通过控制器的转发),JSP会收集请求参数并调用JavaBean来处理请求。业务逻辑的部分封装至JavaBean中,JavaBean也许还会调用一些后端的元件(如操作数据库)。JavaBean处理完毕后,JSP会再从JavaBean中提取结果,进行画面的呈现处理。
XML格式标签
  • 可以使用XML格式标签来编写JSP,每个JSP元素都有对应的XML标签。只需知道有这些标签的存在即可。

表达式语言(EL)

  • 可以将业务逻辑编写在JavaBean元件中,然后搭配几个标准标签来取得、生成JavaBean对象,设置或取得JavaBean的值,这样有助于减少页面上Scriptlet的分量。
  • 对于JSP中一些简单的属性、请求参数、标头与Cookie等信息的取得,一些简单的运算或判断,则可以试着使用表达式语言(EL)来处理,甚至可以将一些常用的公用函数编写为EL函数,这对于Scriptlet又可以有一定分量的减少。
EL简介
  • 直接改写前面的add.jsp范例页面中的Scriptlet:
<%
    String a = request.getParameter("a");
    String b = request.getParameter("b");
    out.println("a + b = " + (Integer.parseInt(a) + Integer.parseInt(b)));
%>
  • 如果使用EL,则可以使用一行代码来改写,甚至加强这段Scriptlet
<%--
  Created by IntelliJ IDEA.
  user.User: lancibe
  Date: 2021/4/8
  Time: 上午10:29
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" errorPage="error.jsp" language="java" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>加法网页</title>
</head>
<body>
    ${param.a} + ${param.b} = ${param.a + param.b} <%-- 使用EL --%>

</body>
</html>
  • 在这个简单的例子中可以看到几个EL元素。EL是使用${与}来包括起来所要进行处理的表达式,可以使用点运算符指定要存取的属性,使用加号运算符来进行加法运算。param是EL隐式对象之一,表示用户的请求参数,param.a表示取得用户所发出的请求参数a的值。

  • 如果请求参数是正常的两个数,可以发现EL计算出了结果,但如果没有给出a的参数,会发现结果为:+ 20 = 20,可以发现null值被当成了空字符串处理,也就不会因此发生错误而抛出异常了。

  • EL的点运算还可以连续存取对象,就如同在Java代码一样。例如原先需要这么写:

方法:<%= ((HttpServletRequest) pageContext.getRequest()).getMethod() %><br>
参数:<%= ((HttpServletRequest) pageContext.getRequest()).getQueryString() %><br>
IP:<%= ((HttpServletRequest) pageContext.getRequest()).getRemoteAddr() %><br>
  • 如果使用EL,则可以这么编写:
方法:${pageContext.request.method}<br>
参数:${pageContext.request.queryString}<br>
IP:${pageContext.request.remoteAddr}<br>
  • pageContext也是EL的隐式对象之一,通过点运算符之后加上xxx名称,表示调用getXxx()方法。如果必须转换类型,EL也会自行处理,而不是像编写JSP表达式元素时,必须自行转换类型。
使用EL取得属性
  • 可以在JSP中将对象设置为page、request、session或application范围中作为属性,但是调用、设置都必须在Scriptlet中或者<jsp:useBean>等中进行,语法冗长,如果只想要取得属性,EL则可以更为简洁,例如:
<h1>
    <jsp:getProperty name="user" property="name" />登陆成功
    ${user.name}登陆成功
</h1>
  • 上面两行代码作用完全一样。
  • 在EL中,可以使用EL隐式对象指定范围来存取属性,若不指定属性的存在范围,则也是默认page、request、session、application的顺序。
  • 数组对象可以使用[]指定索引来访问,如果属性是个List类型的对象,也可以使用[]运算符指定索引来进行访问元素。
  • 有些时候点运算符和[]运算符可以混用,如:
${login.user}
${login["user"]}
自定义EL函数
  • 如果设计了一个Util类,其中有个length()静态方法可以将传入的Collection长度返回。例如原先可以这么使用:
<%= Util.length(request.getAttribute("someList")) %>
  • 使用EL来调用,以下也许是想要编写的方式:
${ util:length(requwstScope.someList)}
  • 如果这是想要的需求,可以自定义EL函数来满足这项需求。自定义EL函数的第一步是编写类,必须是一个公开类,而想要调用的方法必须是公开且静态的方法。例如,Util类可能是这么编写的:
import java.util.Collection;

public class Util {
    public static int length(Collection collection){
        return collection.size();
    }
}
  • Web容器必须知道如何将这个类中的length()方法当做EL函数来使用,所以必须编写一个标签程序库(Tag Library Descriptor, TLD)文件,这个文件是个XML文件,后缀为*.tld。例如:
<?xml version="1.0" encoding="UTF-8"?>

<taglib xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
        version="2.1">

    <tlib-version>1.0</tlib-version>
    <short-name>util</short-name>
    <uri>http://chapter6.user/util</uri>

    <function>
        <description>Collection Length</description>
        <name>length</name> <!--自定义的EL函数名称-->
        <function-class>user.Util</function-class><!--对应的哪个类-->
        <function-signature>int length(java.util.Collection)</function-signature> <!--对应至该类的哪个方法-->
    </function>
</taglib>
  • 接着编写一个JSP来使用这个自定义EL函数。
<%@ taglib prefix="util" uri="http://chapter6.user/util" %>
<%--
  Created by IntelliJ IDEA.
  User: lancibe
  Date: 2021/4/9
  Time: 下午3:25
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>自定义EL函数</title>
</head>
<body>
    ${util:length(requestScope.someList)}
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lanciberrr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值