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网页中输出这两个符号,可以使用
<%
,和%>
或使用%\>
。 - 如果想禁用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>