目录
四、JSP 中 out 输出和 response.getWriter() 输出的区别
一、JSP简介
1、什么是 jsp,它有什么用?
(1)jsp 的全称是 java server pages,叫做 Java 服务器页面。
jsp 的主要作用是代替 Servlet 程序回传 html 页面的数据这方面的操作。因为使用 Servlet 程序回传 html 页面数据是一件非常繁琐的事情,开发和维护的成本都很高。
举个例子:假如在登录页面输入的密码错误,想要把这个提示显示(补充)到 html 页面上,如果用 Servlet 程序写一个输出流来改变 html 文件,这样的操作显然非常麻烦且不好维护。
(2)创建 web 工程时,默认的 index.jsp:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1><%= "Hello World!" %>
</h1>
<br/>
<a href="hello-servlet">Hello Servlet</a>
</body>
</html>
(3)只要是在 Web 工程目录下,都可以创建 jsp 文件:
(4)jsp 也是个页面,所以访问方式什么的都和 html 一样
http://ip:port/工程路径/index.jsp
2、jsp 的本质
jsp 本质上是一个 Servlet 程序。
(1)当我们第一次访问 jsp 页面的时候,Tomcat 服务器会帮我们把 jsp 页面翻译称为一个 .java 源文件,并且把他编译为 .class 字节码程序:
(1-1)启动服务器即可得到服务器资源的本地绝对路径:
(1-2)按下面路径即可找到这两个文件:
(1-3)打开 java 文件就能看到这个类是继承自 HttpServlet 的。
(1-4)观察 index_jsp.java,就能发现,它的底层实现,就是通过输出流把 html 页面数据回传到客户端的:
二、JSP 的三种语法
1、jsp 头部的 page 指令
(1)jsp 的 page 指令可以修改 jsp 页面中一些重要的属性、行为。
- language:表示 jsp 翻译后是什么语言文件。(暂时只支持 java)
- contentType:表示 jsp 返回的数据的编码格式。也是源码中 response.setContentType() 的参数值
- pageEncoding:表示 jsp 页面自己的字符集。
- import:跟 java 源代码一样,用于导包、导类。
- errorPage:表示 jsp 页面错误后,自动跳转到的路径。(因为 jsp 会被翻译为 Servlet 程序,会被服务器解析,所以这个路径需要以 / 开头,表示请求地址为 http://ip:port/工程路径/,映射到 web 目录)
- isErrorPage:设置当前 jsp 页面是否是错误信息页面,默认为 false。如果是 true,则可以获取异常信息。
- session:当访问当前 jsp 页面,是否会创建 HttpSession 对象,默认是true。
- extends:设置 jsp 翻译得到的 java 类默认继承自哪一个类
(2)out 输出流使用的属性
- autoFlush:当 out 输出流缓冲区满了之后,是否自动刷新缓冲区,默认 true。
- buffer:设置 out 缓冲区大小,默认 8 kb。若缓冲区溢出且未设置 autoFlush 为 true,则页面会报错,提示“JSP Buffer overflow”
2、jsp 的常用脚本
(1)声明脚本(极少使用)
声明脚本的格式是:
<%! 定义 java 代码 %>
在 index_jsp.java 中,声明脚本体现为成员变量(函数)和代码块。
(1-1)声明脚本的四个作用:
- 声明类属性
- 声明 static 静态代码块
- 声明类的方法
- 声明内部类
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import = "java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1><%= "Hello World!" %></h1>
<%-- 声明类属性 --%>
<%!
private Integer id;
private String name;
private static Map<Integer, String> map;
%>
<%-- 声明 static 静态代码块 --%>
<%!
static {
map = new HashMap<>();
map.put(1, "123456");
}
%>
<%-- 声明类的方法 --%>
<%!
public int sum(int a, int b) {
return a + b;
}
%>
<%-- 声明内部类 --%>
<%!
public static class A {
private Integer num = 11;
}
%>
</body>
</html>
(2)表达式脚本(常用)
表达式脚本的格式是:
<%= 表达式 %>
(2-1)表达式脚本的作用:在 jsp 页面上输出数据。
在 index_jsp.java 中,表达式脚本体现为 out 输出流的参数。
(2-2)表达式脚本的特点:
- 所有的表达式脚本都会被翻译到 _jspService() 方法中
- 表达式脚本都会被翻译成为 out.print() 输出到 jsp 页面上
- 由于表达式脚本翻译的内容都在 _jspService() 方法中,所以 _jspService() 方法中的对象都可以直接使用
(2-3)使用示例:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import = "java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1><%= "Hello World!" %></h1>
<%-- 输出整型 --%>
<%=
12
%>
<%-- 输出浮点型 --%>
<%=
12.12
%>
<%-- 输出字符串 --%>
<%=
"这是字符串"
%>
<%-- 输出对象 --%>
<%-- 注意这里先使用了 声明脚本 定义变量 --%>
<%!
private static Map<Integer, String> map;
static {
map = new HashMap<>();
map.put(1, "123");
}
%>
<%=
map
%>
<%-- 直接使用 _jspService() 中的对象 --%>
<%-- 访问时添加参数 username = root --%>
<%=
request.getParameter("username")
%>
</body>
</html>
(3)代码脚本
代码脚本的格式是:
<% java语句 %>
代码脚本可以写:
- if 语句
- for 循环语句
- 在 _jspServlet() 方法中可以写的代码,在代码脚本中都可以写
(3-1)代码脚本的作用是:可以在 jsp 页面中,编写我们自己需要的功能。一般写的是 java 语句,如果需要方法,要用声明脚本。
需要注意的是,在代码脚本中不能嵌入 html 语法,这在下面的示例中有所体现。
(3-2)代码脚本的特点:
- 代码脚本翻译之后,都在 _jspServlet() 方法中
- 代码脚本由于翻译到 _jspServlet() 方法中,所以在 _jspServlet() 方法中的对象都可以直接使用
- 还可以由多个代码脚本块组合,完成一个完整的 java 语句
- 代码脚本还可以和表达式脚本一起使用,在 jsp 页面上输出数据
(3-3)示例:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import = "java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1><%= "Hello World!" %></h1>
<%-- if 语句 --%>
<%
int i = 11;
if (i == 10) {
System.out.println("i == 10");
} else {
System.out.println("i != 10");
}
%>
<%-- for 循环语句 --%>
<%
for (i = 1; i <= 10; ++ i) {
System.out.println(i);
}
%>
<%-- 在 _jspServlet() 方法中可以写的代码,在代码脚本中都可以写 --%>
<%-- 访问时记得加上 username = root --%>
<%
String username = request.getParameter("username");
System.out.println(username);
%>
<%-- html 和 表达式脚本 和 代码脚本 组合 --%>
<%
i = 10;
if (i == 10) {
%>
<h1> i == 10 </h1>
<%
} else {
%>
<h1> i != 10 </h1>
<%
}
%>
<%--------------------------------%>
<table border = "1" cellspacing="0">
<%
for (int j = 1; j <= 10; ++ j) {
%>
<tr>
<td> 第<%= j %>行 第 1 列 </td>
<td> 第<%= j %>行 第 2 列 </td>
</tr>
<%
}
%>
</table>
</body>
</html>
3、jsp 中的三种注释
(1)html 注释
<!-- 这是html注释 -->
html 注释会被翻译到 java 源代码中,在 _jspServlet() 方法中,以 out.writer() 输出到客户端。
(2)java 注释
<%
// 这是 java 注释
/*
这是 java 注释
*/
%>
就是写在声明、表达式、代码脚本中的 java 注释。
(3)jsp 注释
<%-- 这是 jsp 注释 --%>
jsp 注释可以注释 jsp 文件中的所有代码。
三、JSP 中的九大内置对象和四个域对象
1、内置对象
jsp 中的内置对象是指,Tomcat 在翻译 jsp 称为 Servlet 源码后,Tomcat 提供的九个对象。
2、域对象
域对象是可以像 Map 一样存取数据的对象,四个域对象功能一样,不同的是他们对数据的存取范围。
(1)上述 9 个内置对象中,有 4 个是域对象:
- pageContext(PageContext 类),当前 jsp 页面范围内有效
- request(HttpServletRequest 类),一次请求有效
- session(HttpSession 类),一个会话范围内有效(打开浏览器访问服务器,直到关闭浏览器)
- application(ServletContext 类),整个 Web 工程范围内有效(只要 Web 工程不停止,数据都在)
(2)使用顺序
虽然四个域对象都可以存取数据,但在使用上有优先顺序。一般推荐范围小的优先使用,优先级:
pageContext > request > session > application
使用范围小的对象,可以让不需要用到的数据在最短的时间内得到释放,减轻服务器的压力。
四、JSP 中 out 输出和 response.getWriter() 输出的区别
1、输出顺序固定原因
(1)运行下面的 jsp 页面:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import = "java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1><%= "Hello World!" %></h1>
<%
response.getWriter().write("<h1> response.getWriter() 输出 </h1>");
out.write("<h1> out 输出 </h1>");
%>
</body>
</html>
(2)将 response 的输出和 out 的输出语句调换顺序,会发现依旧是 response 的输出在前面。原因如下:
2、验证
(1)代码:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import = "java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1><%= "Hello World!" %></h1>
<%
out.write("<h1> out 输出1 </h1>");
out.flush();
response.getWriter().write("<h1> response.getWriter() 输出 </h1>");
out.write("<h1> out 输出2 </h1>");
%>
</body>
</html>
(2)结果:
由于 jsp 翻译之后,底层源代码都是使用 out 进行输出,所以一般情况下,我们在 jsp 页面也是用 out 进行输出。避免打乱页面输出内容顺序。
3、out 的 write() 和 print()
out 输出有两种方法,write() 和 print(),那么用哪一种比较好呢?
(1)运行下面代码,会发现 out.write() 不能正确输出整型:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import = "java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1><%= "Hello World!" %></h1>
<%
out.write("www");
out.print("www");
%>
<br/>
<br/>
<%
out.write(12);
out.print(12);
%>
</body>
</html>
(2)观察源码 JspWriterImpl 类,可以发现,无论什么类型的参数传入 print() 方法,print() 方法都会将其转换为字符串类型,然后再调用 write() 方法。比如:
// 只是个简单的举例,不是实际源码
void print(int a) {
out.write(String.valueOf(a));
}
因此,在 jsp 页面中,可以统一使用 out.print() 来进行输出。
五、JSP 的常用标签
假设有很多页面使用到了同一个标签,当这个标签的内容有所改变时,就需要将所有页面逐一改变,非常麻烦。
使用包含可以将这个标签链接至所有页面,从而只需要更改这个标签,即可实现所有页面的修改。
1、jsp 静态包含
<%@ include file = "" %>
file 属性指定要包含的 jsp 页面的路径。地址一般用相对路径。
(1)示例:
(1-1)index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
头部信息<br/>
主体信息<br/>
<%@include file="./include/pageFoot.jsp"%>
</body>
</html>
(1-2)/include/pageFoot.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
页脚信息<br/>
</body>
</html>
(2)静态包含的特点:
- 静态包含不会翻译被包含的 jsp 页面(即不会产生 pageFoot.jsp 的 java 代码)
- 静态包含实际上是把被包含的 jsp 页面的源码搬到 index.jsp 的 java 代码相应的位置中
2、jsp 动态包含
<jsp:include page = ""></jsp:include>
page 属性指定要包含的 jsp 页面的路径。
(1)示例:
index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
头部信息<br/>
主体信息<br/>
<jsp:include page="./include/pageFoot.jsp"></jsp:include>
</body>
</html>
(2)动态包含的特点:
- 动态包含会把包含的 jsp 页面也翻译为 java 代码
- 动态包含底层代码使用如下,调用被包含 jsp 页面执行输出
- 动态包含,还可以传递参数
-
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> 头部信息<br/> 主体信息<br/> <jsp:include page="./include/pageFoot.jsp"> <jsp:param name="username" value="root"/> </jsp:include> </body> </html>
-
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> 页脚信息<br/> 获取参数:<%= request.getParameter("username") %> </body> </html>
(3)原理:
3、静态包含和动态包含的易错点
假设页面 A.jsp 需要 include 页面 B.jsp(或者其他资源,如:css、html、js、img……),那么:
- 如果使用静态包含,则需要将 B.jsp 中一些可能与 A.jsp 内的元素或变量删除;
- 如果使用静态包含,则标签需要放在正确的位置(比如:<%@ page %> 这类标签就不能放在 <head> 内);
- 简单的说,动态包含适用性更强,静态包含需要适配上下文环境
4、请求转发
<jsp:forward page=""></jsp:forward>
page:设置请求转发的路径。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<jsp:forward page="./include/pageFoot.jsp"></jsp:forward>
</body>
</html>
其功能与 forward(req, resp) 一致。
(1)练习使用,输出学生信息
(1-1)Student 类
package com.test;
public class Student {
private Integer id;
private String name;
private Integer age;
private String phone;
public Student() {
}
public Student(Integer id, String name, Integer age, String phone) {
this.id = id;
this.name = name;
this.age = age;
this.phone = phone;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", phone='" + phone + '\'' +
'}';
}
}
(1-2)student.jsp:(导入上面的 Student 类)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import ="com.test.Student" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<html>
<head>
<title>Title</title>
<style>
table {
border: 1px solid red;
width: 600px;
}
td, th {
border: 1px red solid;
}
</style>
</head>
<body>
<%
List<Student> studentList = new ArrayList<>();
for (int i = 1; i <= 10; ++ i) {
studentList.add(new Student(i, "name" + i, 18 + i, "phone" + i));
}
%>
<table border = "1" cellspacing = "1">
<tr>
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
<th>电话</th>
<th>操作</th>
</tr>
<% for (Student student : studentList) { %>
<tr>
<td> <%= student.getId() %> </td>
<td> <%= student.getName() %> </td>
<td> <%= student.getAge() %> </td>
<td> <%= student.getPhone() %> </td>
<td> 删除、修改 </td>
</tr>
<% } %>
</table>
</body>
</html>
(1-3)Servlet 程序,模拟出了查询数据库得到的学生信息:
package com.web;
import com.test.Student;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.*;
public class searchStudentServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求参数
// 发送 sql 语句查询学生信息
// 使用 for 循环生成数据,做一个简单的模拟
List<Student> studentList = new ArrayList<>();
for (int i = 1; i <= 10; ++ i) {
studentList.add(new Student(i, "name" + i, 18 + i, "phone" + i));
}
// 保存查询结果至 request 域中
req.setAttribute("stuList", studentList);
// 请求转发到 student.jsp 页面
req.getRequestDispatcher("./include/student.jsp").forward(req, resp);
}
}
(1-4)启动 Tomcat,访问 servlet 程序,就会请求转发至 jsp 页面:
六、Listener 监听器
Listener 监听器是 JavaWeb 三大组件之一。JavaWeb 三大组件分别是:Servlet 程序、Filter 过滤器、Listener 监听器。Listener 是 JavaEE 的规范,就是接口。
监听器的作用是,监听某种事物的变化,然后通过回调函数,反馈给客户(程序)去做一些相应的处理。
1、ServletContextListener 监听器
ServletContextListener 它可以监听 ServletContext 对象的创建和销毁。(ServletContext 对象在 Web 工程启动的时候创建,在 Web 工程停止的时候销毁)。
当监听到 ServletContext 对象创建之后,监听器就会调用 contextInitialized();
当监听到 ServletContext 对象销毁之后,监听器就会调用 contextDestroyed();
(1)使用步骤:
编写一个类实现 ServletContextListener;
实现它的两个回调方法;
到 web.xml 中去配置监听器;
(2)示例:
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<listener>
<listener-class>com.Listener.ServletContextListenerImpl</listener-class>
</listener>
</web-app>
ServletContextListenerImpl 类:
package com.Listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ServletContextListenerImpl implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext 对象被创建");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext 对象被销毁");
}
}