Servlet学习笔记
文章目录
- 1 Web开发简介
- 2 Servlet简介
- 3 IDEA新建Servlet项目
- 4 Http协议
- 5 Servlet
- 6 HttpServletRequest
- 7 HttpServletResponse
- 8 请求转发和重定向
- 9 ServletConfig
- 10 ServletContext
- 10.1 为什么需要ServletContext
- 10.2 解决之道ServletContext
- 10.3 this.getServletContext()
- 10.4 servletConfig.getServletContext()
- 10.5 request.getServletContext()
- 10.6 session.getServletContext()
- 10.7 setAttribute(name,value)
- 10.8 getAttribute(name)
- 10.9 getRealPath(filepath)
- 10.10 getResourceAsStream(filepath)
- 10.11 getInitParameter(name)
- 10.12 ServletContext小结
- 10.13 ServletContext应用
- 10.14 ServletContext注意事项
- 11 Session
- 12 Cookie
- 12.1 为什么需要cookie
- 12.2 解决之道cookie
- 12.3 写入cookie
- 12.4 读取cookie
- 12.5 new Cookie(name,value)
- 12.6 cookie.setValue(String)
- 12.7 cookie.setMaxAge()
- 12.8 response.addCookie(cookie)
- 12.9 request.getCookies()
- 12.10 cookie.getName()
- 12.11 cookie.getValue(String)
- 12.12 cookie.getMaxAge()
- 12.13 cookie小结
- 12.14 cookie vs session
- 13 文件上传下载
- 14 中文乱码处理专题
- 15 Servlet操作数据库
- 16 Servlet开发项目
- 17 过滤器Filter
- 18 监听器Listener
1 Web开发简介
1.1 web开发介绍
Web,在英语中web即表示网页的意思,它用于表示Internet主机上供外界访问的资源,即供浏览器访问的资源。
。Internet上供外界访问的Web资源分为:
静态web资源(如html页面):指web页面中供人们浏览的数据始终是不变。
动态web资源:指web页面中供人们浏览的数据是由程序产生的,不同时间点访问web页面看到的内容各不相同。
。静态web资源开发技术
html
。常用动态web资源开发技术
JSP/Servlet、PHP、asp.net等 (Linux+tomcat+MySQL+jsp+servlet)
在Java中,动态web资源开发技术统称为Javaweb,
我们课程的重点也是教大家如何使用Java技术开发动态的web资源,即动态web页面。
1.2 动态网页技术的发展
当www网初见江湖时,当时主要是html,由于html只支持静态的文字和图片,不能与用户进行交互,为了弥补这个不足,
陆续出现了下列网页技术:
(1) asp
微软的产品
(2) php
(3) jsp
jsp = html+java片段+jsp语法+javascript
jsp优势:
1.一次编写,到处运行
2.良好的跨平台性 (linux或windows都行)
3.多种开发工具支持
4.强大的可伸缩性(jsp+javabean)的方式
jsp的不足:
1.jsp产品的复杂度高
2.jsp要求运行的机器配置要高,
因为jsp是用class常驻内存的方式运行的,效率高,但是需要占用更多的内存
1.3 B/S与C/S的介绍
B/S: browser/server
C/S: Client/Sesver
B/S的优势:
1.开发成本低
2.管理维护简单
3.产品升级便利
4.对用户的培训费用低
5.用户使用方便,出现故障的概率小
B/S的不足:
1.安全性不足 (别人能看到html代码)
2.客户端不能随心变化,受浏览器的限制
2 Servlet简介
2.1 为什么会出现servlet?
需求:请你用现有的Java技术,开发一个动态网页,比如:可以让用户留言,其他人可以回复。
用Java实现不了
解决方案:servlet 2000年 大行其道
sun公司就开发了servlet供开发人员使用。
2.2 什么是servlet
Servlet技术是在Java EE出现之前就存在了,在开发动态网页中,得到了广泛的应用,直到现在的Java EE项目中,
也是非常重要的,同时jsp也是在servlet基础上发展起来的。因此,掌握好servlet太重要了。
Servlet(java服务器小程序)是用Java编写的服务器程序,它的特点:
1.他是由服务器端(tomcat)调用和执行的
2.他是用Java语言编写的 Servlet其实就是Java程序(Java类)
3.他是按照Servlet规范开发的 该Java类要遵循Servlet开发规范
4.功能强大,可以完成几乎所有的网站功能
5.是学习JSP的基础
2.3 Servlet开发工具
1.常用的开发工具
普通的文本编辑器:notePad++,editplus
集成开发工具:jcreateor,jbuilder,eclipse,Idea
2.常用的web服务器
浏览器 | Web服务器 | 数据库 |
---|---|---|
所有浏览器 | Tomcat Weblogic Jboss Websphere | SQL2000 Oracle MySQL |
2.4 tomcat和servlet在网络中的位置
3 IDEA新建Servlet项目
IDEA新建Servlet项目步骤:
3.1 建立web工程
1.1 点击File–>New–>Project…
1.2 在弹出的界面中,选择“Java Enterprise”,选择JDK版本,选择Tomcat版本,勾选“Web Application”,点击“Next”
1.3 在接下来的界面中,填写项目名称,选择项目存放的路径,点击“Finish”
1.4 生成的jsp_servlet_project项目结构
3.2 在src目录下新建包
在src目录下新建包com.tangguanlin
3.3 开发一个Servlet
package com.tangguanlin;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 说明:第一个servlet工程
* 作者:汤观林
* 日期:2022年02月02日 17时
*/
public class MyHttpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("hello,servlet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
3.4 在web.xml中添加Servlet映射
<?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">
<servlet>
<servlet-name>MyHttpServlet</servlet-name>
<servlet-class>com.tangguanlin.MyHttpServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyHttpServlet</servlet-name>
<url-pattern>/MyHttpServlet</url-pattern>
</servlet-mapping>
</web-app>
3.5 部署到tomcat
5.1 选择tomcat版本
5.2 添加要部署的项目,填写发布后的访问路径名jsp_servlet_project
5.3 点击"运行"图标,运行项目
3.6 测试项目
浏览器输入:http://localhost:8080/jsp_servlet_project/MyHttpServlet
3.7 扩展:发布到webapp目录下
1.点击“Project Structure”,选择左侧的Artifacts,输入部署后的项目名称,输出文件夹选择Tomcat的webapp目录,
注意:webapp后面要加上项目名称
2.部署后,在webapp目录下,生成了jsp_servlet_project项目
3.8 IDEA发布后不在webapp目录
IDEA发布后,为什么不在webapp目录下
问题:将 tomcat 集成到IDEA中,并且在 tomcat 中部署了 web 项目,在IDEA中运行 tomcat,
但发现IDEA使用 tomcat 部署的项目在 tomcat 安装目录下的 webapps 中无法找到,那么它到底在哪里呢?
结论:IDEA 使用 tomcat 部署项目后不会把编译后的项目复制到 tomcat 的 webapps 目录下,
而是复制一份配置文件到指定目录下,让 tomcat 找到这个项目,
也就是说每个项目都有属于自己的一份 tomcat 配置,互不干扰。
在IDEA安装目录下,
在C:\Users\Administrator.IntelliJIdea2019.3\system\tomcat目录下:
在C:\Users\Administrator.IntelliJIdea2019.3\system\tomcat\Tomcat_8_0_12_jsp_servlet_project_5\conf\Catalina\localhost目录下:
映射文件:jsp_servlet_project.xml
<Context path="/jsp_servlet_project"
docBase="D:\05project\jsp_servlet_project\out\artifacts\jsp_servlet_project_war_exploded" />
其中,path 指虚拟目录名称;docBase 指IDEA使用 tomcat 部署项目的路径。
因此,整个项目的运行过程:
IDEA 复制一份 tomcat 的配置文件到指定目录中,
之后启动 tomcat 安装目录下的 catalina.bat 文件,
tomcat读取配置文件,找到项目位置,然后就运行起来了。
特别说明:jsp_servlet_project.xml这个配置文件是IDEA的,不是Tomcat的,只有Idea才认识。
相当于是IDEA内部的一个映射转移,
如果关闭IDEA,想要项目在Tomcat中运行,
需要将项目拷贝到Webapp目录下,启动Tomcat,才能访问。
4 Http协议
4.1 什么是HTTP协议
1.HTTP协议:HyperText Transfer Protocol 超文本传输协议,是互联网上应用最为广泛的一种网络协议。
是工作在TCP/IP协议基础之上的,所有的www文件都必须遵守这个标准。
设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。
2.通过httpwatch插件来抓取http请求内容
3.http1.0短连接 http1.1 长连接 所谓长和短,是指持续时间的长度
http1.1 持续时间30秒 短连接是发完数据就断掉
4.http是TCP/IP协议的一个应用协议,http也是我们web开发的基础
4.2 http请求
4.2.1 http请求介绍
客户端连上服务器后,向服务器请求某个web资源,称之为客户端向服务器发送了一个http请求。
一个完整的http请求包含了如下内容:
一个请求行,若干消息头,以及实体内容。
其中消息头和实体内容都是可选的,消息头和实体内容之间要用空行隔开。
如下所示:
GET /test/hello.html HTTP/1.1 请求行
Accept: */*
Referer: http://localhost:8080/test/abc.html
Accept-Language:zh-cn
User-Agent:Mozilla/4.0
Accept-Encoding:gzip,deflate
Host:localhost:8080
Connection:Keep-Alive 消息头
空格
消息内容
消息头格式 消息名:内容
特别说明:消息头并不是每一次请求内容都是一样的。有时候多,有时候少
如果Java发起的http请求,Referer是空的。可以防机器人不停的请求。
4.2.2 请求行
。请求行中的get称之为请求方式,请求方式有:
GET,POST,HEAD,PUT,DELETE,TRACE,OPTIONS
常用的有GET,POST
。get和post区别是程序员常常讨论的问题,总结区别如下:
2.get提交,请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),
以?分隔URL和传输数据,多个参数用&连接,例如:
login.action?name=abc&password=kkk
post提交:把提交的数据放置在http包的包体中,比如:
POST /servlet/ParamsServlet HTTP/1.1
Host:
Content-Type:application/x-www-form-urlencoded
Content-Length:28
name=abc&password=xyz 消息内容
因此,get提交的数据会在地址栏中显示出来,而post提交,地址栏不会改变
2.传输数据的大小:
首先声明:http协议没有对传输的数据大小进行限制,http协议规范也没有对URL长度进行限制。
而在实际开发中存在的限制主要有;
get: 特定浏览器和服务器对URL长度有限制。例如IE对URL长度的限制是2083字节(2K+35)。
对于其他浏览器,如FireFox,理论上没有长度限制,其限制取决于操作系统的支持。
因此get提交时,传输数据就会受到URL长度的限制。
post: 由于不是通过URL传输,理论上数据不受限。
3.安全性:相对而言,post提交,安全高
4.2.3 请求消息头
#告诉服务器,我可以接收文本,网页,图片
Accept: text/html,application/xml;q=0.9,image/avif,image/webp,image/apng
#接收字符编码:ISO-8859-1
Accept-Charset:ISO-8859-1
#可以接收gzip 压缩后的数据
Accept-Encoding: gzip, deflate, br
#浏览器支持的语言:中文 英文
Accept-Language: zh-CN,zh;q=0.9
#保存连接,发完数据不关闭连接
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
#我要找的主机是谁
Host: localhost:8080
Origin: http://localhost:8080
#告诉服务器,我来自哪里 常用于防止盗链 Referer为空,代表不是本网站过来的,只有本网站请求才有值
Referer: http://localhost:8080/UserManager/loginJsp
#告诉服务器,我的浏览器的内核
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Mobile Safari/537.36
#cookie
Cookie:
Java中获取消息头信息:
String host = req.getHeader("Host"); //localhost:8080
out.println("host:"+host);
String UserAgent = req.getHeader("User-Agent"); //
out.println("User-Agent:"+UserAgent);
4.3 http响应
4.3.1 http响应介绍
一个http响应代表服务器向客户端回送的数据,它包括:
一个状态行、若干消息头、以及实体内容,其中的一些消息头和实体内容都是可选的,消息头和实体内容之间要用空格隔开。
举例:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html,charset=utf-8;charset=ISO-8859-1
Content-Length: 168
Date: Thu, 03 Feb 2022 07:59:17 GMT
4.3.2 状态行
格式: http版本号 状态码 原因叙述
比如: HTTP/1.1 200 ok
状态码用于表示服务器对请求的处理结果,它是一个3位数的十进制数。
响应状态码分为5类,如下所示:
状态码 | 含义 |
---|---|
100~199 | 表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程 |
200~299 200 | 表示成功接收请求并已完成整个处理过程,常用200 |
300~399 | 为完成请求,客户需进一步细化请求。例如,请求的资源已经移动一个新地址,常用302、307 |
400~499 404 | 客户端的请求有错误,常用404 |
500~599 500 | 服务器出现错误,常用500 |
Java端改写http响应信息:
//重定向
resp.setStatus(302);
resp.setHeader("Location","http://localhost:8080/jsp_servlet_project/MyHttpServlet");
//等价
resp.sendRedirect("/jsp_servlet_project/MyHttpServlet");
4.3.3 响应消息头
HTTP/1.1 302 Found
#告诉浏览器,我是Tomcat类型
Server: Apache-tomcat/1.1
#让浏览器重新定位到url去
Location: http://localhost:8080/jsp_servlet_project/MyHttpServlet
#内容的格式 text/html 编码是ISO-8859-1
Content-Type: text/html,charset=utf-8;charset=ISO-8859-1
#告诉浏览器,我使用了gzip
Content-Encoding:gzip
#告诉浏览器,回送的数据大小80字节
Content-Length: 0
#支持中文
Content-Language:zh-cn
#过多久,刷新到百度
Refresh:1;url=http://www.baidu.com
#告诉浏览器,有文件需要下载
Content-Disposition:attachment;filename=aaa.zip
#cookie信息
Set-Cookie:SS=Q0=5Lb_nQ;path=/search
#指定不要缓存
Expires:-1
#指定不要缓存
Cache-Control:no-cache
#指定不要缓存
Pragma:no-cache
#保存连接,发完数据不关闭连接
Connection: keep-alive
#返回送时间
Date: Thu, 03 Feb 2022 08:57:11 GMT
//定时刷新网页
resp.setHeader("Refresh","5;url=http://www.baidu.com");
4.3.4 禁止缓存
提出问题:
我们的浏览器在默认的情况下,会缓存我们的页面,这样就会出现一个小问题:
如果我们的用户习惯把光标停在地址栏,然后回车去取页面,就会默认从cache中取数据。
第1次:
第2次:
(1)股票,基金系统,有些系统对及时性要求很高,要求我们不缓存页面?
package com.tangguanlin;
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;
import java.util.Date;
/**
* 说明: 页面不缓存
* 作者:汤观林
* 日期:2022年02月03日 18时
*/
public class NoCacheServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setDateHeader("Expires",-1);
resp.setHeader("Cache-Control","no-cache");
resp.setHeader("Pragma","no-cache");
PrintWriter out = resp.getWriter();
out.println("hello world"+new Date());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
运行结果:
(2)有些系统要求网页缓存一定时间,比如缓存一个小时
//指定缓存时间
resp.setDateHeader("Expires",System.currentTimeMillis()+3600*1000);
4.3.5 通用信息头
通用信息头指既能用于请求,又能用于响应的一些消息头。
#保存连接,发完数据不关闭连接
Connection: keep-alive
#返回送时间
Date: Thu, 03 Feb 2022 08:57:11 GMT
5 Servlet
5.1 开发Servlet有三种方法
a.实现servlet接口
b.继承GenericServlet
c.继承HttpServlet
使用eclipse开发servlet非常容易,但是,它隐藏了太多的细节。
为了让大家可以学到Servlet的底层机制和运行原理,我们先用记事本开发,
然后使用eclipse开发。
#补充:如果使用javac去编译一个java文件,则需要带命令参数
javac -d . MyFirstServlet.java
5.2 实现servlet接口
实现servlet接口来编写一个servlet
介绍servlet的生命周期,该servlet完成一个非常简单的功能,向浏览器回送hello,world!
步骤:
1.建立一个web应用hspWeb1
2.在hspWeb1下建立 WEB-INF/web.xml(可以从别的地方拷贝)
3.在hspWeb1下建立 WEB-INF/classes,我们的Servlet就要在这个目录开发
4.开发MyFirstServlet.java
package com.tangguanlin;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class MyFirstServlet implements Servlet{
//得到ServletConfig对象
public ServletConfig getServletConfig(){
return null;
}
//该函数用于初始化servlet,就是把该servlet装载到内存中
//该函数只会调用一次
public void init(ServletConfig config){
}
//该函数是服务函数
//我们的业务逻辑代码就是写在这里
//该函数每次都会调用
public void service(ServletRequest req,ServletResponse res) throws ServletException,java.io.IOException{
System.out.println("hello,world");
res.getWriter().println("hello world"); //向页面输出hello world
}
//该函数是得到servlet配置信息
public String getServletInfo(){
return null;
}
//销毁该Servlet,从内存中清除
//该函数只会调用一次
public void destroy(){
}
}
5.根据Servlet规范,我们还需要在web.xml中部署Servlet
web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<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_3_1.xsd"
version="3.1"
metadata-complete="true">
<!--根据servlet规范,需要将servlet部署到web.xml文件-->
<servlet>
<!--servlet名称 是该servlet取个名字,名字可以自定义,默认就使用该servlet的名字-->
<servlet-name>MyFirstServlet</servlet-name>
<!--指明该servlet放在哪个包下-->
<servlet-class>com.tangguanlin.MyFirstServlet</servlet-class>
</servlet>
<!--servlet的映射-->
<servlet-mapping>
<!--这个servlet的名字,要和上面的servlet名字一样-->
<servlet-name>MyFirstServlet</servlet-name>
<!--是将来访问servlet的请求路径,默认命名规范:就是该Servlet的名字-->
<url-pattern>/MyFirstServlet</url-pattern>
</servlet-mapping>
</web-app>
6.测试
http://localhost:8080/hspWeb1/MyFirstServlet
5.3 实现GenericServlet接口
实现GenericServlet接口,了解即可。
5.4 继承HttpServlet
使用继承HttpServlet的方法开发Servlet
1.在软件公司,90%是通过该方法开发
2.举例说明,还是显示 hello world
步骤:
1.建立一个web应用hspWeb1
2.在hspWeb1下建立 WEB-INF/web.xml(可以从别的地方拷贝)
3.在hspWeb1下建立 WEB-INF/classes,我们的Servlet就要在这个目录开发
4.开发MyHttpServlet.java
package com.tangguanlin;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class MyHttpServlet extends HttpServlet{
//在HttpServlet中,设计者对get提交和post提交 分别处理
//其实,doGet和doPost底层都是调用了Servlet的service()方法
protected void doGet(HttpServletRequest req,HttpServletResponse res) throws ServletException,IOException{
res.getWriter().println("I am httpServlet doGet");
}
protected void doPost(HttpServletRequest req,HttpServletResponse res) throws ServletException,IOException{
res.getWriter().println("I am httpServlet doPost");
}
}
5.根据Servlet规范,我们还需要在web.xml中部署Servlet
web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<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_3_1.xsd"
version="3.1"
metadata-complete="true">
<servlet>
<servlet-name>MyHttpServlet</servlet-name>
<servlet-class>com.tangguanlin.MyHttpServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyHttpServlet</servlet-name>
<url-pattern>/MyHttpServlet</url-pattern>
</servlet-mapping>
</web-app>
6.测试
http://localhost:8080/hspWeb1/MyHttpServlet
get提交和post提交的区别:
1.从安全性看,get<post,get提交的数据会在浏览器的地址栏显示
2.从提交的内容大小,get<post,get提交的数据不能大于2K,而post提交的数据理论上不受限制,但是实际编程中建议不要大于64K。
3.从请求响应速度看,get>post,get要求服务器立即响应,而post请求可能形成一个队列请求。
5.5 Servlet生命周期
Servlet的生命周期是怎样的?
Servlet究竟是怎样工作的?

Servlet运行原理:
1.当Servlet第一次被调用的时候,会触发init函数,该函数会把Servlet实例装载到内存,init函数只会被调用一次
2.然后去调用Servlet的service函数
3.当第二次后访问该Servlet,就直接调用service函数
4.当web应用reload或者关闭tomcat,或者关机,都会去调用destroy函数,该函数就会去销毁Servlet。
Servlet运行过程:
Servlet程序是由Web服务器调用,web服务器收到客户端的Servlet访问请求后:
1.web服务器首先检查是否已经装载并创建了该Servlet的实例对象。
如果是则直接执行第4步,否则,执行第2步
2.装载并创建该Servlet的一个实例对象
3.调用Servlet实例对象的init()方法
4.创建一个用于封装http请求消息的HttpServletRequest对象和一个代表http响应消息的HTTPServletResponse对象,
然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
5.web应用程序被停止或者重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
以下情况会导致destroy()方法调用:
(1)tomcat重新启动 (2)reload该Webapp (3)重新启动电脑
5.6 Servlet细节
一般开发人员习惯把doGet()和doPost合二为一
。HttpServlet指能够处理http请求的servlet,它在原有Servlet接口上添加了一些与http协议处理方法,
它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,
而避免直接去实现Servlet接口。
。HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,
如为Get请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。
因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
Servlet细节:
1.由于客户端是通过URL地址访问web服务器中的资源,
所以Servlet程序若想被外界访问,必须把Servlet程序映射到一个URL地址上,
这个工作在web.xml文件中使用元素和元素完成。
2.元素用于注册Servlet,它包含有两个主要的子元素:和,
分别用于设置Servlet的注册名称和Servlet的完整类名。
3.一个元素用于映射一个已注册的Servlet的一个对外访问路径。
它包含有两个子元素:和,分别用于指定Servlet的注册名称和Servlet的对外访问路径。
例如:
<web-app>
<servlet>
<!--servlet的注册名-->
<servlet-name>MyHttpServlet</servlet-name>
<!--servlet类的全路径-->
<servlet-class>com.tangguanlin.MyHttpServlet</servlet-class>
</servlet>
<servlet-mapping>
<!--servlet的注册名-->
<servlet-name>MyHttpServlet</servlet-name>
<!--servlet的访问路径-->
<url-pattern>/MyHttpServlet</url-pattern>
</servlet-mapping>
</web-app>
4.当映射一个servlet的时候,可以多层,比如
/servlet/index.html
从这里可以看出,后缀名是html不一定就是html。
5.一个已经注册的servlet,可以被映射到多个URL上,
即多个元素的子元素的设置值可以是同一个Servlet的注册名。
6.在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:
一种格式是 *.扩展名
<url-pattern>*.do</url-pattern>
另一种格式是以正斜扛(/)开头并以“/*”结尾
比如 /* /news/*
<url-pattern>/news/*</url-pattern>
在匹配的时候,要参考的标准:
(1) 看谁的匹配度高,谁就被选择
(2) *.do的优先级最低
7.Servlet是一个供其他Java程序(Servlet引擎,其实就是Web服务器Tomcat)调用的Java类,它不能独立运行,
它的运行完全由Servlet引擎来控制和调度。
8.Servlet单例问题
针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例,也就是说Servlet实现对象一旦创建,它就会
驻留在内存中,为后续的其他请求服务,直至web容器退出/或者reload该web应用,Servlet实例对象才会销毁。
当Servlet被第一次访问后,就被加载到内存,以后该实例对各个请求服务,即Servlet在使用中是单例。
因为Servlet是单例,因此会出现线程安全问题。
比如售票系统,如果不加同步机制,则会出现问题。
原则:
(1)如果一个变量需要多个用户共享,则应作为Servlet的成员变量,在访问该变量的时候,要加同步机制,才不会出现线程安全问题。
全局变量 + 加锁
synchronized (this){
//业务逻辑
}
(2)如果一个变量不需要共享,则直接在doGet()或者doPost()定义。
局部变量
9.在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次Servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,
然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
10.如果元素中配置了一个元素,
那么web应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
用途:为web应用写一个InitServlet,这个Servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。
或者启动一个后台线程,定时去完成某些工作(比如每个10秒发送一封电子邮件)。
案例:定时发送电子邮件功能
web.xml
<servlet>
<servlet-name>MyInitServlet</servlet-name>
<servlet-class>com.tangguanlin.MyInitServlet</servlet-class>
<!--1表示Servlet被启动的顺序-->
<load-on-startup>1</load-on-startup>
</servlet>
MyInitServlet.java
package com.tangguanlin;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
/**
* 说明:<load-on-startup>的使用
* 作者:汤观林
* 日期:2022年02月02日 23时
*/
public class MyInitServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("MyInitServlet init被调用");
//完成一些初始化任务
System.out.println("创建数据库,表,读取参数");
SendMailThread sendMailThread = new SendMailThread();
sendMailThread.run();
}
}
SendMailThread.java 线程类
package com.tangguanlin;
import java.util.Date;
/**
* 说明:用线程实现定时任务
* 作者:汤观林
* 日期:2022年02月02日 23时
*/
public class SendMailThread extends Thread {
@Override
public void run() {
while(true){
try {
System.out.println("每分钟执行一次"+new Date());
Thread.sleep(60*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
MyInitServlet init被调用
创建数据库,表,读取参数
每分钟执行一次Thu Feb 03 00:21:54 CST 2022
每分钟执行一次Thu Feb 03 00:22:54 CST 2022
6 HttpServletRequest
Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象和代表响应的response对象。
request对象和response对象即代表请求和响应,
那我们要获取客户端提交过来的数据,只需要找request对象就行了,
要向客户端输出数据,只需要找response对象就行了。
httpServletRequest对象代表客户端的请求,当客户端通过http协议访问服务器时,
http请求头中的所有信息都封装在这个对象中,
开发人员通过这个对象的方法,可以获得客户这些信息。
该对象表示浏览器的请求(http请求),当web服务器得到该请求后,会把请求信息封装成一个httpServletRequest对象。
6.1 获得客户机信息
getRequestURL(); //方法返回客户端发出请求时的完整URL
getRequestURI(); //方法返回请求行中的资源名部分
getQueryString(); //方法返回请求行汇总的参数部分(参数名+值) 获取get方式提交的参数
getRemoteAddr(); //方法返回发出请求的客户端的IP地址
getRemoteHost(); //方法返回发出请求的客户端的完整主机名 要在dns注册了才能返回主机名,没有注册就返回IP地址
getRemotePort(); //方法返回客户端所使用的网络端口号
getLocalPort(); //方法返回web服务器所使用的网络端口号
getLocalName(); //方法返回web服务器的主机名
getLocalAddr(); //方法返回web服务器的IP地址
代码:
String url = request.getRequestURL().toString();
System.out.println("url="+url);
String uri = request.getRequestURI();
System.out.println("uri="+uri);
String queryString = request.getQueryString();
System.out.println("queryString="+queryString);
String clientAddr = request.getRemoteAddr();
System.out.println("clientAddr="+clientAddr);
String clientHost = request.getRemoteHost();
System.out.println("clientHost="+clientHost);
int clientPort = request.getRemotePort();
System.out.println("clientPort="+clientPort);
String localAddr = request.getLocalAddr();
System.out.println("localAddr="+localAddr);
String localName = request.getLocalName();
System.out.println("localName="+localName);
int localPort = request.getLocalPort();
System.out.println("localPort="+localPort);
http请求:http://localhost:8080/jsp_servlet_project/myHttpServletRequest?username=tangguanlin&password=123
运行结果:
url=http://localhost:8080/jsp_servlet_project/myHttpServletRequest
uri=/jsp_servlet_project/myHttpServletRequest
queryString=username=tangguanlin&password=123
clientAddr=127.0.0.1
clientHost=127.0.0.1
clientPort=56023
localAddr=127.0.0.1
localName=www.tangguanlin.com
localPort=8080
6.2 getMethod()
获取http请求方式,比如get请求,post请求
String httpMethod = request.getMethod();
6.3 getHeader()
getHeader()获取请求头的内容
GET /test/hello.html HTTP/1.1 请求行
Accept: */*
Referer: http://localhost:8080/test/abc.html
Accept-Language:zh-cn
User-Agent:Mozilla/4.0
Accept-Encoding:gzip,deflate
Host:localhost:8080
获取请求头信息:
String host = req.getHeader("Host");
String accept = req.getHeader("Accept");
String referer = req.getHeader("Referer");
6.4 getHeaderNames()
把整个http请求头的消息全部获取
//得到所有的消息头信息
Enumeration<String> headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()){
//取出消息头的名字
String headerName = headerNames.nextElement();
System.out.println(headerName+"="+request.getHeader(headerName));
}
运行结果:
host=localhost:8080
connection=keep-alive
upgrade-insecure-requests=1
user-agent=Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
referer=http://localhost:8080/jsp_servlet_project/myform.html
accept-encoding=gzip, deflate, br
accept-language=zh-CN,zh;q=0.9
cookie=JSESSIONID=15C0191BBC468655F6E9CDF8DED11071
6.5 getParameter(String)
获取客户端请求参数,客户端提交的数据 和http协议有关的
String username = request.getParameter("username");
System.out.println(username);
String password = request.getParameter("password");
System.out.println(password);
6.6 getParameterNames()
获取客户端所有请求参数,客户端提交的所有数据
//取出所有的字段数据
Enumeration<String> parameterNames = request.getParameterNames();
while(parameterNames.hasMoreElements()){
String nextElement = parameterNames.nextElement();
String value = request.getParameter(nextElement);
System.out.println(nextElement+"="+value);
}
6.7 getParameterValues(String)
如果接收复选框的内容,则使用getParameterValues,得到是数组数据,多个选项数据
<form>
爱好(复选框):
<input type="checkbox" name="hobby" value="音乐">音乐
<input type="checkbox" name="hobby" value="体育">体育
<input type="checkbox" name="hobby" value="唱歌">唱歌
<input type="checkbox" name="hobby" value="爬山">爬山
<input type="checkbox" name="hobby" value="旅游">旅游
所在城市(下拉列表):
<select name="city">
<option value="bj">北京</option>
<option value="sh">上海</option>
<option value="tj">天津</option>
<option value="gz">广州</option>
</select>
</form>
//如果接收复选框的内容,则使用getParameterValues,得到是数组数据
String[] hobbies = request.getParameterValues("hobby");
for(String hobby:hobbies){
System.out.println(hobby);
}
6.8 setAttribute(key,value)
把数据设置到request域对象的属性中
request.setAttribute("useranme","韩顺平");
6.9 getAttribute(key)
获取request域对象的属性值,获取和http协议无关的值
String username = (String)request.getAttribute("username");
6.10 getServletContext()
获取ServletContext对象
ServletContext servletContext = request.getServletContext();
6.11 getContextPath()
获取项目资源路径
String contextPath = request.getContextPath();
System.out.println(contextPath);
运行结果:
/jsp_servlet_project
6.12 getRequestDispatcher()
通过request对象实现请求转发功能
request.setAttribute("useranme","韩顺平");
request.getRequestDispatcher("/myHttpServlet").forward(request,resp);
6.13 getSession()
获取session对象
HttpSession session = request.getSession();
6.14 getCookies()
获取客户端所有的cookie对象
Cookie[] cookies = request.getCookies();
7 HttpServletResponse
HttpServletResponse对象服务器的响应。
这个对象中封装了向客户端发送数据,发送响应头,发送响应状态码的方法。
Servlet程序向ServletOutputStream或PrintWriter对象中写入的数据将被Servlet引擎从response里面获取,
Servlet引擎将这些数据当做响应消息的正文,当然再与响应状态行和各响应头组合后输出到客户端。
Servlet的service方法结束后,
Servlet引擎将检查getWriter或getOutputStream方法返回的输出流对象是否已经调用close方法,
如果没有,Servlet引擎将调用close方法关闭该输出流对象。
对于发送数据,服务器按照response.setCharacterEncoding—contentType—pageEncoding的优先顺序,对要发送的数据进行编码。
7.1 getWriter()
回送文本,回送字符数据
如果我们要回送字符数据,则使用PrintWriter对象,效率高
PrintWriter和outputStream两个流不能同时使用
PrintWriter out = response.getWriter();
out.println("hello world");
7.2 getOutputStream()
回送二进制数据,可以回送字节数据,也可以回送字符数据
如果我们是要回送字节数据,则只能只用OutputStream。
PrintWriter和outputStream两个流不能同时使用
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write("hello world2".getBytes());
7.3 sendRedirect(String url)
请求重定向:一个web资源收到客户端请求后,通知客户端去访问另外一个web资源。
通过该方法将信息传送给下一个页面。
比如:sendRedirect(“welcome?username=shunping”);的形式
response.sendRedirect("/UserManager/mainJsp?username="+username);
优点:传送信息的速度比较快
缺点:它只能传送字符串,而不能传送一个对象
如果要传对象,可以将对象放入session中,在下一个servlet中将对象从session中取出来即可。
注意点:
1.welcome代表你要跳转的那个servlet的url
2.servlet url名和变量之间有?
3.如要传递两个以上的值,它们间要用&号分开:比如
response.sendRedirect("welcome?username=shunping&password=ok");
4.如果传递的是中文,那你将得到乱码,需要处理一下
7.4 setHeader(String,String)
发送http头,控制浏览器禁止缓存当前文档内容
通过HttpServletResponse回送的http头,可以控制浏览器的行为
//禁止缓存
response.setDateHeader("Expires",-1);
//resp.setDateHeader("Expires",System.currentTimeMillis()+3600*1000);
response.setHeader("Cache-Control","no-cache");
response.setHeader("Pragma","no-cache");
//刷新
resp.setHeader("Refresh","5;url=http://www.baidu.com");
//重定向
response.setHeader("Location","http://localhost:8080/jsp_servlet_project/MyHttpServlet");
//文件下载
resp.setHeader("Content-Disposition","attachment;filename=hourse.jpg");
7.5 setContentType(String)
设置响应给客户端的文件格式
response.setContentType(“text/html;charset=utf-8”)的作用是指定服务器响应给浏览器的编码,
同时,浏览器也是根据这个参数来对其接收到的数据进行重新编码(或者称为解码)。
text/html HTML
text/plain TXT
text/xml XML
application/json json字符串
text/html的意思是将文件的content-type设置为text/html的形式,
浏览器在获取到这种文件时会自动调用html的解析器对文件进行相应的处理
text/plain的意思是将文件设置为纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理
response.setContentType("text/html;charset=utf-8");
response.setContentType("text/plain;charset=utf-8");
response.setContentType("text/xml;charset=utf-8");
response.setContentType("application/json; charset=GBK");
7.6 setCharacterEncoding(String)
response.setCharacterEncoding(“UTF-8”)的作用是指定服务器响应给浏览器的编码。
response.setCharacterEncoding(“UTF-8”)
8 请求转发和重定向
8.1 请求转发forword
一个web资源收到客户端请求后,通知服务器去调用另外一个web资源进行处理,称之为请求转发。
HTTPServletRequest对象(也叫Request对象)提供了一个getRequestDispatcher方法,该方法返回一个RequestDispatcher对象,
调用这个对象的forward方法可以实现请求转发。
request对象同时也是一个域对象,开发人员通过request对象在实现转发时,把数据通过request对象带给其他web资源处理。
request.setAttribute("useranme","韩顺平");
request.getRequestDispatcher("/myHttpServlet").forward(request,resp);
请求转发request对象传数据原理图:
请求转发原理分析:
8.2 请求重定向sendRedirect
一个web资源收到客户端请求后,通知浏览器去访问另外一个web资源,称之为请求重定向。
response.sendRedirect("/UserManager/loginJsp");
8.3 请求转发和请求重定向的区别
总结:
1.使用forward不能转发到该web应用外的url
2.因为forward是发生在web服务器,所有servlet1和servlet2使用的是同一个request和response
3.使用sendRedirect()方法不能通过request.setAttribute()把属性传递给下一个Servlet
什么是一次http请求?
只要没有停止,也没有回到浏览器重定向,就算一次。比如:
如果转发多次,浏览器的地址栏保留的是第一次转发的那个Servlet的URL
sendRedirect()和forward的区别是什么?
1.叫法不一样 sendRedirect叫请求重定向,forward叫请求转发
2.实际发生的位置不一样
sendRedirect发生在浏览器
forword发生在web服务器
3.用法不一样
request.getRequestDispatcher("/myHttpServlet").forward(request,resp);
response.sndRedirect("/web应用/资源URI");
4.能够去URL范围不一样
sendRedirect可以去任何URL,如果要跳转到本web应用外的url,就要用sendRedirect
forward只能去当前的web应用的资源
9 ServletConfig
1.在Servlet的配置文件中,可以使用一个或多个标签为servlet配置一些初始化参数
2.当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,
并在调用Servlet的init方法时,将ServletConfig对象传递给Servlet。
进而,程序员通过ServletConfig对象就可以得到当前Servlet的初始化参数信息。
设置:
<!--这里配置的参数,可以被所有servlet读取-->
<context-param>
<param-name>username</param-name>
<param-value>root</param-value>
</context-param>
<servlet>
<servlet-name>MyServletConfig</servlet-name>
<servlet-class>com.tangguanlin.MyServletConfig</servlet-class>
<!--这里可以给servlet配置信息,这里配置的信息,只能被该servlet读取-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</servlet>
读取:
//获取设置的单个Servlet参数
String encoding = this.getServletConfig().getInitParameter("encoding");
System.out.println(encoding);
//获取设置的全局参数 ServletContext
String username = (String) this.getServletConfig().getServletContext().getInitParameter("username");
System.out.println(username);
代码:
web.xml
<web-app>
<!--这里配置的参数,可以被所有servlet读取-->
<context-param>
<param-name>username</param-name>
<param-value>root</param-value>
</context-param>
<servlet>
<servlet-name>MyServletConfig</servlet-name>
<servlet-class>com.tangguanlin.MyServletConfig</servlet-class>
<!--这里可以给servlet配置信息,这里配置的信息,只能被该servlet读取-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MyServletConfig</servlet-name>
<url-pattern>/MyServletConfig</url-pattern>
</servlet-mapping>
</web-app>
MyServletConfig.java
package com.tangguanlin;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 说明:ServletConfig读取配置属性
* 作者:汤观林
* 日期:2022年02月03日 00时
*/
public class MyServletConfig extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取设置的单个Servlet参数
String encoding = this.getServletConfig().getInitParameter("encoding");
System.out.println(encoding);
//获取设置的全局参数
String username = (String) this.getServletConfig().getServletContext().getInitParameter("username");
System.out.println(username);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
运行结果:
utf-8
root
10 ServletContext
10.1 为什么需要ServletContext
10.2 解决之道ServletContext
什么是ServletContext:
要里面ServletContext就必须和cookie、Session做一个对比,如下图所示:
你可以把它想象成是一个共用的空间,可以被所有的客户访问:
也就是说,A客户可以访问D,B客户也能访问D,C客户也能访问D。
可以这么理解:session是属于单个的用户,但ServletContext每个用户都能访问,是一个公共区域。
ServletContext工作原理:
10.3 this.getServletContext()
获取ServletContext
ServletContext servletContext = this.getServletContext();
10.4 servletConfig.getServletContext()
通过ServletConfig获取ServletContext
ServletConfig servletConfig = this.getServletConfig();
ServletContext servletContext = servletConfig.getServletContext();
10.5 request.getServletContext()
通过HttpServletRequest对象获取ServletContext
ServletContext servletContext = request.getServletContext();
10.6 session.getServletContext()
通过HttpSession获取ServletContext
HttpSession session = request.getSession();
ServletContext servletContext = session.getServletContext();
10.7 setAttribute(name,value)
将数据存入ServletContext中
servletContext.setAttribute("name","韩顺平");
10.8 getAttribute(name)
将数据从ServletContext中取出来
String name = (String)servletContext.getAttribute("name");
10.9 getRealPath(filepath)
获取文件的全路径(tomcat服务器下的全路径)
String realPath = servletContext.getRealPath("/images/hourse.jpg");
10.10 getResourceAsStream(filepath)
获取web目录下下的文件,src目录下的要用类加载器获取
InputStream inputStream = servletContext.getResourceAsStream("/db.properties");
Properties properties = new Properties();
properties.load(inputStream);
String username = properties.getProperty("username");
10.11 getInitParameter(name)
获取web.xml文件中配置的全局参数
web.xml
<!--这里配置的参数,可以被所有servlet读取-->
<context-param>
<param-name>username</param-name>
<param-value>root</param-value>
</context-param>
Java获取:
String value = servletContext.getInitParameter("username");
10.12 ServletContext小结
1.ServletContext是在服务器
2.ServletContext是被所有客户端共享
3.ServletContext是当web应用启动的时候,自动创建
4.ServletContext当web应用关闭/tomcat关闭时,会造成ServletContext销毁
5.web容器在启动时,它就会为每个web应用程序都创建一个对应的ServletContext对象,它代表当前web应用
6.ServletContext对象可以通过ServletConfig.getServletContext方法获得对ServletContext对象的引用,
也可以通过this.getServletContext()来获得其对象的引用
7.由于web应用中的所有servlet共享一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。
ServletContext对象通讯也被称之为Context域对象,公共聊天室就会用到它。web网页版聊天室,不是QQ客户端聊天室。
8.多个Servlet通过ServletContext对象实现数据共享
9.获取web应用的初始化参数,全局的参数
<!--这里配置的参数,可以被所有servlet读取-->
<context-param>
<param-name>username</param-name>
<param-value>root</param-value>
</context-param>
servletContext获取:
String value = servletContext.getInitParameter("username"); //root
10.实现servlet的转发
ServletContext servletContext = this.getServletContext();
//请求转发
//ServletContext的servlet转发和forword原理是一样的
servletContext.getRequestDispatcher("/myHttpServlet").forward(request,response);
11.利用ServletContext对象读取资源文件(properties)
。读取资源文件(读web目录下的文件)
//web目录下才能用servletContext读取,src目录下的文件读不到
InputStream inputStream = servletContext.getResourceAsStream("/db.properties");
Properties properties = new Properties();
properties.load(inputStream);
String username = properties.getProperty("username");
。得到文件路径
String realPath = servletContext.getRealPath("/images/hourse.jpg");
。用类加载器读取src目录下的资源文件
src目录下的文件要用类加载器获取,servletContex读不到
10.13 ServletContext应用
在系统开发中,有很多功能需要使用ServletContext,比如:
1.系统技术器
2.系统在线人数的显示
3.简单的聊天系统
总之,如果是涉及到不同用户共享数据,而这些数据量不大,同时又不希望写入数据库中,
我们就可以考虑使用ServletContext来实现。
系统计数器:
在系统建设中,经常会统计某个页面被浏览的次数,我们把这个功能叫计数器:
那么这个计数器是怎么实现的?怎么样才是一个有效的点击?
1.只要访问过该网页,就算一次,刷新一次也算,当然这是最简单的,不过这有点虚假的成分
2.不同的ip访问该网页,算一次有效点击,如果是同一个ip在一定时间(比如1t天),不管浏览该网页多少次都算一次
3.用户退出网站,再次访问也算一次
代码实现:
登录LoginControllerServlet.java
ServletContext servletContext = this.getServletContext();
String visitCount = (String)servletContext.getAttribute("visitCount");
if(visitCount==null){
visitCount = "1";
servletContext.setAttribute("visitCount",visitCount);
}else{
String newVisitCount = (Integer.parseInt(visitCount)+1)+"";
servletContext.setAttribute("visitCount",newVisitCount);
}
显示MainJspServlet.java
ServletContext servletContext = this.getServletContext();
String visitCount = (String)servletContext.getAttribute("visitCount");
当web服务器关闭后,我们的计数器就被清空了,请大家思考一下,如何能够保证计数器的稳定增长?
思路:
代码实现:
1.web/record.text
0
2.web.xml配置初始化servlet
<servlet>
<servlet-name>initServlet</servlet-name>
<servlet-class>com.tangguanlin.controller.InitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>initServlet</servlet-name>
<url-pattern>/initServlet</url-pattern>
</servlet-mapping>
3.InitServlet.java
tomcat启动时,从record.text文件中读取访问记录数visitCount放入ServletContext中,
tomcat关闭时,从ServletContext取出访问记录数visitCount写入record.text文件中
优点: 不想频繁读读写数据库,不然数据库压力太大,写入项目的web目录下的record.text文件中
package com.tangguanlin.controller;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
/**
* 说明:初始化servlet
* 作者:汤观林
* 日期:2022年02月08日 15时
*/
public class InitServlet extends HttpServlet {
@Override
public void init() throws ServletException {
//从record.text文件中,读取浏览器
//1.首先得到文件的真实路径
String filepath = this.getServletContext().getRealPath("/record.text");
//2.打开文件
try {
BufferedReader bufferedReader = new BufferedReader(new FileReader(filepath));
String line = "";
line = bufferedReader.readLine();
System.out.println("line="+line);
this.getServletContext().setAttribute("visitCount",line);
bufferedReader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
@Override
public void destroy() {
System.out.println("initServlet destroy()被调用......");
//从record.text文件中,读取浏览器
//1.首先得到文件的真实路径
String filepath = this.getServletContext().getRealPath("/record.text");
try {
FileWriter fileWriter = new FileWriter(filepath);
String visitCount = (String)this.getServletContext().getAttribute("visitCount");
fileWriter.write(visitCount);
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
问:如果tomcat异常退出,暴力关闭,比较杀进程,怎么办?
解决办法:可以使用线程,在init方法中,开启一个线程,定时把ServletContext的值,写入到record.text文件中,
比如10min写一次,这样tomcat关闭,对访问数量影响也不大。
10.14 ServletContext注意事项
因为存在ServletContext中的数据会长时间的保存在服务器,会占用内存,
因此我们建议不要向ServletContext中添加过大的数据,
11 Session
11.1 什么是session
session:保存会话数据
不同的用户登录网站后,不管该用户浏览该网站的哪个页面,都可显示登录人的名字。
session是服务端技术,利用这种机制,服务端在运行时可以为每一个用户的浏览器创建一个其独享的session对象,
由于session为用户浏览器独享,所有用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,
当用户再去访问服务器中的其他web资源时,其他web资源再从用户各自的session中取出数据为用户服务。
当用户打开浏览器,访问某个系统操作session时,服务器就会在服务器(tomcat)的内存为该浏览器分一个session对象,
该session对象被这个浏览器独享。
这个session对象也可看做是一个容器,session默认存在时间是30min,你也可以修改。
session工作原理:
使用浏览器访问某一个servlet,其他浏览器可以取到这个servlet存放数据吗?
—答:不能
当发现没有session的时候,就会自动创建session。
//获取session
HttpSession session = request.getSession();
//将数据放入session
session.setAttribute("username","宋江");
11.2 request.getSession()
获取session
HttpSession session = request.getSession();
11.3 setAttribute(name,value)
将数据放入session中
session.setAttribute("username","宋江");
11.4 getAttribute(name)
从session中获取属性的值
String username = (String)session.getAttribute("username");
11.5 removeAttribute(“name”)
将数据从session中删除,让session中的单个session失效
session.removeAttribute("username");
11.6 session.getServletContext()
获取Servlet上下文 ServletContext对象
ServletContext servletContext = session.getServletContext();
11.7 session.setMaxInactiveInterval(60)
发呆时间
session.setMaxInactiveInterval(60);
11.8 session.invalidate()
让session失效,该方法让session中所有属性失效,通常用于安全退出
session.invalidate();
11.9 session.getId()
获取sessionId
String sessionId = session.getId(); //48407A4E7288895936AB3AA1D7EB02A1
11.10 对session的说明
1.session是存放在服务器内存中的
2.一个用户浏览器,独享一个session域对象
电脑1的IE浏览器和电脑2的IE浏览器,是不同的session对象
电脑1的IE浏览器和电脑1的谷歌浏览器,也是不同的session对象
3.session生命周期默认是30min,可以修改
注意:这个的生命周期是指session中所有属性的生命周期,而不是某一个属性的生命周期,session失效了,那就是所有属性都失效了。
(1)tomcat的web.xml文件 -----修改的是 所有的web应用的session生命周期
#tomcat的web.xml文件
<session-config>
<session-timeout>30</session-timeout>
</session-config>
(2)在单个web应用的web.xml中 -----修改的是单个web应用的session生命周期
如果发生冲突,那就以单个web应用的web.xml为准
#单个web应用的web.xml文件
<session-config>
<session-timeout>60</session-timeout>
</session-config>
(3)发呆时间
//发呆时间
session.setMaxInactiveInterval(60);
(4)让session中所有属性失效
session.invalidate();
(5)让session中的单个属性失效
session.removeAttribute("username");
11.11 session小结
session用来做什么
1.保存登录用户的信息
2.防止用户非法登录到某个页面
3.将某些数据放入到session中,供同一用户的各个页面使用
如何理解session:
session不是特别好理解,可以把session看作是一个容器,类似HashMap,有两列。
每一行就是session的一个属性。
每个属性包含两个部分,一个是该属性的名字(String),另外一个是它的值(Object)。
名字 String | 值 Object |
---|---|
username | 韩顺平 |
session小结:
1.session中如果名字相同,会替换
2.session是存在服务器tomcat内存中的
3.session中的属性的默认生命周期是30min,可以通过web.xml来修改
4.session可以存放多个属性
5.session可以存放对象
如何防止用户非法登录到某个页面
用户必须登录后,才能操作管理页面
思路:
当用户登录后,可以把该用户信息存放到session,
然后在需要验证的页面中获取用户信息,如果null,说明用户非法,可以让其重现登录。
防止非法登录:
login.java
HttpSession session = request.getSession();
session.setAttribute("user",user);
每个页面的拦截代码:
HttpSession session = request.getSession();
User user = (User)session.getAttribute("user");
if(user==null){
//跳
request.setAttribute("errorInfo","请输入用户名和密码登录");
request.getRequestDispatcher("/loginJsp").forward(request,response);
//必须加return 不然 要等执行完后面的代码结束后,才会再跳转页面
return;
}
11.12 一个用户浏览器独享一个session
服务器是如何实现一个session为一个用户浏览器服务的?
问题:关掉IE后,再访问ID,上次购买的商品还在,怎么实现?
//方式一 实践中采用这种方式
HttpSession session = request.getSession();
session.setAttribute("name","韩顺平");
Connection: close
Content-Language: en
Content-Length: 2118
Content-Type: text/html;charset=utf-8
Date: Tue, 08 Feb 2022 15:58:37 GMT
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=C4222064039307660A2124A0CDEC401A; Path=/UserManager2/; HttpOnly
上面这段代码,创建了session,同时也会返回Set-Cookie: JSESSIONID=232EAA85BEC6306CF1D4A00C147F1537给前端,但因为没有给cookie设定生命周期,那就相当于就生命周期为0,只存在于浏览器内存中,没有写cookie临时文件。当本次浏览器关闭后,浏览器里面的cookie就失效了。请求后端,因为cookie失效了,没有sessionId传给服务器,所有也找不到session了。
现在的开发实践基本使用这种,关闭浏览器后,需要重现输入用户名和密码登录。
//方式二
HttpSession session = request.getSession();
session.setAttribute("name","韩顺平");
Cookie cookie = new Cookie("JSESSIONID",sessionId);
cookie.setMaxAge(3*60*60);
response.addCookie(cookie);
上面这段代码,服务器创建了session,同时给cookie设定了生命周期3小时,因为有生命周期,cookie会写到浏览器的临时文件。
当浏览器关闭,再打开后,浏览量还是能到cookie临时文件中获取到sessionId,请求服务器,能找回session对象。
12 Cookie
12.1 为什么需要cookie
cookie:保存会话数据
1.什么是会话?
会话可以简单理解为:
用户开一个浏览器访问某个网站,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为会话。
只要不关闭浏览器,不管该用户点击多少链接,访问了多少资源,直到关闭浏览器,都算一次会话。
比喻:打电话
同一个用户,同一时间通过2个浏览器,访问一个网站,是不同的会话。
会话过程中要解决的一些问题:
每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,服务器要想办法为每个用户保存这些数据。
比如:多个用户点击超链接通过一个servlet各自购买了一个商品,服务器应该想办法把每一个用户购买的商品保存在各自的地方,
以便于这些用户点结账Servlet时,结账servlet可以得到用户各自购买的商品为用户结账。
提问:这些数据保存在request行不行?
引子:为什么需要cookie?
1.大家访问某个网站的时候,是否看到提示你上次登录网站的时间,而且要注意,不同用户上次登录的时间肯定是不一样的。
这是怎么实现的?
如何保持用户上次登录时间?
解决方法:
2.大家在访问某个购物网站的时候,是否能看到提示你曾经浏览过的商品,同样也是不同用户浏览过的商品不一样,
这是怎么实现的?
没有登录,也能看到浏览历史,怎么处理?
如何显示用户浏览历史?
3.我们在登录某个网站的时候,往往都可以选择保存登录信息多久,不用重复输入登录信息,这个又是怎么实现的?
如何把登录的用户名和密码保存到电脑,下次登录,不需要重新登录。
总结:如果保存用户的登录信息?
12.2 解决之道cookie
Cookie是客户端技术,服务器把每个用户的数据以cookie的形式写给用户各自的浏览器。
当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。
这样,web资源处理的ius用户各自的数据了。
保存会话数据的计算—cookie
网站可以使用 Cookie 来提升您的浏览体验,例如让您保持登录状态或记住您购物车中的商品
网站可以使用 Cookie 查看您在各个不同网站上的浏览活动,以便实现某些功能或目的(例如为您展示个性化广告)
什么是cookie
服务器在客户端保存用户的信息,比如登录名,密码等,就是cookie。
这些信息就像是小甜饼一样,数据量并不大,服务器端在需要的时候可以从客户端取,保存子啊客户端的浏览器缓存目录下。
cookie原理示意图:
cookie可以用来做什么
1.保存上次登录时间等信息
2.保存用户名、密码,在一定时间不用重新登录
3.记录用户访问网站的喜好(比如有无背景音乐、网页的背景色是什么)
4.网站的个性化,比如定制网站的服务、内容
cookie的基本使用:
1.cookie有点像一张表,分两列,一个是名字,一个是值,数据类型都是String
2.如何创建cookie(在服务端创建的)
Cookie cookie = new Cookie("name","hanshunping");
cookie.setMaxAge(3600); //一天
3.如何将cookie添加到客户端
response.addCookie(cookie);
4.如何读取cookie(从客户端读到服务器)
Cookie[] cookies = request.getCookies();
12.3 写入cookie
代码生成cookie:
//创建cookie(api)
Cookie cookie = new Cookie("name","hanshunping");
//设置cookie有效时间 生命周期 单位是 s 秒
cookie.setMaxAge(3600); //一天
//把cookie信息回写给浏览器
response.addCookie(cookie);
带cookie数据的http响应格式:
Content-Length: 0
Content-Type: text/html;charset=utf-8
Date: Sun, 06 Feb 2022 10:47:22 GMT
Server: Apache-Coyote/1.1
#cookie数据
Set-Cookie: name=hanshunping; Expires=Sun, 06-Feb-2022 11:47:22 GMT
浏览器查看cookie数据:
12.4 读取cookie
含cookie数据的http请求格式:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
#cookie数据
Cookie: name=hanshunping; name2=hanshunping2
Host: localhost:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36
浏览器查看cookie:
代码读取cookie:
//读取cookie
Cookie[] cookies = request.getCookies();
for(Cookie cookie:cookies){
if("name".equals(cookie.getName())){
System.out.println(cookie.getName()+"="+cookie.getValue());
}
}
运行结果:
name=hanshunping
name2=hanshunping2
12.5 new Cookie(name,value)
新建,生成一个cookie
Cookie cookie = new Cookie("name2","hanshunping2");
12.6 cookie.setValue(String)
给当前cookie设新的值,相当于修改cookie的值。
//给当前cookie设置新值
cookie.setValue("hanshunping");
12.7 cookie.setMaxAge()
设置cookie的生命周期,单位是秒
//设置cookie有效时间 生命周期 单位是 s 秒
cookie.setMaxAge(24*60*60); //一天
//删除该cookie
cookie.setMaxAge(0);
12.8 response.addCookie(cookie)
添加cookie到response对象中,cookie才会生效
//把cookie信息回写给浏览器
response.addCookie(cookie);
12.9 request.getCookies()
从浏览器中获取所有的cookie,没有获取指定cookie的方法,只能通过遍历获取指定cookie
//读取cookie
Cookie[] cookies = request.getCookies();
for(Cookie cookie:cookies){
System.out.println(cookie.getName()+"="+cookie.getValue());
}
12.10 cookie.getName()
获取当前cookie的名称
String name =cookie.getName()
12.11 cookie.getValue(String)
获取当前cookie的值
String value = cookie.getValue();
12.12 cookie.getMaxAge()
获取cookie的生命周期
int maxAge = cookie.getMaxAge();
12.13 cookie小结
1.cookie是服务端创建的
2.cookie是保存在浏览器端的
3.cookie的生命周期设定,实践中,一般保存1周
cookie默认生命周期是会话级别(存储在浏览器的内存中),用户退出浏览器之后即被删除。
当然,可以你可以通过setMaxAge()设置cookie的生命周期
特别说明:如果该web应用只有一个cookie,则删除cookie后,在浏览器的临时文件夹下没有该cookie文件,
如果该web应用由多个cookie,则删除一个cookie后,文件还在,只是该cookie没有了。
cookie.setMaxAge(3600); //单位是秒
cookie.setMaxAge(0); //值为0,代表删除该cookie
cookie.setMaxAge(负数); //当值为负数时,该浏览器退出时,就会删除,相当于会话级。
如果不设置setMaAge,则该cookie的生命周期当浏览器关闭时,就消亡
4.cookie可以被多个浏览器共享
5.怎么理解
我们可以把cookie想成一张表
名字 String | 值 String |
---|---|
shunshunping | 123456 |
如果重名了,就会替换存在的cookie值。
6.一个web应用可以保存多个cookie,都存放到一个cookie文件中。一个浏览器也可以保存多个web应用提供的cookie。
一般只允许存放300个cookie,每个站点最多存放20个cookie,每个cookie的大小限制为4KB.
因此cookie不会塞满你的硬盘,更不会被用作“拒绝服务”攻击手段。
7.cookie存放的时候,是以明文方式存放的,因此安全性较低。我们可以通过加密后再保存。
MD5算法,不可逆。
用户密码 MD5加密后存入数据库,(数据库管理也无法知道明文密码,数据库泄漏了也没事),
用户支付时,输入明文密码,通过MD5加密后,和数据库的MD5密码进行对比。
相当于虽然系统保存了你的密码,但没有任何人知道你的明文密码,只有你自己知道明文密码。
8.cookie存放中文,怎么处理
中文值放入cookie:
String value = URLEncoder.encode("韩顺平", "utf-8");
Cookie cookie = new Cookie("name2",value);
从cookie取出中文值:
String value = URLDecoder.decode(cookie.getValue(),"utf-8");
12.14 cookie vs session
1.存在的位置
cookie保存在客户端,session保存在服务器,服务器可以为每个用户浏览器创建一个会话对象(session对象),
注意:一个浏览器独占一个session对象(默认情况下).
因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中。
当用户使用浏览器访问其他模块代码时,其他模块可以从用户的session中取出该用户的数据,为用户服务。
session的数据不宜过多。
2.安全性
比较而言,cookie是以明文方式存放在客户端的,可以通过MD5加密后再存放,
session是存放在服务器端内存中的,安全性好,
所以,cookie的安全性比session要弱。
3.网络传输量
cookie通过网络在客户端与服务器端传输。
而session保存在服务器,不需要传输。
4.生命周期不一样(20分钟为例)
(1)cookie的生命周期是累计的,从创建时,就开始计时,20分钟后cookie生命周期结束,cookie就无效。
(2)session的生命周期是间隔的,从创建时,开始计时如在20分钟,没有访问过session,那么session信息无效,
如果在20分钟内,比如第19分钟时,访问过session,那么,它的生命周期将重新开始计算。
(3)另外,关机会造成session生命周期结束,但是对cookie没有任何影响。
5.从访问范围来看
session为一个用户浏览器独享
cookie为多个用户浏览器共享
6.使用原则:
因为session会占用 服务器的内存,因此,不要向session中存放过多,过大的对象,会影响性能。
13 文件上传下载
13.1 文件下载
文件下载原理:
代码:
package com.tangguanlin;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
/**
* 说明:文件下载
* 作者:汤观林
* 日期:2022年02月03日 18时
*/
public class FileDownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String temp = java.net.URLEncoder.encode("传奇.mp3","utf-8"); //防止中文乱码
resp.setHeader("Content-Disposition","attachment;filename=hourse.jpg");
ServletOutputStream outputStream = resp.getOutputStream();
//1.获取到要下载文件的全路径
String filepath = req.getServletContext().getRealPath("/images/hourse.jpg");
//2.创建文件输入流
FileInputStream fis = new FileInputStream(filepath);
byte[] buff = new byte[1024];
int length = 0;
while((length=fis.read(buff))!=-1){
outputStream.write(buff,0,length);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
运行结果:
http://localhost:8080/jsp_servlet_project/fileDownloadServlet
13.2 文件上传
(省略)
14 中文乱码处理专题
乱码产生的原理:
发生中文乱码有三种情况:
14.1 表单form提交
(1)post提交
<head>
<meta charset="UTF-8">
</head>
<form action='/UserManager/loginController' method='post'>
用户名:<input type='text' name='username' /></br>
密 码:<input type='password' name='password'/></br>
<input type='submit' value='登录' />
</form>
解决方法:
接收时,按页面一样的编码格式utf-8接收即可。
request.setCharacterEncoding("utf-8");
String username = request.getParameter("username");
(2)get提交
http://localhost:8080/UserManager/mainJsp?username=韩顺平
解决办法:
因为数据不是包含在包里面
String username = new String(request.getParameter("username").getBytes("ISO-8859-1"),"utf-8");
14.2 超链接
<a href="http://www.sohu.com?username=韩顺平">测试</a>
解决办法:
超链接本质是get提交,处理和get提交一样
14.3 sendRedirect()
response.sendRedirect("/jsp_servlet_project/MyHttpServlet?name=韩顺平");
解决办法:
重定向本质是get提交,处理和get提交一样
特别说明:如果您的浏览器是IE6或以下版本,则我们的2和3中情况还是会出现乱码
解决方法是:请求时要编码,接收时不变
String info = java.net.URLEncoder.encode("你好吗","utf-8");
说明:我们尽量用post提交
14.4 Response回送乱码
解决方法:指定浏览器显示编码格式
response.setContentType("text/html,charset=utf-8");
14.5 下载文件提示框乱码
当我们下载文件的时候,可能提示框是中文乱码
解决方法:
String temp = java.net.URLEncoder.encode("传奇.mp3","utf-8"); //防止中文乱码
resp.setHeader("Content-Disposition","attachment;filename="+temp);
15 Servlet操作数据库
因为Servlet本身就是一个Java类,因此我们Servlet操作数据库和普通java类是一样的。
1.如何在servlet中显示图片
2.分页技术详解(Oracle分页)
3.用户登录系统功能改进
4.网站架构的改进
发现问题---->处理问题---->解决问题
15.1 分页技术算法
思路:
定义4个变量: 死去活来法
pageNow:第几页 ----该变量是用户决定的,变化的
pageCount : 总页数 ----程序指定 常量设定
pageSize:每页显示几条记录 ----计算出来的
rowCount:总共有多少记录 -----该变量是查询数据库得到的
1.算法1
if(rowCount%pageSize==0){
pageCount = rowCount/pageSize;
}else{
pageCount = rowCount/pageSize+1;
}
比如: users表 9条记录 pageSize = 3 —> pageCount = 3
users表 10条记录 pageSize = 3 —> pageCount = 4
2.算法2
//算法1 等价
pageCount = rowCount%pageSize==0?rowCount/pageSize:rowCount/pageSize+1;
3.算法3
简洁的写法
pageCount = (rowCount-1)/pageSize + 1;
比如: users表 9条记录 pageSize = 3 —> pageCount = 3
users表 11条记录 pageSize = 3 —> pageCount = 4
15.2 页面 manager.jsp
out.println("<table border = 1 width=500px >");
out.println("<tr><th>id</th><th>用户名</th><th>email</th><th>级别</th></tr>");
//5.拿到结果集
if(rs.next()){
//进来,说明该用户合法
out.println("<tr><td>"+rs.getInt(1)+
"</td><td>"+rs.getString(2)+
"</td><td>"+rs.getString(4)+
"</td><td>"+rs.getString(5)+
"</td></tr>");
}
out.println("</table>");
//显示分页
out.println("\n<a href='/UserManager/managerJsp?pageNow="+(pageNow-1)+"'>上一页</a>");
for(int i=1;i<=pageCount;i++){
out.println("<a href='/UserManager/managerJsp?pageNow="+i+"'>第"+i+"页</a>\n");
}
out.println("\n<a href='/UserManager/managerJsp?pageNow="+(pageNow+1)+"'>下一页</a>");
out.println("\n共"+pageCount+"页");
15.3 JDBC
Connection connection = null;
ResultSet rs = null;
PreparedStatement ps = null;
try {
//1.加载驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
//2.得到连接
connection = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:orcl","scott","admin");
//3.创建preparedSatement
ps = connection.prepareStatement("select * from users where id=? and password=?");
//给?赋值
ps.setObject(1,userId);
ps.setObject(2,password);
//4.执行操作
rs = ps.executeQuery();
//5.拿到结果集
if(rs.next()){
//进来,说明该用户合法
//跳转到下一个页面
//resp.sendRedirect("/UserManager/mainJsp?userId="+userId);
request.getRequestDispatcher("/mainJsp").forward(request,response);
}else{
//跳回
//resp.sendRedirect("/UserManager/loginJsp");
request.setAttribute("errorInfo","用户Id或者密码有误");
request.getRequestDispatcher("/loginJsp").forward(request,response);
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭资源
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
15.4 分页sql(oracle)
select t2.* from (select t1.*,rownum rn from users t1) t2 where t2.rn >3 and t2.rn <7;
"select t2.* from (select t1.*,rownum rn from users t1) t2
where t2.rn >=("+pageSize+"*("+pigeNow"+"-1)+1) and t2.rn <="+pageSize+"*"+pageNow;
当我们使用Java web的时候,读取文件要使用类加载器
因为类加载器去读取资源的时候,默认的主目录是src.
properties = new Properties();
InputStream is = SqlHelper.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(is);
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
16 Servlet开发项目
16.1 用户管理系统框架图
简单的用户登录系统(servlet版本1.0)
通过HttpServlet去开发Servlet
现在我们已经初步了解了Servlet的基本原理和机制,现在开始做一个比较综合的案例—用户登录系统
v1.0包含的功能:
1.进行简单的用户验证
2.如何使用Servlet开发动态网页
3.如何从一个页面跳转到另外一个页面
注意重要提示:怎样去确定需要一个新的控制器,
原则:一类事务请求,对应一个控制器,比如用户的增删改查,都写到一个控制器里
1.一个请求对应一个控制器
优点:逻辑清晰
缺点:会造成控制器过多
2.可以这样考虑:一类事务请求,我们做一个控制器,即让该控制器可以处理多个请求
为了让一个控制器去区分不同的请求,我们可以这样做,
在发出请求的同时,再带一个type=add或者 type=update 或者 type =delete 或者type=query
在控制器中我们接收type的值,从而判断用户希望做什么事情。
关于跳转到修改用户界面有两种思路:
(1)传递用户id号的同时,把用户的其他信息一并传递,这样可以减少数据库查询次数
缺点:增加网络开销; 有可能中文乱码; 把对象字段信息暴露在地址栏
优点:减少对数据库的一次操作
(2)只传递用户Id号,控制器接收到id后,再查询数据库,从而显示。
推荐使用这种方式
用户管理系统框架图:
16.2 项目界面
登录界面:
主界面:
用户管理界面:
16.3 项目结构
16.4 实现代码
见代码仓库
https://gitee.com/tangguanlin2006/UserManager2(gitee仓库)
17 过滤器Filter
Servlet + Filter + Listener = Servlet规范的三大组件
17.1 Filter概述
17.1.1 是什么
过滤器:能够完成筛选不需要数据的工具
web中:过滤器其实就是服务端的一个程序(程序最小单元是类)
在web中,过滤器其实就是一个web组件(Servlet/Filter/Listener),其实就是一个抽象类
一个类实现java.servlet.servlet接口 ------>Servlet类
一个类实现java.servlet.Filter接口 ------>Filter类
17.1.2 有什么作用
过滤器的作用:过滤请求和响应
可以在请求到达目标资源之前先对请求进行拦截过滤 ,即对请求进行一些处理,
也可以在响应到达客户端之前先对响应进行拦截过滤,即对响应进行一些处理。
17.1.3 应用场景
。自动登录
。统一编码
。过滤一些敏感词
17.2 入门程序
1.编写一个类
—实现一个Filter接口
—重写所有的方法
package com.tangguanlin;
import javax.servlet.*;
import java.io.IOException;
/**
* 说明:过滤器
* 作者:汤观林
* 日期:2022年02月12日 14时
*/
public class Demo1Filter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//创建Filter
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//拦截处理
System.out.println("Demo1Filter 过滤到请求");
//放行
filterChain.doFilter(servletRequest,servletResponse);
//拦截响应
System.out.println("Demo1Filter 过滤到响应");
}
@Override
public void destroy() {
//销毁Filter
}
}
2.编写配置文件
—注册filter
—绑定路径
web.xml
<!--过滤器-->
<filter>
<filter-name>demo1Filter</filter-name>
<filter-class>com.tangguanlin.Demo1Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>demo1Filter</filter-name>
<url-pattern>/demo1Servlet</url-pattern>
</filter-mapping>
17.3 Filter生命周期
1.Filter在tomcat启动时就创建Filter
2.Filter在tomcat启动时就初始化Filter
3.在每次http请求时,doFilter时被执行
4.Filter在tomcat关闭时销毁
总结:Filter是单实例,
Filter是多线程,每次请求,都会调用doFilter方法
当项目启动的时候,服务器创建Filter对象,调用init方法进行初始化操作,
每当请求来的时候,服务器获取一个线程,执行doFilter方法,实现过滤的逻辑,每请求一次,就会执行一次doFilter方法
当服务器正常关闭的时候,服务器调用destroy方法,实现销毁操作
由于Filter是单例多线程的,为了保证其线程安全性,一般是不会为Filter添加可修改的成员变量
17.4 Filter方法
17.4.1 init()
初始化Filter
17.4.2 doFilter()
过滤器业务处理
17.4.3 destroy()
销毁Filter
17.5 FilterChain过滤链
FilterChain过滤链:多个Filter组合在一起
执行顺序:多个Filter的执行顺序是由web.xml中filter-mapping的位置决定的,
当一个filter收到请求的时候,调用chainFilter才可以访问下一个匹配的Filter,
如果当前的filter是最后一个filter,调用chainFilter.doFilter才能访问目标资源。
当应用中存在多个Filter时,其执行顺序与其注册顺序一致。
将请求放行到下一个过滤器
filterChain.doFilter(servletRequest,servletResponse);
web.xml
<!--人脸识别过滤器2-->
<filter>
<filter-name>demo2Filter</filter-name>
<filter-class>com.tangguanlin.Demo2Filter</filter-class>
</filter>
<!--指纹识别过滤器1-->
<filter>
<filter-name>demo1Filter</filter-name>
<filter-class>com.tangguanlin.Demo1Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>demo2Filter</filter-name>
<url-pattern>/demo1Servlet</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>demo1Filter</filter-name>
<url-pattern>/demo1Servlet</url-pattern>
</filter-mapping>
运行结果:
人脸识别过滤器2 接收到请求
指纹识别过滤器1 过滤到请求
Demo1Servlet 接收到请求
指纹识别过滤器1 过滤到响应
人脸识别过滤器2 接收到响应
17.6 Filter-mapping中的子标签
filter-mapping中可以不使用url-pattern,但需要指定servlet-name,即当前过滤器拦截的是对指定的servlet的请求。
17.6.1 url-pattern
1.完全匹配
以“/”开始, 比如: /demo1/demo2/aa/bb/demo3
2.目录匹配
以“/”开始,以*结束, 比如:/aa/* /*
若Filter为全路径匹配方式,那么url-pattern只能为/*,而不能写为/ 。
/* :表示当前Servlet可以匹配所有的请求,既可以拦截所有的请求,无论发出的是静态资源,还是动态资源的访问,统统会被拦截
3.后缀名匹配
以*开始 比如: *.jsp *.html *.do *.action
17.6.2 servlet-name
指定具体过滤哪个servlet
<filter>
<filter-name>demo2Filter</filter-name>
<filter-class>com.tangguanlin.Demo2Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>demo2Filter</filter-name>
<servlet-name>demo1Servlet</servlet-name>
</filter-mapping>
17.6.3 dispatcher
request: 表示当前过滤器会拦截普通请求,但对于forward()的跳转不进行拦截,默认值
forward: 表示当前过滤器只会拦截由一个servlet通过request.getRequestDispatcher的forward()完成的跳转
<filter-mapping>
<filter-name>demo1Filter</filter-name>
<url-pattern>/demo1Servlet</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
17.7 FilterConfig
FilterConfig指的是Filter在web.xml中的注册信息,初始化参数
<filter>
<filter-name>demo1Filter</filter-name>
<filter-class>com.tangguanlin.Demo1Filter</filter-class>
<init-param>
<param-name>username</param-name>
<param-value>tangguanlin</param-value>
</init-param>
<init-param>
<param-name>db</param-name>
<param-value>oracle</param-value>
</init-param>
</filter>
17.7.1 getFilterName()
获取filter的名称
String filterName = filterConfig.getFilterName();
17.7.2 getServletContext()
获取上下文servletContext对象
ServletContext servletContext = filterConfig.getServletContext();
17.7.3 getInitParameter(name)
获取指定的filter初始化参数
String username = filterConfig.getInitParameter("username");
17.7.4 getInitParameterNames()
获取filter所有初始化名称
Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
while (initParameterNames.hasMoreElements()){
String nextElement = initParameterNames.nextElement();
String value = filterConfig.getInitParameter(nextElement);
System.out.println(nextElement+"="+value);
}
17.8 全局统一错误页面
当获取不到请求的路径对应的资源时,就跳到这个404.jsp页面
web.xml
<error-page>
<error-code>404</error-code>
<location>/404.jsp</location>
</error-page>
404.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>404</title>
</head>
<body>
<h1>这个通过error-page设置的页面</h1>
</body>
</html>
运行结果:
http://localhost:8080/jsp_servlet_project/111 没有111这个路径对应的资源
17.9 应用案例
完成自动登录
17.9.1 需求
当用户第一次登录该网站的时候,如果勾选了‘自动登录’复选框,
那么浏览器关闭后,下次再访问该网站任何资源的时候,都会显示用户在线。
17.9.2 技术分析
cookie(保存用户第一次访问网站的用户名和密码)
session(保证用户在线)
Filter过滤器
17.9.3 流程图
17.9.4 代码实现
(省略)
18 监听器Listener
Servlet规范中已经定义好了8个监听器接口,
它们要监听的对象分别是request,session,ServletContext对象,触发监听器的实际是这三个对象的创建与销毁,
它们的域属性控件中属性的添加,删除,修改,及session的钝化与活化操作。
在Java web项目中使用监听器,需要在web.xml文件中读监听器进行注册。
web.xml 注册监听器
<listener>
<listener-class>com.tangguanlin.listener.Listenerable</listener-class>
</listener>
下面分别对着8个监听器进行学习。
18.1 ServletRequestListener
该监听器用于完成度request对象的创建和销毁的监听。
即当request对象被创建或被销毁时,会触发该监听器中相应方法的执行。
18.1.1 requestInitialized()
请求对象被创建
18.1.2 requestDestroyed()
请求对象被销毁
18.1.3 代码实现
web.xml 注册监听器
<listener>
<listener-class>com.tangguanlin.listener.MyRequestListener</listener-class>
</listener>
监听器类MyRequestListener.java
package com.tangguanlin.listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
/**
* 说明:RequestListener监听器
* 作者:汤观林
* 日期:2022年02月13日 16时
*/
public class MyRequestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("请求对象被创建");
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println("请求对象被销毁");
}
}
运行结果:
http://localhost:8080/jsp_servlet_project/demo1Servlet
请求对象被创建
Demo1Servlet 接收到请求
请求对象被销毁
18.2 ServletRequestAttributeListener
该监听器用于完成对request域属性空间中属性的添加、修改、删除操作的监听。
18.2.1 attributeAdded()
ServletRequest属性被添加
18.2.2 attributeRemoved()
ServletRequest属性被删除
18.2.3 attributeReplaced()
ServletRequest属性被替换
18.2.4 代码实现
web.xml 注册监听器
<listener>
<listener-class>com.tangguanlin.listener.MyRequestAttributeListener</listener-class>
</listener>
MyRequestAttributeListener.java
package com.tangguanlin.listener;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
/**
* 说明:RequestAttributeListener监听器
* 作者:汤观林
* 日期:2022年02月13日 17时
*/
public class MyRequestAttributeListener implements ServletRequestAttributeListener {
@Override
public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent) {
System.out.println("request属性"+servletRequestAttributeEvent.getName()+"被添加"+servletRequestAttributeEvent.getValue());
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent servletRequestAttributeEvent) {
System.out.println("request属性"+servletRequestAttributeEvent.getName()+"被删除"+servletRequestAttributeEvent.getValue());
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent servletRequestAttributeEvent) {
System.out.println("request属性"+servletRequestAttributeEvent.getName()+"被替换");
}
}
HttpRequestServlet1.java
req.setAttribute("username","tangguanlin");
req.setAttribute("username","tangguanlin2006");
req.removeAttribute("username");
运行结果:
http://localhost:8080/jsp_servlet_project/HttpRequestServlet1
request属性username被添加tangguanlin
request属性username被替换
request属性username被删除tangguanlin2006
18.3 HttpSessionListener
该监听器用于完成度session对象的创建和销毁的监听
18.3.1 sessionCreated()
session对象被创建
18.3.2 sessionDestroyed()
session对象被销毁
18.3.3 代码实现
注册监听器
<listener>
<listener-class>com.tangguanlin.listener.MySessionListener</listener-class>
</listener>
MySessionListener.java session监听器
package com.tangguanlin.listener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* 说明:SessionListener监听器
* 作者:汤观林
* 日期:2022年02月13日 17时
*/
public class MySessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println("session被创建");
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("session被销毁");
}
}
HttpRequestServlet1.java
HttpSession session = req.getSession();
session.invalidate();
运行结果:
session被创建
session被销毁
18.4 HttpSessionAttributeListener
该监听器用于完成session域属性空间中属性的添加,修改,删除操作的监听。
当向session域中添加属性时触发
18.4.1 attributeAdded()
添加了session属性
18.4.2 attributeRemoved()
删除了session属性
18.4.3 attributeReplaced()
session的属性被替换了
18.4.4 代码实现
注册监听器
<listener>
<listener-class>com.tangguanlin.listener.MySessionAttributeListener</listener-class>
</listener>
MySessionAttributeListener.java session属性监听器
package com.tangguanlin.listener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
/**
* 说明:HttpSessionAttributeListener监听器
* 作者:汤观林
* 日期:2022年02月13日 17时
*/
public class MySessionAttributeListener implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
System.out.println("向session中添加了属性:"+se.getName()+"="+se.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
System.out.println("向session中删除了属性:"+se.getName()+"="+se.getValue());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
System.out.println("session中属性"+se.getName()+"值被替换");
}
}
HttpRequestServlet1.java
HttpSession session = req.getSession();
session.setAttribute("username","tangguanlin");
session.setAttribute("username","tangguanlin2006");
session.removeAttribute("username");
运行结果:
http://localhost:8080/jsp_servlet_project/HttpRequestServlet1
向session中添加了属性:username=tangguanlin
session中属性username值被替换
向session中删除了属性:username=tangguanlin2006
18.5 ServletContextListener
该监听器用于完成对ServletContext对象的创建和销毁的监听。
不过需要注意,由于ServletContext在一个应用中只有一个,且是在服务器启动时创建。
另外,servletContext的生命周期和整个应用的相同,所以当项目重现部署,或者tomcat正常关闭,
可以销毁servletContext。
18.5.1 contextInitialized()
tomcat服务器启动时调用,创建ServletContext对象
14.5.2 contextDestroyed()
tomcat服务器关闭时调用,销毁ServletContext对象
18.5.3 代码实现
注册监听器
<listener>
<listener-class>com.tangguanlin.listener.MyServletContextListener</listener-class>
</listener>
MyServletContextListener.java 监听器
package com.tangguanlin.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* 说明:ServletContextListener监听器
* 作者:汤观林
* 日期:2022年02月13日 18时
*/
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext对象被创建");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext对象被销毁");
}
}
运行结果:
tomcat启动时:
ServletContext对象被创建
tomcat关闭时:
ServletContext对象被销毁
18.6 ServletContextAttributeListener
该监听器用于完成ServletContext域属性空间中属性的添加,修改,删除操作的监听。
18.6.1 attributeAdded()
已经添加了ServletContext对象属性
18.6.2 attributeRemoved()
已经删除了 ServletContext对象属性
18.6.3 attributeReplaced()
ServletContext对象属性已经被重置
18.6.4 代码实现
注册监听器
<listener>
<listener-class>com.tangguanlin.listener.MyServletContextAttributeListener</listener-class>
</listener>
MyServletContextAttributeListener.java 监听器
package com.tangguanlin.listener;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
/**
* 说明:ServletContextAttributeListener监听器
* 作者:汤观林
* 日期:2022年02月13日 18时
*/
public class MyServletContextAttributeListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent sce) {
System.out.println("向ServletContext属性中添加了属性:"+sce.getName()+"="+sce.getValue());
}
@Override
public void attributeRemoved(ServletContextAttributeEvent sce) {
System.out.println("向ServletContext属性中删除了属性:"+sce.getName()+"="+sce.getValue());
}
@Override
public void attributeReplaced(ServletContextAttributeEvent sce) {
System.out.println("向ServletContext属性中重置了属性:"+sce.getName()+"="+sce.getValue());
}
}
HttpRequestServlet1.java
ServletContext servletContext = this.getServletContext();
servletContext.setAttribute("username","tangguanlin");
servletContext.setAttribute("username","tangguanlin2006");
servletContext.removeAttribute("username");
运行结果:
http://localhost:8080/jsp_servlet_project/HttpRequestServlet1
向ServletContext属性中添加了属性:username=tangguanlin
向ServletContext属性中重置了属性:username=tangguanlin
向ServletContext属性中删除了属性:username=tangguanlin2006
18.7 HttpSessionBindingListener
该监听器用于监听指定类型对象与session的绑定和解绑,即该类型对象被放入到session域中,
或从session域中删除该类型对象,均会引发该监听器中相应方法的执行。
它与HttpSessionAttributeListener的不同之处是,该监听器监听的是指定类型的对象在session域中的操作,
而HttpSessionAttributeListener监听的是session域属性空间的变化,无论是什么类型的对象。
另外,需要强调两点:
。该监听器是由实体类实现
。该监听器无需在web.xml中注册
18.7.1 valueBound()
当当前类的对象绑定到session时(放入到session域中)会触发valueBound方法
18.7.2 valueUnbound()
当当前类的对象与session解绑时(从session域中删除)会触发valueUnbound方法
18.7.3 代码实现
Student.java
package com.tangguanlin.listener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
/**
* 说明:HttpSessionBindingListener监听器
* 实体类实现HttpSessionBindingListener接口
* 该监听器不需要注册
* 作者:汤观林
* 日期:2022年02月13日 19时
*/
public class Student implements HttpSessionBindingListener {
private String name;
private int age;
//当当前类的对象绑定到session时(放入到session域中)会触发该方法
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("student对象放入到session域");
System.out.println(event.getName()+"="+event.getValue().toString());
}
//当当前类的对象与session解绑时(从session域中删除)会触发该方法
@Override
public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("student对象从session域中删除");
}
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
HttpRequestServlet1.java
Student student = new Student("zhangsan", 22);
HttpSession session = req.getSession();
session.setAttribute("student",student);
session.removeAttribute("student");
运行结果:
http://localhost:8080/jsp_servlet_project/HttpRequestServlet1
student对象放入到session域
student=Student{name='zhangsan', age=22}
student对象从session域中删除
18.8 HttpSessionActivationListener
该监听器用于监听在Session中存放的指定类型对象的钝化和活化。
钝化是指将内存中的数据写入到硬盘中,
而活化是指将硬盘中的数据恢复到内存中。
当用户正在访问的应用或该应用所在的服务器由于种种原因被停掉,然后再短时间内又重启,
此时用户在访问时session中的数据是不能丢掉的,在应用关闭之前,需要将数据写入到硬盘,
在重启后应可以立即重新恢复Session中的数据,这就称为session的钝化和活化。
那么session中的哪些数据能够钝化呢?只有存放在JVM堆内存中的实现了Serializable类的
对象能够被钝化,也就是说,对于字符串常量,基本数据类型常量存放在JVM方法区常量池中的常量,
是无法被钝化的。
实体类实现HttpSessionActivationListener接口,同时还要实现Serializable接口
18.8.1 sessionDidActivate()
当当前类的对象被钝化时(内存中的数据写入到硬盘),会触发该方法的执行
18.8.2 sessionWillPassivate()
当当前类的对象被活化时(硬盘中的数据恢复到内存),会触发该方法的执行
18.8.3 代码实现
Student2.java
package com.tangguanlin.listener;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
import java.io.Serializable;
/**
* 说明:HttpSessionActivationListener监听器
* 实体类实现HttpSessionActivationListener接口,同时还要实现Serializable接口
* 该监听器不需要注册
* 作者:汤观林
* 日期:2022年02月13日 19时
*/
public class Student2 implements HttpSessionActivationListener, Serializable {
private String name;
private int age;
//当当前类的对象被钝化时(内存中的数据写入到硬盘),会触发该方法的执行
@Override
public void sessionDidActivate(HttpSessionEvent httpSessionEvent) {
System.out.println("Student2已经活化");
}
//当当前类的对象被活化时(硬盘中的数据恢复到内存),会触发该方法的执行
@Override
public void sessionWillPassivate(HttpSessionEvent httpSessionEvent) {
System.out.println("Student2将要被钝化");
}
public Student2() {
}
public Student2(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
HttpRequestServlet1.java
Student2 student2 = new Student2("zhangsan", 22);
HttpSession session = req.getSession();
session.setAttribute("student2",student2);
运行结果:
tomcat关闭时:
Student2将要被钝化
tomcat启动时:
Student2已经活化