一、Java Web 概述
Java Web 学习笔记。Java Web 内容庞杂,很难通过学习一本书就全面掌握所有的知识点,所以,如有错误,烦请联系笔者,QQ邮箱:2737035853@qq.com。本文主要是老师的授课笔记。链接如下:一、Java Web 概述 · 语雀 (yuque.com)
0. 前言
0.1 什么是 Java Web
狭隘的定义:JavaWeb = Servlet + JSP + 前端
宽泛的定义:一切以 Java 为基础的 Web 开发技术,包括各种开发框架。
0.2 为什么要学习 Java Web
-
我们现在能够接触到的系统中,绝大部分都是基于 Web 的。
-
在所有的开发技术中,Java 的生态体系最完善,最稳定,应用范围最广。
-
Java Web 是所有开发框架的基础,开发框架中各种概念、术语,其基础都是 Java Web,所以当我们掌握了 Java Web,就可以自学开发框架了。
-
做网站最基础的技术,就是 Java Web,即 Servlet 和 JSP。
1. Web体系结构
本章目标:Web 客户端和 Web 服务器之间如何对话
注意:“客户端”即可以是使用电脑的人,也可以是电脑,或者是浏览器,需要根据具体的语境来确定“客户端”的含义。
很多人想要一个超级网站来打败竞争对手,这就需要一个灵活而且可扩展的体系结构,实现该体系结构的手段就是 Servlet 和 JSP。
在构建具体的 Web 应用之前,我们先看一看 WWW(World Wide Web,万维网)长什么样子。
Web 包括数以亿计地客户和服务器,这些客户和服务器之间通过有线和无线网络连接。我们的目标就是构建一个全世界客户都能访问的 Web 应用。
1.1 Web 服务器能做些什么
Web 服务器接收客户请求,然后向客户返回一个结果(响应)。
-
用户可以通过 Web 浏览器请求一个资源。
-
Web 服务器得到请求后,查找该资源,然后向用户返回一个结果(响应)。
-
资源可能是一个 HTML 页面、一张图片、一个声音文件或者一个 PDF 文档。
整个过程就是客户端请求某个资源,再由服务器返回所请求的资源。
如果没有这样一个资源,或者资源不在服务器原来预想的位置上,此时客户端会显示一个“404 Not Found”错误。即如果服务器找不到客户所请求的资源,它(服务器)就是给客户端这样一个响应。
1.2 Web 客户端能做什么
Web 客户端允许用户请求服务器上的某个资源,并且向用户显示请求的结果。
- 浏览器就是一个软件(比如 Chrome 或 Edge),它知道怎么与服务器通信。
- 浏览器还有一个重要的任务,就是解释 HTML 代码,并把 Web 页面呈现给用户。
1.3 HTML 和 HTTP
服务器和客户端都知道 HTML 和 HTTP。
1.3.1 HTML
-
当服务器对一个请求做出回答时,通常会向浏览器发送某种类型的内容,以便浏览器显示。
-
服务器一般会向浏览器发送一组用 HTML 编写的指令(HTML 文档)。
-
HTML 告诉浏览器怎样把内容呈现给用户(规则)。
-
所有 Web 浏览器都知道如何处理 HTML,不过,如果页面是用新版本的 HTML 编写的(如 HTML5),有时老版本的浏览器可能无法完全理解。
1.3.2 HTTP
-
Web 上客户端和服务器之间的大多数会话都是使用 HTTP 协议完成的,HTTP 协议支持简单的请求和响应会话。
-
客户端发送一个 HTTP 请求,服务器会用一个 HTTP 响应做出应答。
-
关键是:如果你是一个 Web 服务器,就必须讲 HTTP。
-
Web 服务器向客户端发送 HTML 页面时,就是使用 HTTP 发送的。
再次回到本章的目标:
客户端和服务器如何对话呢?
-
客户端和服务器必须有一种共同的语言。
-
在 Web 上,客户端和服务器必须讲 HTTP。
-
浏览器必须懂 HTML。
HTML 告诉浏览器怎样向用户显示内容
HTTP 是 Web 上客户端和服务器之间进行通信所用的协议
服务器使用 HTTP 向客户端发送 HTML
1.4 什么是 HTTP 协议
-
HTTP 是 TCP/IP 的上层协议。
-
TCP 负责确保从一个网络节点向另一个网络节点发送的文件能作为一个完整的文件到达目的地,尽管在具体传送过程中这个文件可能会分解为小块传输。
-
IP 是一个底层协议,负责把数据块(数据包)沿路移动/路由到目的地。
-
HTTP 是另一个网络协议,有一些 Web 特定的特性,不过它要依赖于 TCP/IP 从一处向另一处完整地传送请求和响应。
-
HTTP 会话的结构是一个简单的请求/响应序列:浏览器发出请求,服务器做出响应。
目前,我们不需要记住 HTTP 规范。
1.5 HTML 是 HTTP 响应的一部分
-
HTTP 响应可以包含 HTML。
-
HTTP 还会在响应中所包含的内容(即服务器返回的东西)前面,增加首部信息。
-
HTML 浏览器使用首部信息来帮助处理 HTML 页面。
1.6 请求里是什么
-
会看到一个 HTTP 方法名。
-
方法名告诉服务器做了哪一类请求,并指出消息中余下的部分该如何格式化。
-
HTTP 协议有许多种方法,但最常用的当属 GET 和 POST。
1.7 GET/POST
GET
-
GET 是最简单的 HTTP 方法,主要任务是要求从服务器获得一个资源并把资源发送回来。
-
这个资源可能是一个 HTML 页面、一张图片,一个 PDF 文档等等。
-
具体是什么资源没有关系,关键是,GET 就是要从服务器拿些东西回来。
POST
- POST 是一种更强大的请求,就像是 GET++。
- 利用 POST,可以请求某个资源,同时向服务器发送一些表单数据。
Tips:
GET 和 POST 是使用最多的两个方法,除此之外,还有 HEAD、TRACE、PUT、DELETE、OPTIONS、CONNECT。
2. 技术分类
2.1 静态网站
相关技术:HTML、CSS、Javascript 等技术。
静态网页文件的扩展名为“.htm”或“.html”,这些页面不能直接与服务器进行数据交互。
2.2 动态网站
基于数据库架构的网站,一般分为前端和后端。
特点:
-
交互性
-
自动更新
-
随机性
注意:
动态网站一般采用动静结合的原则:网站中频繁更新的内容采用动态技术;不需要更新的采用静态技术。
2.3 常见的动态网站技术
-
JSP
-
PHP
-
ASP
2.4 动态网站架构
B/S:Browser/Server
考点:GET 与 POST 的区别是什么?
第一层理解
参考:https://www.w3school.com.cn/tags/html_ref_httpmethods.asp
参考:https://www.jianshu.com/p/678ff764a253
参考:https://zhuanlan.zhihu.com/p/38217343
二、Servlet
1. Servlet 初窥
1.1 Servlet 介绍
1.1.1 Servlet 简介
Servlet 是用 Java 编写的服务器端程序。
狭义的 Servlet 是指 Java 语言实现的一个接口,广义的 Servlet 是指任何实现了这个 Servlet 接口的类。
功能:处理请求和发送响应。
-
获取用户提交的表单数据(如注册、登录等)
-
呈现后端处理结果
-
动态创建网页(很少使用)
1.1.2 案例 - HelloServlet
HelloServlet.java
package com.demo;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取输出流
PrintWriter out = response.getWriter();
out.println("Hello Servlet!");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
代码分析:
-
@WebServlet 注解(标注)
-
当请求该 Servlet 时,服务器就会自动读取当中的信息。
-
如果注解为@WebServlet("/HelloServelt"),则表示访问该 Servlet 的请求路径为 http://localhost:8080/HelloServelt/HelloServelt(http://服务器地址:8080/项目名/Servlet的url)。
-
代码中省略了 urlPatterns 属性名,完整的写法应该是:[@WebServlet(urlPatterns ](https://www.yuque.com/WebServlet(urlPatterns) = “/HelloServelt”),如果在 @WebServlet 中需要设置多个属性,必须给属性值加上属性名称,中间用逗号隔开,否则会报错。
-
以前在使用 Servlet 时,需要在 web.xml 中对 Servlet 进行配置。Servlet 3.0 开始支持已注解的形式对 Servlet 进行配置。
-
HelloServlet:类名,Servlet 本质为一个 Java 类,所以首字母大写。Servlet 的命名方式一般为 Servlet 或者 Controller。
-
HttpServlet
-
HttpServlet 是 Servlet 接口的一个实现类,同时又是 GenericServlet 的子类,是在 GenericServlet 的基础上做了增强。
-
HTTP 的请求方式包括 DELETE,GET,OPTIONS,POST,PUT 和 TRACE,在HttpServlet 类中分别提供了相应的服务方法, 它们是分别是:doDelete(),doGet(),doOptions(),doPost(),doPut() 和 doTrace()
-
如果继承了 HttpServlet 没有实现任何的 doXxx 方法则会抛出一个异常405。
-
Serialize
-
含义
-
序列化:将对象写入到 IO 流中
-
反序列化:从 IO 流中恢复对象
-
意义:序列化机制允许将实现序列化的 Java 对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
-
使用场景:所有可在网络上传输的对象都必须是可序列化的,比如 RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的 Java 对象都必须是可序列化的。
-
建议:程序创建的每个 JavaBean 类都实现 Serializeable 接口。
-
serialVersionUID
-
serialVersionUID 是 Java 为每个序列化类产生的版本标识,可用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象。
-
如果接收方接收的类的 serialVersionUID 与发送方发送的 serialVersionUID 不一致,进行反序列时会抛出 InvalidClassException。
-
序列化的类可显式声明 serialVersionUID 的值,如上面代码中所示。
-
当显式定义 serialVersionUID 的值时,Java 根据类的多个方面(具体可参考 Java 序列化规范)动态生成一个默认的 serialVersionUID 。
-
尽管这样,还是建议在每一个序列化的类中显式指定 serialVersionUID 的值,因为不同的 JDK 编译很可能会生成不同的 serialVersionUID 默认值,进而导致在反序列化时抛出 InvalidClassExceptions 异常。
-
所以,为了保证在不同的 JDK 编译实现中,其 serialVersionUID 的值也一致,可序列化的类必须显式指定 serialVersionUID 的值。
-
另外,serialVersionUID 的修饰符最好是 private,因为 serialVersionUID 不能被继承,所以建议使用 private 修饰 serialVersionUID。
-
案例在本文档的最后:案例 - Person 序列化
-
doGet() 和 doPost()
-
doGet() 和 doPost() 方法都有两个参数,分别为 HttpServletRequest 和 HttpServletResponse 类型。
-
这两个参数分别用于表示客户端的请求和服务器的响应。
-
通过 HttpServletRequest,可以从客户端中获得发送过来的信息;通过 HttpServletResponse,可以让服务器对客户端做出相应,最常用的就是向客户端发送信息。
-
如果在浏览器中直接输入地址来访问 Servlet 资源,属于使用 Get 方式访问。
1.2 HTTP GET 请求剖析
login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="LoginServlet" method="get">
<div>
<label for="username">用户名: </label>
<input type="text" id="username" name="username" placeholder="请输入用户名">
</div>
<div>
<label for=""password"">密码: </label>
<input type="text" id="password" name="password" placeholder="请输入密码">
</div>
<button type="submit">提交</button>
</form>
</body>
</html>
LoginServlet
package com.demo;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
PrintWriter out = response.getWriter();
out.print("<h1>" + username + "</h1>");
out.print("<h1>" + password + "</h1>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
运行系统,进行以下操作:
- 在地址栏中输入 login.html 进行访问:http://localhost:8080/HelloServlet/login.html
打开浏览器的 Console,查看 Network 标签页,发现以下信息:
其中,Request Method:GET,表示该请求的类型为 Get 请求,即当我们在地址栏中输入资源路径直接访问时,发起的请求类型为 Get 请求。
- 在 login.html 页面中输入用户名“guo”和密码“123”,点击“提交”按钮后,地址栏显示的信息如下:
http://localhost:8080/HelloServlet/LoginServlet?username=guo&password=123
打开浏览器的 Console,查看 Network 标签页,发现以下信息:
其中的 Request URL 与 地址栏中的值一致,分析如下:
-
?:表示要向该资源传递参数。
-
参数跟在?后面,以“键-值”对的形式传递,“=”左边是键名,“=”右边是键值,不同的参数之间用“&”分隔。
1.3 HTTP POST 请求剖析
修改 login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="LoginServlet" method="post">
<div>
<label for="username">用户名: </label>
<input type="text" id="username" name="username" placeholder="请输入用户名">
</div>
<div>
<label for=""password"">密码: </label>
<input type="text" id="password" name="password" placeholder="请输入密码">
</div>
<button type="submit">提交</button>
</form>
</body>
</html>
LoginServlet.java 保持不变。
进行如下操作:
- 在地址栏中输入 login.html 进行访问:http://localhost:8080/HelloServlet/login.html。
- 在 login.html 页面中输入用户名“guo”和密码“123”,点击“提交”按钮后,打开浏览器的 Console,查看 Network 标签页,发现以下信息:
- Request Method:POST,与 form 表单的 action 属性值一致。
- Post 请求所携带的参数显示在最下面的“Form Data”中。
考点:GET 请求和 POST 请求有什么区别?
第一层理解:
参考链接:https://www.w3school.com.cn/tags/html_ref_httpmethods.asp
第二层理解:
- GET 和 POST 本质上没有区别。
-
GET 和 POST 是什么?HTTP 协议中的两种发送请求的方法。
-
HTTP 是什么?HTTP 是基于 TCP/IP 的关于数据如何在万维网中如何通信的协议。
-
HTTP 的底层是 TCP/IP。所以 GET 和 POST 的底层也是 TCP/IP,也就是说,GET/POST 都是 TCP 链接。GET 和 POST 能做的事情是一样一样的。你要给 GET 加上 request body,给 POST 带上 url 参数,技术上是完全行的通的。
那么,“第一层理解”里的那些区别是怎么回事?
在我大万维网世界中,TCP 就像汽车,我们用 TCP 来运输数据,它很可靠,从来不会发生丢件少件的现象。但是如果路上跑的全是看起来一模一样的汽车,那这个世界看起来是一团混乱,送急件的汽车可能被前面满载货物的汽车拦堵在路上,整个交通系统一定会瘫痪。为了避免这种情况发生,交通规则 HTTP 诞生了。HTTP给汽车运输设定了好几个服务类别,有 GET, POST, PUT, DELETE 等等,HTTP 规定,当执行 GET 请求的时候,要给汽车贴上 GET 的标签(设置 method 为GET),而且要求把传送的数据放在车顶上(url 中)以方便记录。如果是 POST 请求,就要在车上贴上POST的标签,并把货物放在车厢里。当然,你也可以在 GET 的时候往车厢内偷偷藏点货物,但是这是很不光彩;也可以在 POST 的时候在车顶上也放一些数据,让人觉得傻乎乎的。HTTP 只是个行为准则,而 TCP 才是 GET 和 POST 怎么实现的基本。
但是,我们只看到 HTTP 对 GET 和 POST 参数的传送渠道(url还是requrest body)提出了要求。“第一层理解”里关于参数大小的限制又是从哪来的呢?
在我大万维网世界中,还有另一个重要的角色:运输公司。不同的浏览器(发起 http 请求)和服务器(接受 http 请求)就是不同的运输公司。 虽然理论上,你可以在车顶上无限的堆货物(url 中无限加参数)。但是运输公司可不傻,装货和卸货也是有很大成本的,他们会限制单次运输量来控制风险,数据量太大对浏览器和服务器都是很大负担。业界不成文的规定是:超过的部分,恕不处理。如果你用GET服务,在 request body 偷偷藏了数据,不同服务器的处理方式也是不同的,有些服务器会帮你卸货,读出数据,有些服务器直接忽略,所以,虽然 GET 可以带 request body,也不能保证一定能被接收到哦。
好了,现在你知道,GET 和 POST 本质上就是 TCP 链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
- GET和POST还有一个重大区别。
简单说:
GET产生一个TCP数据包;POST产生两个TCP数据包。
长的说:
- 对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
- 而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
也就是说,GET只需要汽车跑一趟就把货送到了,而POST得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。
因为POST需要两步,时间上消耗的要多一点,看起来GET比POST更有效。因此Yahoo团队有推荐用GET替换POST来优化网站性能。但这是一个坑!跳入需谨慎。为什么?
(1)GET与POST都有自己的语义,不能随便混用。
(2)据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。
(3)并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。
参考链接:https://www.jianshu.com/p/678ff764a253
第三层理解:https://zhuanlan.zhihu.com/p/38217343
1.4 HTTP 响应剖析,到底什么是“MIME类型”
HTTP 响应包括一个首部和一个体:
- 首部信息告诉浏览器使用了什么协议,请求是否成功,以及体中包括何种类型的内容。
- 体包含了让浏览器显示的具体内容。
案例:
- 地址栏:http://localhost:8080/HelloServlet/login.html
·
- 地址栏:http://localhost:8080/HelloServlet/LoginServlet (POST 和 GET 请求的响应头一样)
结论:
- 响应首部中 Content-Type(内容类型)的值称为 MIME 类型。
- MIME 类型告诉浏览器要接收的数据是什么类型,这样浏览器才能知道如何显示这些数据。
注意:MIME 类型值与 HTTP 请求“Accept”首部中所列的值相关。
1.5 URL 剖析
http://localhost:8080/HelloServlet/login.html
-
http:协议,告诉服务器使用什么通信协议。
-
localhost:服务器,所请求物理服务器的唯一域名。这个域名会映射到一个唯一的 IP 地址(现代域名解析中,为了负载均衡,一个域名可能会对应多个 IP 地址)。IP 地址是一串数字,数字之间用“.”隔开,形式为“xx.xx.xx.xx”。在这里也可以指定一个 IP 地址而不是域名,不过域名更容易记忆。(127.0.0.1)
-
8080:端口。一个服务器可以支持多个端口。一个服务器应用由端口标识。如果在 URL 中没有指定端口,默认端口则是端口80,这正是 Web 服务器的默认端口,即对于80端口,可以不写;但是对于其他端口,必须填写。
-
HelloServlet:路径,所请求的资源在服务器上的路径。因为 Web 上大多数较早的服务器都采用 Unix 系统,因此还是 Unix 语法来描述 Web 服务器的目录层次结构(Windows使用“\”来表示目录层级结构)。
-
login.html:资源,所请求的内容的名字。可以是一个 HTML 页面,一个 Servlet,一个图像、PDF、音频、视频,或者服务器能够提供的任何资源。这部分是可选的,如果 URL 中没有这一部分,大多数 Web 服务器都会默认地查找 index.html(依据 web.xml)。
1.6 端口解析
TCP 端口只是一个数字而已。
端口是一个16位数,标识服务器硬件上一个特定的软件程序。
问题:一台服务器,即可以接收用户的普通的HTTP请求,也可以接收 Telnet(远程连接)请求、FTP(文件传输)请求和 POP3(邮件)请求,服务器该如何区分每种不同的请求?
答案:区分的依据就是端口号。
端口表示与服务器硬件上运行的一个特定软件的逻辑连接,可以把端口看作是唯一的标识符。
-
服务器上有65536个端口(0~65535);
-
端口并不表示一个可以插入物理设备的位置,端口只是表示服务器应用的“逻辑”数而已。
-
如果没有端口号,服务器就没有办法知道客户想连接哪个应用。
-
每个应用可能有自己特定的协议。
从0到1023的 TCP 端口号已经保留,由一些众所周知的服务使用(包括我们最关心的“NO.1”—端口80)。我们自己的定制服务器程序不要使用这些端口。
2. Servlet 生命周期
2.1 什么是生命周期
问题:为什么 Servlet 中没有 Main() 方法?
回答:Servlet 是运行在服务器上的,其生命周期由 Servlet 容器(Tomcat)负责管理。
问题:生命周期长什么样子?
回答:Servlet 生命周期是指 Servlet 实例从创建到响应客户请求直至销毁的过程。
Servlet 生命周期中最重要的3个阶段,由3个 Servelt API管理并体现。
-
init():用于 Servlet 初始化。当容器创建 Servlet 实例后,会自动调用此方法。
-
service():
-
用于服务处理。当客户端发出请求时,容器会自动调用此方法进行处理,并将结果返回到客户端。
-
有2个参数,分别使用 ServletRequest 接口 和 ServletResponse 接口的对象来处理请求和响应。
-
destory():用于销毁 Servlet。当容器销毁 Servlet 实例时自动调用此方法,释放 Servlet 实例,清除当前 Servlet 所持有的资源。
Servlet 生命周期:
-
装载 Servlet:该项操作一般是动态执行的,有些服务器提供了相应的管理功能,可以在启动的时候就装在 Servlet。
-
创建一个 Servlet 实例:容器创建 Servlet 的一个实例对象,即调用构造器。
-
初始化:init()。
-
服务:service()。
-
销毁:destory()。
2.2 案例
LoginServlet
package cn.edu.qfnu.servlet;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/LifeCycleServlet")
public class LifeCycleServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String METHOD_GET = "GET";
private static final String METHOD_POST = "POST";
public LifeCycleServlet() {
super();
System.out.println("LifeCycleServlet Constructor...");
}
public void init(ServletConfig config) throws ServletException {
System.out.println("LifeCycleServlet init...");
}
public void destroy() {
System.out.println("LifeCycleServlet destroy...");
}
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("LifeCycleServlet service...");
String method = request.getMethod();
if (method.equals(METHOD_GET)) {
doGet(request, response);
} else if (method.equals(METHOD_POST)) {
doPost(request, response);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("LifeCycleServlet doGet...");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("LifeCycleServlet doPost...");
}
}
3. 请求转发和重定向
请求转发和重定向是 Servlet 处理完数据后进行跳转的两种主要方式。
3.1 请求转发
3.1.1 请求转发的概念及特点
-
将请求再转发到另一个页面或者 Servlet(页面和 Servlet 统一称为资源)。
-
此过程依然在 request 对象作用域范围之内。
-
转发后的地址栏内容不变。
-
请求转发使用 RequestDispatcher 接口中的 forward() 方法来实现,该方法可以把请求转发到另一个资源,并让该资源对浏览器的请求进行响应。
RequestDispatcher 接口有两个方法:
- forward() 方法:请求转发,可以从当前 Servlet 跳转到其他资源(页面或者 Servlet)。
- include() 方法:引入其他 Servlet。
注意:RequestDispatcher 是一个接口,那么如何使用接口的中的方法呢?
接口实例化!
通过使用 HttpRequest 对象的 getRequestDispatcher() 方法可以获得 RequestDispatcher 接口的实例对象。
3.1.2 案例
通过 LoginServlet 获取用户名和密码,交由 ValidationServlet 进行校验,输出校验结果。
LoginServlet.java
package cn.edu.qfnu.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取用户提交的用户名
String uname = request.getParameter("user_name");
// 获取用户提交的密码
String upwd = request.getParameter("pass_word");
request.setAttribute("username", uname);
request.setAttribute("password", upwd);
request.getRequestDispatcher("ValidationServlet").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
ValidationServlet.java
package cn.edu.qfnu.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/ValidationServlet")
public class ValidationServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = (String) request.getAttribute("username");
String password = (String) request.getAttribute("password");
if ("admin".equals(username)) {
if ("123".equals(password)) {
PrintWriter out = response.getWriter();
out.print("Welcome!");
}
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
3.2 重定向
3.2.1 重定向的概念及特点
-
页面重新定位到某个新地址。
-
之前的 request 失效,进入一个新的 request。
-
跳转后地址栏的内容将变为新的指定地址。
-
通过 HttpServletResponse 对象的 sendRedirect() 来实现。
-
该方法用于生成 302 响应码和 Location 响应头,从而通知客户端去重新访问 Location 响应头中指定的 URL。
3.2.2 案例
通过 LoginServlet 获取用户名和密码,交由 ValidationServlet 进行校验,如果用户和密码正确,则跳转到首页,否则跳转到错误信息提示页面。
LoginServlet.java
package cn.edu.qfnu.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取用户提交的用户名
String uname = request.getParameter("user_name");
// 获取用户提交的密码
String upwd = request.getParameter("pass_word");
request.setAttribute("username", uname);
request.setAttribute("password", upwd);
response.sendRedirect("index.html");
// request.getRequestDispatcher("ValidationServlet").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
ValidationServlet.java
package cn.edu.qfnu.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/ValidationServlet")
public class ValidationServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = (String) request.getAttribute("username");
String password = (String) request.getAttribute("password");
String path = "login.html";
if ("admin".equals(username)) {
if ("123".equals(password)) {
path = "index.html";
}
}
response.sendRedirect(path);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
4. 会话状态
HTTP 是一种无状态的协议,这就意味着 Web 服务器并不了解同一用户以前请求的信息,即 Web 服务器是没有记忆的。一旦发送了响应(即完成了一次通信过程,请求+响应),Web 服务器就会忘了你是谁,服务器上不会保留任何客户端的信息。换句话说,Web 服务器不记得你曾经做过请求,也不记得它们曾经给你发出过响应。
但是,对于现在的 Web 应用而言,需要记录特定客户端的一系列请求之间的关系,以便于对客户的状态进行管理,以便于对客户的状态进行跟踪。
比如在电商网站中,Web 服务器回味每个客户配置一辆购物车,购物车需要一直跟随客户,以便于客户将商品放入购物车中,而且,不同客户的购物车,绝对不能混肴,即客户 A 添加的商品,不能出现在客户 B 的购物车中。
常用的会话跟踪技术:
-
Cookie 技术
-
Session 技术
-
URL 重写
4.1 Cookie
4.1.1 Cookie 的概念
- Cookie 是服务器发送给客户端(一般是浏览器)的一小段文本,保存在浏览器所在机器的内存或磁盘上。
- Cookie 通过 HTTP Header 从服务器端返回到客户端,即 Cookie 首先由服务器创建,然后在服务器向客户端发送响应时,把 Cookie 一起发送给客户端。
Cookie 是会话跟踪的一种解决方案,典型应用就是记住用户登录状态。
Cookie 常用的方法:
方法 | 说明 |
---|---|
Cookie uCookie = new Cookie(“user”, username); | 创建保存用户名的 Cookie |
Cookie[] cookies = request.getCookies(); | 获取客户端所保存的该网站的所有 Cookie |
getMaxAge() / setMaxAge() | 读取/设置 Cookie 的过期时间。对于 setMaxAge():参数为一个负值,表示这个 Cookie 在用户退出浏览器后马上过期; 参数为 0,表示删除此 Cookie |
getName() / getValue() | 获取 Cookie 的名字 / 值 |
response.addCookie(uCookie) | 将 Cookie 发送到客户端 |
4.1.2 案例
login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="CookieServlet" method="get">
<div>
<label for="username">用户名: </label>
<input type="text" id="username" name="username" placeholder="请输入用户名">
</div>
<div>
<label for="password">密码: </label>
<input type="text" id="password" name="password" placeholder="请输入密码">
</div>
<div>
<input type="checkbox" name="saveCookie" value="yes">记住登录状态
</div>
<button type="submit">提交</button>
</form>
</body>
</html>
CookieServlet.java
package com.demo;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/CookieServlet")
public class CookieServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
// String saveCookie = request.getParameter("saveCookie");
String[] saveCookie = request.getParameterValues("saveCookie");
Cookie usernameCookie = new Cookie("username", username);
Cookie passwordCookie = new Cookie("password", password);
if (saveCookie!=null && "yes".equals(saveCookie[0])) {
usernameCookie.setMaxAge(60);
passwordCookie.setMaxAge(60);
} else {
usernameCookie.setMaxAge(0);
passwordCookie.setMaxAge(0);
}
response.addCookie(usernameCookie);
response.addCookie(passwordCookie);
PrintWriter out = response.getWriter();
out.print("Welcome: " + username);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
LoginHtmlServlet.java
package com.demo;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/LoginHtmlServlet")
public class LoginHtmlServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
String username = "";
String password = "";
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if ("username".equals(cookie.getName())) {
username = cookie.getValue();
}
if ("password".equals(cookie.getName())) {
password = cookie.getValue();
}
}
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>\r\n"
+ "<html>\r\n"
+ "<head>\r\n"
+ "<meta charset=\"UTF-8\">\r\n"
+ "<title>登录</title>\r\n"
+ "</head>\r\n"
+ "<body>\r\n"
+ " <form action=\"CookieServlet\" method=\"get\">\r\n"
+ " <div>\r\n"
+ " <label for=\"username\">用户名: </label>\r\n"
+ " <input type=\"text\" value=\"" + username + "\" id=\"username\" name=\"username\" placeholder=\"请输入用户名\">\r\n"
+ " </div>\r\n"
+ " <div>\r\n"
+ " <label for=\"password\">密码: </label>\r\n"
+ " <input type=\"text\" value=\"" + password + "\" id=\"password\" name=\"password\" placeholder=\"请输入密码\">\r\n"
+ " </div>\r\n"
+ " <div>\r\n"
+ " <input type=\"checkbox\" name=\"saveCookie\" value=\"yes\">记住登录状态\r\n"
+ " </div>\r\n"
+ " <button type=\"submit\">提交</button>\r\n"
+ " </form>\r\n"
+ "</body>\r\n"
+ "</html>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
Cookie 的本质就是键值对儿,即通过键值对儿的形式保存值,一个想要保留的值,对应一个 Cookie。这就带来了两个问题:
(1)安全
客户端相对于服务器来说,更容易被黑客攻破,但目前还没有发送应为 Cookie 所带来的重大安全问题,这主要也是由 Cookie 的安全机制所决定的:
-
Cookie 不会以任何方式在客户端被执行
-
浏览器会限制来自同一个网站的 Cookie 数目
-
单个 Cookie 的长度是有限制的
-
浏览器限制了最多可以接受的Cookie数目
(2)效率
使用 Cookie 可以将请求的状态信息传递到下一次请求中,但是如果传递的状态信息较多,将极大地降低网络传输效率,并且回增大服务器端程序地处理难度,为此,各种服务器端技术都提供了一种将会话状态保存在服务器端的方案,即 Session(会话)技术。
4.2 Session
4.2.1 Session 的概念
- Servlet 容器为每一个 HttpSession 对象分配一个唯一的标识符,称为 SessionID,同时将 SessionID 发送到客户端,由浏览器负责保存此 SessionID。
- 当客户端再发送请求时,浏览器会同时发送 SessionID,Servlet 容器可以从请求对象中读取 SessionID,根据 SessionID 的值找到相应的 HttpSession 对象。
通常服务器借助于 Cookie 把 SessionID 存储在浏览器进程中,在该浏览器进程下一次访问服务器时,服务器就可以从请求中的 Cookie 里获取 SessionID。此外,Session 还可以借助 URL 重写的方式在客户端保存 SessionID。
方法名 | 描述 |
---|---|
public void setAttribute(String name,Object value) | 将value对象以name名称绑定到会话 |
public object getAttribute(String name) | 获取指定name的属性值,如果属性不存在则返回null |
public void removeAttribute(String name) | 从会话中删除name属性,如果不存在不会执行,也不会抛处错误 |
public Enumeration getAttributeNames() | 返回和会话有关的枚举值 |
public void invalidate() | 使会话失效,同时删除属性对象 |
public Boolean isNew() | 用于检测当前客户是否为新的会话 |
public long getCreationTime() | 返回会话创建时间 |
public long getLastAccessedTime() | 返回在会话时间内web容器接收到客户最后发出的请求的时间 |
public int getMaxInactiveInterval() | 返回在会话期间内客户请求的最长时间.秒 |
public void setMasInactiveInterval(int seconds) | 允许客户客户请求的最长时间 |
ServletContext getServletContext() | 返回当前会话的上下文环境,ServletContext对象可以使Servlet与web容器进行通信 |
public String getId() | 返回会话期间的识别号 |
4.2.2 案例
login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="SessionServlet" method="get">
<div>
<label for="username">用户名: </label>
<input type="text" id="username" name="username" placeholder="请输入用户名">
</div>
<div>
<label for="password">密码: </label>
<input type="text" id="password" name="password" placeholder="请输入密码">
</div>
<div>
<input type="checkbox" name="saveCookie" value="yes">记住登录状态
</div>
<button type="submit">提交</button>
</form>
</body>
</html>
SessionServlet.java
package com.demo;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/SessionServlet")
public class SessionServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String[] saveCookie = request.getParameterValues("saveCookie");
HttpSession session = request.getSession();
if (saveCookie!=null && "yes".equals(saveCookie[0])) {
session.setAttribute("uname", username);
session.setAttribute("upwd", password);
} else {
session.removeAttribute("uname");
session.removeAttribute("upwd");
}
PrintWriter out = response.getWriter();
out.print("Welcome: " + username);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
LoginHtmlServlet.java
package com.demo;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/LoginHtmlServlet")
public class LoginHtmlServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
String username = "";
String password = "";
HttpSession session = request.getSession();
Object unameAttribute = session.getAttribute("uname");
Object upwdAttribute = session.getAttribute("upwd");
if ( (unameAttribute != null) && (upwdAttribute != null) ) {
username = "" + unameAttribute;
password = "" + upwdAttribute;
}
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>\r\n"
+ "<html>\r\n"
+ "<head>\r\n"
+ "<meta charset=\"UTF-8\">\r\n"
+ "<title>登录</title>\r\n"
+ "</head>\r\n"
+ "<body>\r\n"
+ " <form action=\"SessionServlet\" method=\"get\">\r\n"
+ " <div>\r\n"
+ " <label for=\"username\">用户名: </label>\r\n"
+ " <input type=\"text\" value=\"" + username + "\" id=\"username\" name=\"username\" placeholder=\"请输入用户名\">\r\n"
+ " </div>\r\n"
+ " <div>\r\n"
+ " <label for=\"password\">密码: </label>\r\n"
+ " <input type=\"text\" value=\"" + password + "\" id=\"password\" name=\"password\" placeholder=\"请输入密码\">\r\n"
+ " </div>\r\n"
+ " <div>\r\n"
+ " <input type=\"checkbox\" name=\"saveCookie\" value=\"yes\">记住登录状态\r\n"
+ " </div>\r\n"
+ " <button type=\"submit\">提交</button>\r\n"
+ " </form>\r\n"
+ "</body>\r\n"
+ "</html>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
4.3 URL 重写
有时,用户由于某些原因禁止了浏览器的 Cookie 功能,Servlet 规范中还引入了一种补充的会话管理机制,它允许不支持 Cookie 的浏览器也可以与 Web 服务器保持连续的会话。这种补充机制要求在需要加入同一会话的每个 URL 后附加一个特殊参数,其值为会话标识号(SessionID) 。
LoginHtmlServlet.java
package com.demo;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/LoginHtmlServlet")
public class LoginHtmlServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
String username = "";
String password = "";
HttpSession session = request.getSession();
Object unameAttribute = session.getAttribute("uname");
Object upwdAttribute = session.getAttribute("upwd");
if ( (unameAttribute != null) && (upwdAttribute != null) ) {
username = "" + unameAttribute;
password = "" + upwdAttribute;
}
System.out.println("session_id: " + session.getId());
System.out.println("url_ecode: " + response.encodeURL("SessionServlet"));
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>\r\n"
+ "<html>\r\n"
+ "<head>\r\n"
+ "<meta charset=\"UTF-8\">\r\n"
+ "<title>登录</title>\r\n"
+ "</head>\r\n"
+ "<body>\r\n"
+ " <form action=\"SessionServlet\" method=\"get\">\r\n"
+ " <div>\r\n"
+ " <label for=\"username\">用户名: </label>\r\n"
+ " <input type=\"text\" value=\"" + username + "\" id=\"username\" name=\"username\" placeholder=\"请输入用户名\">\r\n"
+ " </div>\r\n"
+ " <div>\r\n"
+ " <label for=\"password\">密码: </label>\r\n"
+ " <input type=\"text\" value=\"" + password + "\" id=\"password\" name=\"password\" placeholder=\"请输入密码\">\r\n"
+ " </div>\r\n"
+ " <div>\r\n"
+ " <input type=\"checkbox\" name=\"saveCookie\" value=\"yes\">记住登录状态\r\n"
+ " </div>\r\n"
+ " <button type=\"submit\">提交</button>\r\n"
+ " </form>\r\n"
+ "</body>\r\n"
+ "</html>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
SessionServlet.java
package com.demo;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/SessionServlet")
public class SessionServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String[] saveCookie = request.getParameterValues("saveCookie");
HttpSession session = request.getSession();
if (saveCookie!=null && "yes".equals(saveCookie[0])) {
session.setAttribute("uname", username);
session.setAttribute("upwd", password);
} else {
session.removeAttribute("uname");
session.removeAttribute("upwd");
}
PrintWriter out = response.getWriter();
out.print("Welcome: " + username);
out.print("<a href='" + response.encodeRedirectURL("TestServlet") + "'>跳转</a>");
// out.print("<a href='TestServlet'>跳转</a>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
TestServlet.java
package com.demo;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
HttpSession session = request.getSession();
out.print(session.getAttribute("uname"));
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
5. 补充
5.1 Servlet 中文乱码
5.1.1 乱码产生的原因
背景知识:HTTP 协议中规定,数据的传输采用字节编码(Unicode)方式,即无论浏览器所提交的数据中,包含的中文是什么字符编码格式,一旦由浏览器经过 HTTP 协议传输,那么这些数据均以字节的形式上传给服务器。因为 HTTP 协议的底层使用的是 TCP 传输协议。TCP(Transmission Control Protocol:传输控制协议)是一种面向连接的、可靠的、基于字节流的、端对端的通信协议。在请求中,这些字节均以 % 开头,并以十六进制形式出现。如%5A%3D等。
乱码产生的原因:当用户通过浏览器提交一个包含 UTF-8 编码格式的两个字的中文请求时,浏览器会将这两个中文字符变为六个字节(一般一个 UTF-8 汉字占用三个字节),即形成六个类似 %8E 的字节表示形式,并将这六个字节。上传至 Tomcat 服务器。
Tomcat 服务器在接收到这六个字节后,并不知道它们原始采用的是什么字符编码。而 Tomcat 默认的编码格式为 IS0-8859-1。所以会将这六个字节按照【IS0-8859-1】的格式进行编码,编码后在控制台显示,所以在控制台会显示乱码。
Servlet 乱码分为 request 乱码和 response 乱码。
通俗地说,不管是request乱码还是response乱码,其实都是由于客户端(浏览器)跟服务器端采用的编码格式不一致造成的。
5.1.2 request 乱码
requst 乱码:浏览器向服务器发送的请求参数中包含中文字符,服务器获取到的请求参数的值是乱码。
requst 请求分为 GET 请求和 POST 请求,GET 和 POST 请求是不一样的,当发送 GET 请求时,其传递给服务器的数据是附加在 URL 地址之后的;而发送 POST 请求时,其传递给服务器的数据是作为请求体的一部分传递给服务器。所以两者处理乱码的方式是不一样的。下面分别对 GET 请求和 PSOT 请乱码问题进行处理。
- 如果浏览器以 GET 方式发送请求,请求参数在请求头存放,在请求协议包到达服务端之后,请求头内容是由 Tomcat 负责解析,Tomcat8、9 在解析数据时,默认采用的字符集UTF-8,所以如果浏览器以GET方式发送中文参数,此时在服务端不会出现中文乱码问题。也就是说,Tomcat 8、9 版本中文乱码问题已解决,但是 Tomcat7 依然存在这个问题。
- 如果浏览器以 POST 方式发送请求,请求参数在请求体存放,在请求协议包到达服务端之后,请求体内容是由对应请求对象 request 负责解码的。request 对象默认使用 IS0-8859-1 字符集。所以如果浏览器以 POST 方式方式发送中文参数,此时在服务端必会出现中文乱码问题。解决方法是改变 HTTP 请求体中的字符编码(对于 GET 无效,因为 GET 提交的信息在请求头中)。
request.setCharacterEncoding("UTF-8");
5.1.3 reponse 乱码
response乱码:服务器向浏览器发送的数据包含中文字符,浏览器中显示的是乱码。
response 有一个缓冲区编码,默认值为ISO-8859-1。
对于 response 乱码,只需要在服务器端指定一个编码字符集,然后通知浏览器按照这个字符集进行解码就可以了。
- 对服务器的响应进行编码:
① response.setCharacterEncoding(“utf-8”);
② response.setContentType(“text/html;charset=utf-8”);
- 通知浏览器,服务器发送的数据格式(UTF-8)
① r****esponse.setHeader(“contentType”, “text/html;charset=utf-8”);
② response.setContentType(“text/html;charset=utf-8”);
问题:选择哪种结合方式?
①+①?
总结:
- 设置服务器端编码:response.setCharacterEncoding(“utf-8”);
- 通知浏览器,服务器发送的数据格式:response.setContentType(“text/html;charset=utf-8”);
注意:
response.setCharacterEncoding() 方法的使用前提是:之前必须要先使用 response.setContentType() 方法,response.setCharacterEncoding() 方法用于修改 ContentType 的 MIME 类型字符编码,如果之前使用response.setContentType() 设置了编码格式,则使用 response.setCharacterEncoding() 会覆盖之前的设置,二者结合使用方法如下:
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
最终解决方案:
response.setContentType("text/html;charset=utf-8”)
5.2 ServletConfig
ServletConfig 代表当前 Servlet 在 web.xml 中的配置信息(用的不多)。
在运行 Servlet 程序时,可能需要一些辅助信息,例如,文件使用的编码、使用 Servlet 程序的共享信息等,这些信息可以在 web.xml 文件中使用一个或多个 元素进行配置。
当 Tomcat 初始化一个 Servlet 时,会将该 Servlet 的配置信息封装到 ServletConfig 对象中(不能自己去创建 ServletConfig 对象),此时可以通过调用 init(ServletConfig config)方法将 ServletConfig 对象传递给 Servlet。进而,我们通过 ServletConfig 对象就可以得到当前 Servlet 的初始化参数信息。
这样做的好处是:如果将数据库信息、编码方式等配置信息放在web.xml中,如果以后数据库的用户名、密码改变了,则直接很方便地修改web.xml就行了,避免了直接修改源代码的麻烦。
ServletConfig 接口中定义了一系列获取配置信息的方法:
方法 | 说明 |
---|---|
String getInitParameter(String name) | 根据初始化参数名返回对应的初始化参数值 |
Enumeration getInitParameterNames() | 返回一个 Enumeration 对象,其中包含了所有的初始化参数名 |
ServletContext getServletContext() | 返回一个代表当前 Web 应用的 ServletContext 对象 |
String getServletName() | 返回 Servlet 的名字,即 web.xml 中 元素的值 |
web.xml
<servlet>
<servlet-name>MyServletConfigServlet</servlet-name>
<servlet-class>com.demo.MyServletConfigServlet</servlet-class>
<init-param>
<param-name>username</param-name>
<param-value>admin</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MyServletConfigServlet</servlet-name>
<url-pattern>/MyServletConfigServlet</url-pattern>
</servlet-mapping>
MyServletConfigServlet.java
package com.demo;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//@WebServlet("/MyServletConfigServlet")
public class MyServletConfigServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
public void init(ServletConfig config) throws ServletException {
String paramValue = config.getInitParameter("username");
System.out.println("username = " + paramValue);
String paramName = null;
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
paramName = paramNames.nextElement();
paramValue = config.getInitParameter(paramName);
System.out.println(paramName + " : " + paramValue);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
问题:如何通过 ServletConfig 设置编码方式?
5.3 ServletContext
5.3.1 简介
ServletContext 称为 Servlet 上下文,当 Tomcat 启动时,Tomcat 会为每个 Web 应用创建一个唯一的 ServletContext 对象,该对象代表当前的 Web 应用,并封装了当前 Web 应用的所有信息,这个对象全局唯一,而且该 Web 应用内部的所有 Servlet 都共享这个对象,所以叫全局应用程序共享对象。可以利用该对象获取 Web 应用程序的初始化信息、读取资源文件等。
5.3.2 ServletContext 生命周期
-
新 Servlet 容器启动的时候,服务器端会创建一个 ServletContext 对象。
-
在容器运行期间,ServletContext 对象一直存在。
-
当容器停止时,ServletContext 的生命周期结束。
6. 补充
6.1 案例 - Pserson 序列化
Person.java - 普通类
package com.demo;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public Person() {}
public Person(String name) {
super();
this.name = name;
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
}
PersonTest.java - 测试类
package com.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.junit.jupiter.api.Test;
class PersonTest {
@Test
void test() throws Exception {
File file = new File("person.out");
// 序列化
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
Person person = new Person("小明");
oout.writeObject(person);
oout.close();
// 反序列化
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object newPerson = oin.readObject();
oin.close();
System.out.println(newPerson);
}
}
测试发现没有什么问题。有一天,因业务发展,需要在 Person 中增加了一个属性 age,如下:
package com.demo;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
这时我们假设和之前序列化到磁盘的 Person 类是兼容的,便不修改版本标识 serialVersionUID。再次测试如下:
@Test
void deserialize1LWithAge() throws Exception {
File file = new File("person.out");
// 反序列化
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object newPerson = oin.readObject();
oin.close();
System.out.println(newPerson);
}
将以前序列化到磁盘的旧 Person 反序列化到新 Person 类时,没有任何问题。
可当我们增加 age 属性后,不作向后兼容,即放弃原来序列化到磁盘的 Person 类,这时我们可以将版本标识提高,如下:
private static final long serialVersionUID = 2L;
再次进行反序列化,则会报错,如下:
java.io.InvalidClassException: com.demo.Person; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
到这里,大致可以清楚,serialVersionUID 就是控制版本是否兼容的,若我们认为修改的 Person 是向后兼容的,则不修改 serialVersionUID;反之,则提高 serialVersionUID 的值。再回到一开始的问题,为什么 Eclipse 会提示声明 serialVersionUID 的值呢?
因为若不显式定义 serialVersionUID 的值,Java 会根据类的细节自动生成 serialVersionUID 的值,如果对类的源代码作了修改,再重新编译,新生成的类文件的 serialVersionUID 的取值有可能也会发生变化。
类的 serialVersionUID 的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的 Java 编译器编译,也有可能会导致不同的 serialVersionUID。所以 Eclipse 才会提示声明 serialVersionUID 的值。
三、JSP
1. JSP 概述
1.1 JSP 简介
-
是 Servlet 的扩展
-
是一种基于 Java 的 服务器端技术
-
目的是简化建立和管理动态网站的工作
1.2 JSP 特别点
-
简单快捷
-
动态内容的生成和显示分离
-
组件重用
-
易于部署、升级和维护
1.3 JSP 与 Servlet 的比较
示例:
hello-jsp.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello JSP</title>
</head>
<body>
<%
out.print("Hello JSP!");
%>
</body>
</html>
- 指令:<%@ directive attribute=“value” %>
指令 | 描述 |
---|---|
<%page … %> | 定义页面的依赖属性,比如脚本语言、error页面、缓存需求等等 |
<%@ include … %> | 包含其他文件 |
<%@ taglib … %> | 引入标签库的定义,可以是自定义标签 |
- 语法:<% 代码片段 %>
1.4 执行原理
- 首次被请求
- 再次被请求
翻译后的 Java 源码和编译后的字节码文件,都在 Tomcat 目录下的 work 子目录中。
1.5 生命周期
理解 JSP 底层功能的关键就是去理解它们所遵守的生命周期。
JSP 生命周期就是从创建到销毁的整个过程,类似于 Servlet 生命周期,区别在于 JSP 生命周期还包括将 JSP 文件编译成 Servlet。
-
编译阶段:Servlet 容器编译 Servlet 源文件,生成 Servlet 类。
-
初始化阶段:加载与 JSP 对应的 Servlet 类,创建其实例,并调用它的初始化方法。
-
执行阶段:调用与 JSP 对应的 Servlet 实例的服务方法。
-
销毁阶段:调用与 JSP 对应的 Servlet 实例的销毁方法,然后销毁 Servlet 实例。
1.6 JSP 编译
当浏览器请求 JSP 页面时,JSP 引擎会首先去检查是否需要编译这个文件。如果这个文件没有被编译过,或者在上次编译后被更改过,则编译这个 JSP 文件。
编译的过程包括三个步骤:
-
解析 JSP 文件。
-
将 JSP 文件转为 Servlet。
-
编译 Servlet。
life-cycle.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP LifeCycle</title>
</head>
<body>
<%!
int initVar = 0;
int serviceVar = 0;
int destroyVar = 0;
%>
<%!
public void jspInit(){
initVar++;
System.out.println("jspInit(): JSP被初始化了" + initVar + "次");
}
public void jspDestroy(){
destroyVar++;
System.out.println("jspDestroy(): JSP被销毁了" + destroyVar + "次");
}
%>
<%
serviceVar++;
System.out.println("_jspService(): JSP共响应了" + serviceVar + "次请求");
String content1 = "初始化次数 : " + initVar;
String content2 = "响应客户请求次数 : " + serviceVar;
String content3 = "销毁次数 : " + destroyVar;
%>
<p><%=content1 %></p>
<p><%=content2 %></p>
<p><%=content3 %></p>
</body>
</html>
问题:contentType 和 pageEncoding 都有对字符集的设置,两者有什么区别?
2. JSP 指令和动作
2.1 JSP 指令
JSP 指令用来向 JSP 引擎(Tomcat)提供编译信息。
-
page 指令
-
include 指令
-
taglib 指令
2.1.1 page 指令
- 用于设置页面的各种属性,如导入包、指明输出内容类型、控制Session等。
- 一般位于JSP 页面的开头部分,一个 JSP 页面可包含多条 page 指令。
属性名 | 说明 |
---|---|
language | 设定JSP页面使用的脚本语言。默认为java,目前只可使用java语言 |
extends | 此JSP页面生成的Servlet的父类 |
import | 指定导入的java软件包或类名列表。如果多个类时,中间用逗号隔开 |
session | 设定JSP页面是否使用Session对象。值为“true|false”,默认为true |
buffer | 设定输出流是否有缓冲区。默认为8KB,值为“none | sizekb” |
autoFlush | 设定输出流的缓冲区是否要自动清除。值为“true | false”,默认值为true |
isThreadSafe | 设定JSP页面生成的Servlet是否实现SingleThreadModel接口。值为“true | false”,默认为true |
info | 主要表示此JSP网页的相关信息 |
errorPage | 设定JSP页面发生异常时重新指向的页面URL |
isErrorPage | 指定此JSP页面是否为处理异常错误的网页。值为“true | false”,默认为false |
contentType | 指定MIME类型和JSP页面的编码方式 |
pageEncoding | 指定JSP页面的编码方式 |
isELlgnored | 指定JSP页面是否忽略EL表达式。值为“true | false”,默认值为false |
2.1.1.1 import 属性
- import属性可以在当前JSP 页面中引入 JSP 脚本代码需要用到的其他类。
- 如果需要引入多个类或包,可以在中间使用逗号隔开或使用多个page 指令。
page-instruction.jsp
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>page 指令</title>
</head>
<body>
<p>现在时间:<%=new Date() %></p>
</body>
</html>
对日期进行格式化
<%@page import="java.text.SimpleDateFormat"%>
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>page 指令</title>
</head>
<body>
<%
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
%>
<p>现在时间:<%=sdf.format(new Date()) %></p>
</body>
</html>
注意:import 是 page 指令中唯一一个可以在同一个 JSP 页面中多次出现的属性。
2.1.1.2 contentTyp 属性
-
contentType 用于指定 JSP 输出内容的MIME 类型和字符集。MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
-
MIME 类型通常由两部分组成:
-
MIME类型
-
MIME 子类型
- 通过设置contentType 属性,可以改变JSP 输出的 MIME 类型,从而实现一些特殊的功能。
page-excel.jsp
<%@ page language="java" contentType="application/vnd.ms-excel; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP 生成 Excel 表格</title>
</head>
<body>
<table border="1" cellspacing="0" cellpadding="3">
<thead>
<tr>
<th>姓名</th>
<th>兵器</th>
</tr>
</thead>
<tbody>
<tr>
<td>唐僧</td>
<td>九环锡杖</td>
</tr>
<tr>
<td>孙悟空</td>
<td>金箍棒</td>
</tr>
<tr>
<td>猪八戒</td>
<td>九齿钉耙</td>
</tr>
<tr>
<td>沙僧</td>
<td>降妖宝杖</td>
</tr>
</tbody>
</table>
</body>
</html>
page-word.jsp
<%@ page language="java" contentType="application/msword; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP 生成 Excel 表格</title>
</head>
<body>
<table border="1" cellspacing="0" cellpadding="3">
<thead>
<tr>
<th>姓名</th>
<th>兵器</th>
</tr>
</thead>
<tbody>
<tr>
<td>唐僧</td>
<td>九环锡杖</td>
</tr>
<tr>
<td>孙悟空</td>
<td>金箍棒</td>
</tr>
<tr>
<td>猪八戒</td>
<td>九齿钉耙</td>
</tr>
<tr>
<td>沙僧</td>
<td>降妖宝杖</td>
</tr>
</tbody>
</table>
</body>
</html>
2.1.2 include 指令
- include 指令用于在当前 JSP 中包含其他文件,被包含的文件可以是 JSP、HTML 或文本文件。
- 包含的过程发生在将 JSP 翻译成 Servlet 时,当前 JSP 和被包含的 JSP 会融合到一起,形成一个 Servlet,然后进行编译并运行。
include-index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<%@ include file="include-header.jsp" %>
<h1>Hello JSP!</h1>
<%@ include file="include-footer.jsp" %>
</body>
</html>
include-header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>头部</title>
</head>
<body>
<h1>Hello Header</h1>
</body>
</html>
include-footer.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>版权</title>
</head>
<body>
<h1>Hello Footer</h1>
</body>
</html>
2.2 JSP 标准动作
2.2.1 JavaBean 简介
JavaBean实际上就是一种满足特定要求的Java类:
-
是一个公有类,含有公有的无参构造方法;
-
属性私有;
-
属性具有公有的get和set方法。
2.2.2 JSP 动作
-
在 JSP 中可以使用 XML 语法格式的一些特殊标记来控制行为,称为 JSP 标准动作(Standard Action)。
-
JSP规范定义了一系列标准动作,常用有下列几种:
-
jsp:useBean:查找或者实例化一个 JavaBean;
-
jsp:setProperty:设置 JavaBean 的属性;
-
jsp:getProperty:输出某个 JavaBean 的属性;
-
jsp:include:在页面被请求时引入一个文件;
-
jsp:forward:把请求转发到另一个页面。
2.2.2.1 jsp:useBean
useBean标准动作用来查找或者实例化一个JavaBean。
<jsp:useBean id="name" class="className" scope="scope" />
<jsp:useBean id="name" type="className" scope="scope" />
-
id:指定该 JavaBean 实例的变量名,通过 id 可以访问这个实例。
-
class:指定 JavaBean 的类名。如果需要创建一个新的实例,容器会使用 class 指定的类并调用无参构造方法来完成实例化。
-
scope 指定 JavaBean 的作用范围。可以使用四个值:page、request、session 和 application。
-
page(缺省值):表明此 JavaBean 只能应用于当前页;
-
request:表明此 JavaBean 只能应用于当前的请求;
-
session:表明此 JavaBean 能应用于当前会话;
-
application:则表明此 JavaBean 能应用于整个应用程序内。
2.2.2.2 jsp:setProperty
setProperty标准动作用于设置JavaBean中的属性值。
<jsp:setProperty name="id" property="属性名" value="值"/>
<jsp:setProperty name="id" property="属性名" param="参数名"/>
-
name:指定JavaBean对象名,与useBean标准动作中的id相对应;
-
property:指定JavaBean中需要赋值的属性名;
-
value:指定要为属性设置的值;
-
param:指定请求中的参数名(该参数可以来自表单、URL传参数等),并将该参数的值赋给property所指定的属性。
2.2.2.3 jsp:getProperty
getProperty标准动作用于访问一个bean的属性并将其输出。访问所得到的值将转换成String类型。
<jsp:getProperty name="id" property="属性名"/>
- name:指定JavaBean对象名,与useBean标准动作中的id相对应;
- property:指定JavaBean中需要访问的属性名。
User.java
package cn.cyber.entity;
public class User {
public User() {
super();
System.out.println("Constructor()...");
}
public User(String username, String password) {
super();
this.username = username;
this.password = password;
}
private String username;
private String password;
public String getUsername() {
System.out.println("getUsername()...");
return username;
}
public void setUsername(String username) {
System.out.println("setUsername()...");
this.username = username;
}
public String getPassword() {
System.out.println("getPassword()...");
return password;
}
public void setPassword(String password) {
System.out.println("setPassword()...");
this.password = password;
}
}
login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="login.jsp" method="post">
<div>
<label for="uname">Username: </label>
<input type="text" id="uname" name="user_name">
</div>
<div>
<label for="upwd">Password: </label>
<input type="password" id="upwd" name="pass_word">
</div>
<input type="submit" value="Submit">
</form>
</body>
</html>
login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 实例化一个 JavaBean(Java 类) -->
<!--
User user = new User();
-->
<jsp:useBean id="user" class="cn.cyber.entity.User" scope="request"></jsp:useBean>
<!-- 对实例化后的 JavaBean 进行初始化,即对属性进行赋值 -->
<!--
user.setUsername(request.getParamenter("user_name"));
user.setPassword(request.getParamenter("pass_word"));
-->
<jsp:setProperty name="user" property="username" param="user_name"/>
<jsp:setProperty name="user" property="password" param="pass_word"/>
<!-- 展示已经实例化的 JavaBean 的属性值 -->
<!--
user.getUsername("username");
user.getPassword("password");
-->
<p>Username: <jsp:getProperty name="user" property="username"/></p>
<p>Password: <jsp:getProperty name="user" property="password"/></p>
</body>
</html>
2.2.2.4 jsp:include
- include 标准动作用于在 JSP 页面动态包含其他页面。
- 该动作的功能与 JSP 的 include 指令类似,区别是 include 指令在编译时完成包含,是静态包含(一共只有一个文件);而 include 标准动作是在运行时完成包含,是动态包含(有 N 个 JSP 文件,则有 N 个 Java 文件,同时,有 N 个 class 文件)。
语法如下:
<jsp:include page="被包含文件的URL" />
如果需要额外的请求参数,则需要使用 jsp:param:
<jsp:include page="被包含文件的URL">
<jsp:param name="参数名" value="参数值" />
</jsp:include>
对之前的 include-index.jsp 进行改造:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<!--
<%@ include file="include-header.jsp" %>
-->
<jsp:include page="include-header.jsp"></jsp:include>
<h1>Hello JSP!</h1>
<jsp:include page="include-footer.jsp"></jsp:include>
<!--
<%@ include file="include-footer.jsp" %>
-->
</body>
</html>
查看 Tomcat 的 work 子目录
2.2.2.5 jsp:forward
forward 标准动作用于将用户的请求转发到另一个 HTML 文件、JSP 页面或 Servlet,语法格式如下:
<jsp:forward page="URL地址" />
如果需要额外的请求参数,则需要使用 jsp:param:
<jsp:forward page="被包含文件的URL">
<jsp:param name="参数名" value="参数值" />
</jsp:forward>
注意:forward 动作与 RequestDispatcher 类型对象的 forward 方法类似,调用者和被调用者共享同一个 request 对象。
2.2.2.6 jsp:param
param 标准动作用于为其他动作标签提供附加参数值信息,该动作可以与 jsp:include、jsp:forward 等一起使用,其语法格式如下:
<jsp:param name="参数名" value="参数值" />
例如:
<jsp:include page="show.jsp">
<jsp:param name="username" value="admin" />
</jsp:include>
上述代码中,在 include 标准动作中使用 jsp:param 设置了参数,这样在 show.jsp 页面中就可以用 request 对象的 getParameter() 方法取得这两个参数。
测试
本题目共涉及4个文件:
1.login.html:登录页面,提交用户名和密码给 login.jsp。
2.login.jsp:实例化 JavaBean 并进行初始化,然后使用 JSP 标准动作转发到 index.jsp。
3.index.jsp:使用 JavaBean,显示用户名和密码;使用 JSP 标准动作包含 header.jsp 页面。
4.header.jsp:打印“Hello XXX”(XXX 为用户名)。
3. JSP 内置对象
3.1 内置对象概述
JSP 内置对象:由 JSP 容器(Tomcat)加载,不需要声明就可以直接在 JSP 页面中使用的对象。
JSP 九大内置对象
名称 | 说明 |
---|---|
request | 客户端的请求,包含所有从浏览器发往服务器的请求信息 |
response | 返回客户端的响应 |
session | 会话对象,表示用户的会话状态 |
application | 应用上下文对象,作用于整个应用程序 |
out | 输出流,向客户端输出数据 |
pageContext | 用于存储当前 JSP 页面的相关信息 |
config | JSP 页面的配置信息对象 |
page | 表示 JSP 页面的当前实例 |
exception | 异常对象,用于处理 JSP 页面中的错误 |
3.2 常用内置对象
3.2.1 out 对象
out 对象是一个输出流,用于将信息输出到网页中。常用得方法:
-
print()
-
println()
-
writer()
三者都是输出信息,有区别么?
out.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>out 对象</title>
</head>
<body>
<%
out.print("Hello Print");
out.println("Hello Println");
out.write("Hello Write");
%>
</body>
</html>
-
println() 虽然看似是换行,但转成网页之后,这种换行被认为是空格,所以输出的内容仍然在同一行,用空格分隔。
-
print() 和 println() 是 JspWriter 类中定义的方法,write() 则是 Writer 类中定义的。
-
print() 和 println() 方法可将各种类型的数据转换成字符串的形式输出,而 write() 方法只能输出字符、字符数组和字符串等与字符相关的数据。
-
如果字符串对象的值为 null,print() 和 println() 方法将输出内容为“null”的字符串,而 write() 方法则是抛出 NullPointerException 异常。
Student.java
package cn.cyber.entity;
public class Student {
private String stuNo;
private int age;
public Student() {
super();
// TODO Auto-generated constructor stub
}
public Student(String stuNo, int age) {
super();
this.stuNo = stuNo;
this.age = age;
System.out.println("Student Constructor...");
}
public String getStuNo() {
return stuNo;
}
public void setStuNo(String stuNo) {
this.stuNo = stuNo;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [stuNo=" + stuNo + ", age=" + age + "]";
}
}
out.jsp
<%@page import="cn.cyber.entity.Student"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>out 对象</title>
</head>
<body>
<%
out.print("Hello Print");
out.println("Hello Println");
out.write("Hello Write");
Student student = new Student("111", 20);
out.print(student);
out.println(student);
// out.write(student);
String text = null;
out.print(text);
out.println(text);
try{
out.write(text);
} catch(Exception e){
e.printStackTrace();
out.print(e.getMessage());
out.print("text=null");
}
%>
</body>
</html>
除了用于输出数据的上述三种方法外,out 对象中还拥有其他常用方法:
-
clear():清除缓冲区的内容,如果缓冲区已经被刷出(flush),将抛出 IOException。
-
clearBuffer():清除缓冲区的当前内容,和 clear() 方法不同,即使缓冲区已经 flush,也不会发生异常。
-
flush():输出缓冲区中的内容。
-
close():关闭输出流,清除所有内容。
3.3.2 request 对象
request 对象是r HttpServletRequest 接口实现类的实例。包含所有从浏览器发往服务器的请求信息,例如请求的来源、Cookie 和客户端请求相关的数据。
request 对象中最常用得方法如下:
-
String getParameter(String name):根据参数名称得到单一参数值;
-
String[] getParameterValues(String name):根据参数名称得到一组参数值;
-
void setAttribute(String name, Object value):以名/值的方式存储数据;
-
Object getAttribute(String name):根据名称得到存储的数据。
注意:
-
对于复选框 checkbox,需要先判断其数组是否为 null,再进行遍历,因为如果用户没有选择任何选项,则 getParameterValues() 返回 null。
-
对于普通文本框 text,当没有任何输入信息时,浏览器仍然提交该表单元素信息,这时再服务器端通过 getParameter() 获取表单值时,结果为空字符串""。
-
对于单选框 radio 或复选框 checkbox,当没有任何选择时,浏览器不会提交该表单元素信息,因此再服务器端获取该表单元素信息时为 null。
-
并不是每个文本框都会生成请求参数,而是有 name 属性得文本框才生成请求参数,即每个有 name 属性得文本框对应一个请求参数。
-
如果文本框配置了 disabled="disabled"属性,则相应表单提交数据时不会提交该文本框信息。
-
除文本框外,其他表单元素亦如此。
3.3.3 response 对象
response 对象是 HttpServletResponse 接口实现类的实例,负责将响应结果发送到浏览器端。
response 常用的方法如下:
- void setContentType(String name):设置响应内容的类型和字符编码;
- void sendRedirect(String url):重定向到指定的 URL 资源。
3.3.4 session 对象
session 对象是 HttpSession 接口实现类的实例,表示用户的会话状态。
session 常用的方法如下:
- void setAttribute(String name, Object value):以名/值的方式存储数据;
- Object getAttribute(String name):根据名称得到存储的数据。
3.3.5 application 对象
application 对象是 ServletContext 接口实现类的实例,其作用于整个应用程序,由应用程序中的所有 Servlet 和 JSP 页面共享。
application 常用的方法如下:
- void setAttribute(String name, Object value):以名/值的方式存储数据;
- Object getAttribute(String name):根据名称得到存储的数据。
3.3.6 page 对象
page 对象表示 JSP 页面的当前实例,实际上相当于 this,可以提供对 JSP 页面上定义的所有对象的访问。实际开发中很少使用 page 对象。
3.3.7 pageContext 对象
pageContext 对象可以访问当前 JSP 页面所有的内置对象,如 request、response、session、application、out等。pageContext 对象还提供存取数据的方法,作用范围为当前 JSP 页面。
pageContext 常用方法如下:
- void setAttribute(String name, Object value):以名/值方式存储数据;
- Object getAttribute(String name):根据名称得到存储的数据。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
if(pageContext.getAttribute("pageCount") == null){
pageContext.setAttribute("pageCount", 0);
}
if(request.getAttribute("requestCount") == null){
request.setAttribute("requestCount", 0);
}
if(session.getAttribute("sessionCount") == null){
session.setAttribute("sessionCount", 0);
}
if(application.getAttribute("applicationCount") == null){
application.setAttribute("applicationCount", 0);
}
%>
<%
// page 计数
int pageCount = Integer.parseInt(pageContext.getAttribute("pageCount").toString());
pageCount++;
pageContext.setAttribute("pageCount", pageCount);
// request 计数
int requestCount = Integer.parseInt(request.getAttribute("requestCount").toString());
requestCount++;
request.setAttribute("requestCount", requestCount);
// session 计数
int sessionCount = Integer.parseInt(session.getAttribute("sessionCount").toString());
sessionCount++;
session.setAttribute("sessionCount", sessionCount);
// application 计数
int applicationCount = Integer.parseInt(application.getAttribute("applicationCount").toString());
applicationCount++;
application.setAttribute("applicationCount", applicationCount);
%>
<p>page 计数:<%=pageContext.getAttribute("pageCount") %></p>
<p>request 计数:<%=request.getAttribute("requestCount") %></p>
<p>session 计数:<%=session.getAttribute("sessionCount") %></p>
<p>application 计数:<%=application.getAttribute("applicationCount") %></p>
</body>
</html>
JSP 的四大作用域:pageContext、request、session、application。
- 这四个对象都是通过 setAttribute(String name, Object value) 方法来保存数据的,通过 getAttribute(String name) 方法获得数据的。
- 开发中要根据实际情况选择合适的范围,尽量使用小的范围,因为越大的范围其生命周期越长,相应的就会占用更多的服务器资源。
3.3.8 config 对象
config 对象用来存放 Servlet 的一些初始化信息,常用方法如下:
-
String getInitParameter(String name):返回指定名称的初始参数值。
-
Enumeration getInitParameterNames():返回所有初始参数的名称集合。
-
ServletContext getServletContext():返回 Servlet 上下文。
-
String getServletName():返回 Servlet 的名称。
3.3.9 exception 对象
exception 对象表示 JSP 页面中的异常信息。需要注意的是,要使用 exception 对象,必须将此 JSP 中 page 指令的 isErrorPage 属性值设置为 true。
四、JDBC
本节将通过一个完整的微型系统来展示 JDBC 的使用,同时也会用到 Servlet 、 JSP 以及 MySQL 的部分知识,系统的功能包括:
-
用户注册
-
用户登录
-
用户信息展示
1. JDBC 简介
百度百科中对 JDBC 的定义如下:
Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
JDBC API 允许用户访问任何形式的表格数据,尤其是存储在关系数据库中的数据。
2. JDBC 的使用
2.1 下载驱动
- 要想使用 JDBC,首先需要下载 JDBC 驱动(jar包),链接为:
https://dev.mysql.com/downloads/connector/j/
选择“Platform Independent”
- 不用在意版本,选择“ZIP”下载即可。
- 可以选择登录,抑或是直接下载“No thanks, just start my download.”
- 打开下载的压缩包,将“mysql-connector-java-8.x.xx.jar”文件解压缩,留作备用。
2.2 创建项目
2.2.1 新建项目
创建一个项目名称为“HelloJDBC”的 Dynamic Web Project,
2.2.2 拷贝驱动
将之前解压缩的 jar 包 mysql-connector-java-8.0.25.jar 拷贝到项目中,详细路径如下:
(1)新版 Eclipse:项目/src/main/webapp/WEB-INF/lib。
(2)老版 Eclipse:项目/WebContent/WEB-INF/lib。
少一张截图
**注意:**Eclipse 会自动加载该路径下的 jar包。
2.3 创建数据库
2.3.1 新建数据库
在 MySQL 中新建与项目同名的数据库“hellojdbc”。
2.3.2 新建表
新建 user 表。
create table user
(
id int,
username varchar(20) null,
password varchar(20) null
)
comment '用户表';
create unique index user_id_uindex
on user (id);
alter table user
add constraint user_pk
primary key (id);
alter table user modify id int auto_increment;
2.4 创建实体类
根据 user 表,创建 User 实体类。
package cn.cyber.entity;
public class User {
private Integer id;
private String username;
private String password;
public User(String username, String password) {
super();
this.username = username;
this.password = password;
}
public User() {
super();
// TODO Auto-generated constructor stub
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2.5 创建 DBUtil
使用 JDBC 连接数据库,最重要的是要记住步骤(套路):
-
准备连接数据库的资源
-
加载驱动
-
获取连接
-
准备 SQL 语句
-
创建 Statement 对象
-
执行 SQL 语句,保存结果集 ResultSet
-
遍历结果集,输出结果
-
释放资源,关闭连接
万变不离其宗,只要记住了套路,CRUD 轻松拿下!
package cn.cyber.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DBUtil {
public static void main(String[] args) {
/**
* 1.准备连接数据库的资源
*/
// 用户名
String username = "root";
// 密码
String password = "root";
// 数据库地址
String url = "jdbc:mysql://localhost:3306/hellojdbc";
// 驱动名称
String driver = "com.mysql.cj.jdbc.Driver"; // 新版本驱动
// String driver = "com.mysql.jdbc.Driver"; // 老版本驱动
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 2.加载驱动
Class.forName(driver);
// 3.获取连接
conn = DriverManager.getConnection(url, username, password);
// 4.准备 SQL 语句
String sql = "select * from user";
// 5.创建 Statement 对象
st = conn.createStatement();
// 6.执行 SQL 语句,保存结果集 ResultSet
rs = st.executeQuery(sql);
// 7.遍历结果集,输出结果
while (rs.next()) {
System.out.println("ID= " + rs.getInt("id"));
System.out.println("Username= " + rs.getString("username"));
System.out.println("Password= " + rs.getString("password"));
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
// 8.释放资源,关闭连接
if (rs != null) {
rs.close();
}
if (st != null) {
st.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
2.6 运行测试
在数据库中输入测试数据:
运行 DBUtil,得到输出结果:
2.7 升级改造
2.7.1 在原 DBUtil 上进行升级
package cn.cyber.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtilTmp {
public static void main(String[] args) {
/**
* 1.准备连接数据库的资源
*/
// 用户名
String username = "root";
// 密码
String password = "root";
// 数据库地址
String url = "jdbc:mysql://localhost:3306/hellojdbc";
// 驱动名称
String driver = "com.mysql.cj.jdbc.Driver"; // 新版本驱动
// String driver = "com.mysql.jdbc.Driver"; // 老版本驱动
Connection conn = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
// 2.加载驱动
Class.forName(driver);
// 3.获取连接
conn = DriverManager.getConnection(url, username, password);
// 4.准备 SQL 语句
String sql = "select * from user";
// 5.创建 PreparedStatement 对象
pst = conn.prepareStatement(sql);
// 6.执行 SQL 语句,保存结果集 ResultSet
rs = pst.executeQuery(sql);
// 7.遍历结果集,输出结果
while (rs.next()) {
System.out.println("ID= " + rs.getInt("id"));
System.out.println("Username= " + rs.getString("username"));
System.out.println("Password= " + rs.getString("password"));
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
// 8.释放资源,关闭连接
if (rs != null) {
rs.close();
}
if (pst != null) {
pst.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
2.7.2 创建真正的 DBUtil
- 把 DBUitl 重命名为 DBUtilTmp。
- 新建 DBUtil。
package cn.cyber.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtil {
private static String username = "root";
private static String password = "root";
private static String url = "jdbc:mysql://localhost:3306/hellojdbc";
private static String driver = "com.mysql.cj.jdbc.Driver";
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
public static void release(Connection conn, PreparedStatement pst, ResultSet rs) throws SQLException {
if (rs != null) {
rs.close();
}
if (pst != null) {
pst.close();
}
if (conn != null) {
conn.close();
}
}
}
2.8 运行测试
package cn.cyber.test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import cn.cyber.util.DBUtil;
public class DBUtilTest {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
String sql = "select * from user";
pst = conn.prepareStatement(sql);
rs = pst.executeQuery(sql);
while (rs.next()) {
System.out.println("ID= " + rs.getInt("id"));
System.out.println("Username= " + rs.getString("username"));
System.out.println("Password= " + rs.getString("password"));
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
DBUtil.release(conn, pst, rs);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3. 实现登录功能
3.1 前端页面
login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="UserLoginController" method="post">
<div>
<label for="uname">Username: </label>
<input type="text" id="uname" name="user_name">
</div>
<div>
<label for="upwd">Password: </label>
<input type="password" id="upwd" name="pass_word">
</div>
<input type="submit" value="Submit">
</form>
</body>
</html>
3.2 DAO 层
UserDAO.java
package cn.cyber.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import cn.cyber.entity.User;
import cn.cyber.util.DBUtil;
public class UserDao {
// 增
// 删
// 改
// 查
/**
* 查询所有用户
* - 访问修饰符:public
* - 返回值类型:List<User>
* - 方法名称:getUsers
* - 方法参数:空
*/
public List<User> getUsers() {
Connection conn = null;
PreparedStatement pst = null;
ResultSet rs = null;
List<User> users = new ArrayList<User>();
try {
// 获取连接
conn = DBUtil.getConnection();
// 准备 SQL 语句
String sql = "select * from user";
// 创建 PreparedStatement 对象
pst = conn.prepareStatement(sql);
// 执行 SQL 语句
rs = pst.executeQuery();
// 遍历 ResultSet,封装对象
while (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
users.addUser(user);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
DBUtil.release(conn, pst, rs);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return users;
}
}
3.3 Service 层
UserService.java
package cn.cyber.service;
import java.util.List;
import cn.cyber.dao.UserDao;
import cn.cyber.entity.User;
public class UserService {
private UserDao userDao = new UserDao();
// 查询所有用户
public List<User> getUsers() {
return userDao.getUsers();
}
}
3.4 Controller 层
UserLoginController.java
package cn.cyber.controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.cyber.entity.User;
import cn.cyber.service.UserService;
@WebServlet("/UserLoginController")
public class UserLoginController extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
String name = request.getParameter("user_name");
String password = request.getParameter("pass_word");
UserService userService = new UserService();
List<User> users = userService.getUsers();
String url = "fail.html";
for (User user : users) {
if (name.equals(user.getUsername())) {
if (password.equals(user.getPassword())) {
url = "success.html";
break;
}
}
}
request.getRequestDispatcher(url).forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
4. 实现注册功能
4.1 前端页面
register.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<form action="UserRegisterController" method="post">
<div>
<label for="uname">Username: </label>
<input type="text" id="uname" name="user_name">
</div>
<div>
<label for="upwd">Password: </label>
<input type="password" id="upwd" name="pass_word">
</div>
<input type="submit" value="Submit">
</form>
</body>
</html>
4.2 DAO 层
UserDao.java
package cn.cyber.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import cn.cyber.entity.User;
import cn.cyber.util.DBUtil;
public class UserDao {
private Connection conn = null;
private PreparedStatement pst = null;
private ResultSet rs = null;
// 增
public int addUser(User user) {
int result = 0;
try {
// 获取连接
conn = DBUtil.getConnection();
// 准备 SQL 语句,“?”为占位符,表示此时该值尚不能确定
String sql = "insert into user(username,password) values(?,?)";
// 创建 PreparedStatement 对象
pst = conn.prepareStatement(sql);
// 在执行 SQL 语句前,需要为 SQL 语句中的占位符赋值
pst.setString(1, user.getUsername());
pst.setString(2, user.getPassword());
// 执行 SQL 语句
result = pst.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
DBUtil.release(conn, pst, rs);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return result;
}
// 删
// 改
// 查
/**
* 查询所有用户
* - 访问修饰符:public
* - 返回值类型:List<User>
* - 方法名称:getUsers
* - 方法参数:空
*/
public List<User> getUsers() {
List<User> users = new ArrayList<User>();
try {
// 获取连接
conn = DBUtil.getConnection();
// 准备 SQL 语句
String sql = "select * from user";
// 创建 PreparedStatement 对象
pst = conn.prepareStatement(sql);
// 执行 SQL 语句
rs = pst.executeQuery();
// 遍历 ResultSet,封装对象
while (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
users.add(user);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
DBUtil.release(conn, pst, rs);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return users;
}
}
4.3 Service 层
UserService.java
package cn.cyber.service;
import java.util.List;
import cn.cyber.dao.UserDao;
import cn.cyber.entity.User;
public class UserService {
private UserDao userDao = new UserDao();
// 添加用户
public int addUser(User user) {
return userDao.addUser(user);
}
// 查询所有用户
public List<User> getUsers() {
return userDao.getUsers();
}
}
4.4 Controller 层
UserRegisterController.java
package cn.cyber.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.cyber.entity.User;
import cn.cyber.service.UserService;
@WebServlet("/UserRegisterController")
public class UserRegisterController extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
String username = request.getParameter("user_name");
String password = request.getParameter("pass_word");
User user = new User(username, password);
UserService userService = new UserService();
int result = userService.register(user);
String url = "fail.html";
if (result > 0) {
url = "success.html";
}
request.getRequestDispatcher(url).forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
五、EL 和 JSTL
1 EL
随着 JSP 技术的广泛应用,一些问题也随之而来:JSP 主要用于内容的显示,如果嵌入大量的 Java 代码以完成复杂的功能,会使得 JSP 页面难以维护,虽然可以将尽可能多的 Java 代码放到 Servlet 或者 JavaBean 中,但是对于 JavaBean 的操作及集合对象的遍历访问等还是不可避免地会用到 Java 代码。
为了简化 JSP 页面中对象的访问方式,JSP2.0 引入了一种简捷的语言——表达式语言(Expression Language,EL)。
EL 表达式可以使 JSP 写起来更加简单,它基于可用的命名空间,嵌套属性和对集合、操作符的访问符,映射到 Java 类中静态方法的可扩展函数以及一组隐式对象。
在使用 EL 从 scope 中得到参数时可以自动转换类型,因此对于类型的限制更加宽松。Web 服务器对于 request 请求参数通常以字符串类型来发送,在获取时需要使用 Java API 类操作,而且还需要进行强制类型转换,而 EL 可以避免这些类型转换工作,允许用户直接使用 EL 表达式获取值,而不必关心数据类型。
1.1 EL 基础语法
${EL表达式}
${person.name}
上述 EL 表达式由两部分组组成:
- “.”操作符左边是一个 JavaBean 对象或 EL 隐含对象。
- “.”操作符右边是一个 JavaBean 属性名或映射键。
与其等价的是:
${person["name"]}
上述 EL 表达式中使用了“[]”操作符,与“.”相比,“[]”操作符更加灵活。
- “.”操作符要求左边是 JavaBean 对象或 EL 隐含对象,不能是数组或集合元素。
- “[]”可以是 JavaBean 对象或 EL 隐含对象或数组或集合元素。
例如访问数组 a 中的第一个元素:
${a[0]}
“[]”中可以是属性、映射键或索引下标,并使用双括号括起来。
注意:大部分情况下,使用“.”的 方式更加简捷方便,但当要访问的内容只有在运行时才能决定时,就只能使用“[]”的方式,因为“.”后面只能是字面值,而“[]”中的内容可以是一个变量。
1.2 EL 隐含对象
类别 | 对象 | 描述 |
---|---|---|
JSP | pageContext | 引用当前JSP页面的pageContext内置对象 |
作用域 | pageScope | 获得页面作用范围中的属性值,相当于pageContext.getAttribute() |
requestScope | 获得请求作用范围中的属性值,相当于request.getAttribute() | |
sessionScope | 获得会话作用范围中的属性值,相当于session.getAttribute() | |
applicationScpe | 获得应用程序作用范围中的属性值,相当于application.getAttribute() | |
请求参数 | param | 获得请求参数的单个值,相当于request.getParameter() |
paramValues | 获得请求参数的一组值,相当于request.getParameterValues() | |
HTTP请求头 | header | 获得HTTP请求头中的单个值,相当于request.getHeader() |
headerValues | 获得HTTP请求头中的一组值,相当于request.getHeadersValues() | |
Cookie | cookie | 获得请求中的Cookie值 |
初始化参数 | initParam | 获得上下文的初始参数值 |
1.3 EL 运算符
1.3.1 算术运算符
算术运算符 | 说明 | 范例 | 运算结果 |
---|---|---|---|
+ | 加 | ${1+2} | 3 |
- | 减 | ${2-1} | 1 |
* | 乘 | ${2*3} | 6 |
/ 或 div | 除 | 16 / 5 或 {16 / 5}或 16/5或{16 div 5} | 3 |
% 或 mod | 取余 | KaTeX parse error: Expected '}', got 'EOF' at end of input: {16 % 5}或{16 mod 5} | 1 |
1.3.2 关系运算符
关系运算符 | 说 明 | 范 例 | 运算结果 |
---|---|---|---|
== 或 eq | 等于 | 1 = = 2 或 {1==2}或 1==2或{1 eq 2} | false |
!= 或 ne | 不等于 | 2 ! = 1 或 {2!=1}或 2!=1或{1 ne 2} | true |
< 或 lt | 小于 | 2 < 3 或 {2 < 3}或 2<3或{2 lt 3 } | true |
> 或 gt | 大于 | 16 > 5 或 {16 > 5}或 16>5或{16 gt 5} | true |
<= 或 le | 小于等于 | 16 < = 5 或 {16 <= 5}或 16<=5或{16 le 5} | false |
>= 或 ge | 大于等于 | 16 > = 5 或 {16 >= 5}或 16>=5或{16 ge 5} | true |
1.3.3 逻辑运算符
逻辑运算符 | 说明 | 范例 | 运算结果 |
---|---|---|---|
&& 或 and | 与运算 | KaTeX parse error: Expected '}', got '&' at position 7: {true &̲& true}或{true and true} | true |
|| 或or | 或运算 | t r u e ∥ ∥ f a l s e 或 { true \|\| false}或 true∥∥false或{true or false} | true |
! 或not | 非运算 | ! t r u e 或 {! true}或 !true或{not true } | false |
2 JSTL
2.1 JSTL 简介
JSTL —Java Server Pages Standard Tag Library,JSP 标准标签库
JSTL 有以下优点:
-
针对JSP开发中频繁使用的功能提供了简单易用的标签,从而简化了JSP开发;
-
作为JSP规范,以统一的方式减少了JSP中的Java代码数量,力图提供一个无脚本环境;
-
在应用程序服务器之间提供了一致的接口,最大程度的提高了Web应用在各应用服务器之间的可移植性。
JSTL 提供的标签库分为5个部分:
-
核心标签库
-
国际化输出标签库
-
XML 标签库
-
SQL 标签库
-
EL 函数库
2.2 核心标签库
在 JSP 页面中使用核心标签库,需要使用 taglib 指令导入,核心标签库通常使用前缀“c”,语法格式如下:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
2.2.1 通用标签
-
<c:cout>
-
<c:set>
-
<c:remove>
-
<c:catch>
(1)<c:cout>:输出数据。
其语法格式如下:
<c:out value="value" />
其中,“value”表示要输出的数据,可以是一个 EL 表达式或静态值。
(2)<c:set>:设置指定范围内的变量值。
其语法格式如下:
<c:set var="name" value="value" scope="page|request|session|application" />
其中:
-
var:指定变量的名称。
-
value:设置变量的值。
-
scope:指定变量的范围,可以是 page、request、session 或 application,缺省为 page。
2.2.2 条件标签
-
<c:if>
-
<c:choose>
-
<c:when>
-
<c:otherwise>
2.2.2.1 <c:if>
<c:if>用于进行条件判断,其语法格式:
<c:if test="condition" var="name" scope="page|request|session|application">
// condition为true时,执行的代码
</c:if>
2.2.3 迭代标签
JSTL提供迭代标签简化了迭代操作代码,包括:
- <c:forEach>
- <c:forTokens>
六、监听器与过滤器
1. 监听器
1.1 监听器简介
- 监听器就是一个实现特定接口的普通 Java 程序,这个程序专门用于监听另一个 Java 对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行。
- Java Web 中的监听器是 Servlet 规范中定义的一种特殊类,它用于监听 Web 应用程序中的 ServletContext、HttpSession 和 ServletRequest 等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。
1.2 监听器分类
在 Servlet 规范中定义了多种类型的监听器,它们用于监听的事件源分别为 ServletContext,HttpSession 和 ServletRequest 这三个域对象
Servlet 规范针对这三个对象上的操作,又把多种类型的监听器划分为三种类型:
-
监听域对象自身的创建和销毁的事件监听器。
-
监听域对象中的属性的增加和删除的事件监听器。
-
监听绑定到 HttpSession 域中的某个对象的状态的事件监听器。
1.3 常用的监听器
-
ServletContextListener
-
HttpSessionListener
-
ServletRequestListener
1.3.1 ServletContextListener
ServletContextListener 接口用于监听 ServletContext 对象的创建和销毁事件。
实现了 ServletContextListener 接口的类都可以对 ServletContext 对象的创建和销毁进行监听。
- 当 ServletContext 对象被创建时,激发 contextInitialized (ServletContextEvent sce)方法。
- 当 ServletContext 对象被销毁时,激发 contextDestroyed(ServletContextEvent sce)方法。
ServletContext域对象创建和销毁时机:
创建:服务器启动针对每一个Web应用创建ServletContext。
销毁:服务器关闭前先关闭代表每一个web应用的ServletContext。
案例1:编写一个 MyServletContextListener 类,实现 ServletContextListener 接口,监听 ServletContext 对象的创建和销毁。
案例2:统计 Web 应用启动后,被访问的次数。
分析:
-
如果重新启动 Web 应用,计数器不会重新从1 开始统计访问次数,而是从上次统计的结果上进行累加。
-
在实际应用中,往往需要统计自 Web 应用被发布后的次数,这就要求当 Web 应用被终止时,计数器的数值被永久存储在一个文件中或者数据库中。
-
等 Web 应用重新启动时,先从文件或数据库中读取计数器的初始值,然后在此基础上继续计数。
1.3.2 HttpSessionListener
HttpSessionListener 接口用于监听 HttpSession 对象的创建和销毁。
- 创建一个 Session 时,激发 sessionCreated (HttpSessionEvent se) 方法。
- 销毁一个 Session 时,激发 sessionDestroyed (HttpSessionEvent se) 方法。
案例1:编写一个 MyHttpSessionListener 类,实现 HttpSessionListener 接口,监听 HttpSession 对象的创建和销毁。