目录
一、JavaWeb概述
Web:全球广域网,也称为万维网(www),能够通过浏览器访问的网站。
JavaWeb: 是用 Java技术来解决相关web互联网领域的技术栈。
静态资源(网页):HTML、CSS、JavaScript、图片等。负责页面展现
动态资源(JavaWeb程序):Servlet、JSP 等。负责逻辑处理
数据库:负责存储数据
HTTP协议:定义通信规则
Web服务器:负责解析HTTP协议,解析请求数据,并发送响应数据
二、HTTP
HyperText Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则。
HTTP 协议特点:
基于TCP协议:面向连接,安全
基于请求-响应模型的:一次请求对应一次响应
HTTP协议是无状态的协议:对于事务处理没有记忆能力。每次请求-响应都是独立的。
缺点:多次请求间不能共享数据。Java中使用会话技术来解决这个问题。
HTTP-请求数据格式
GET请求:
GET / HTTP/1.1
Host: www.itcast.cn
Connection: keep-alive
User-Agent: Mozilla/5.0 Chrome/91.0.4472.106
POST请求:
POST / HTTP/1.1
Host: www.itcast.cn
Connection: keep-alive
Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 Chrome/91.0.4472.106
username=superbaby&password=123456
GET请求和 POST请求区别:
GET请求请求参数在请求行中,没有请求体。POST请求请求参数在请求体中
GET请求请求参数大小有限制,POST没有
请求数据分为3部分:
请求行:第一行。GET表示请求方式,/表示请求资源路径,HTTP/1.1表示协议版本
请求头:第二行开始,格式为key:value形式。
请求体: POST请求的最后一部分,存放请求参数
常见的HTTP 请求头:
Host: 表示请求的主机名
User-Agent: 浏览器版本,例如Chrome浏览器的标识类似Mozilla/5.0 ... Chrome/79。
Accept:表示浏览器能接收的资源类型,如text/*,image/*或者*/*表示所有;
Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
Accept-Encoding:表示浏览器可以支持的压缩类型,例如gzip, deflate等。
HTTP-响应数据格式
HTTP/1.1 200 OK
Server: Tengine
Content-Type: text/html
Transfer-Encoding: chunked…
<html>
<head>
<title></title>
</head>
<body></body>
</html>
响应数据分为3部分:
响应行:第一行。其中HTTP/1.1表示协议版本,200表示响应状态码,OK表示状态码描述
响应头:第二行开始,格式为key:value形式
响应体: 最后一部分。存放响应数据
常见的HTTP 响应头:
Content-Type:表示该响应内容的类型,例如text/html,image/jpeg;
Content-Length:表示该响应内容的长度(字节数);
Content-Encoding:表示该响应压缩算法,例如gzip;
Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒
三、Web服务器
Web服务器是一个应用程序(软件),对 HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是“提供网上信息浏览服务”
Tomcat是Apache 软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/JSP少量JavaEE规范。Tomcat 也被称为 Web容器、Servlet容器。Servlet 需要依赖于 Tomcat才能运行。
下载、安装、卸载、启动、关闭、配置、部署项目。
启动:双击 bin\startup.bat 控制台中文乱码:修改conf/ logging.properties
关闭:双击bin\shutdown.bat 配置环境变量 修改启动端口号 conf/server.xml
端口号冲突:找到对应程序,将其关闭掉
Tomcat部署:
一般 JavaWeb项目会被打成war包,然后将 war包放到 webapps目录下,Tomcat会自动解压缩 war文件,即部署完成。
IDEA中创建 Maven Web项目 (IDEA中使用 Tomcat略)
Maven Web项目结构:开发中的项目 | 部署的JavaWeb项目结构:开发完成打成的war包 |
![]() | ![]() |
编译后的Java字节码文件和resources的资源文件,放到WEB-INF下的classes目录下
pom.xml中依赖坐标对应的jar包,放入WEB-INF下的lib目录下
四、Servlet
Servlet 是 Java提供的一门动态web资源开发技术。
Servlet 是JavaEE 规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并由web服务器运行Servlet。
4.1、Servlet 快速入门
创建 web项目,导入 Servlet依赖坐标
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
创建一个类,实现 Servlet接口,并重写接口中所有方法,并在 service方法中输入一句话
@WebServlet("/demo1")
public class ServletDemo1 implements Servlet {
@override
public void service(SerletRequest req, ServletResponse res){
System.out.print("hellp world")
}
}
配置:在类上使用@WebServlet 注解,配置该 Servlet的访问路径
访问:启动 Tomcat,浏览器输入URL 访问该Servlet http://localhost:8080/web-demo/demo1
4.2、Servlet 执行流程
浏览器输入URL 访问Servlet时:
http://localhost:8080:定位Tomcat服务器
web-demo:定位 web项目
demo1:定位Servlet实现类
Servlet 由谁创建?Servlet方法由谁调用?
Servlet由web服务器创建,Servlet方法由web服务器调用。
服务器怎么知道Servlet中一定有service方法?
因为自定义的Servlet,必须实现Servlet接口并复写其方法,而Servlet接口中有service方法
4.3、Servlet 生命周期
对象的生命周期指一个对象从被创建到被销毁的整个过程
Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:
(1)加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象。也可以在在配置Servlet的访问路径时,通过loadOnStartup参数来修改创建时机,
@WebServlet(urlPatterns = "/demo",loadOnStartup = 1)
public class ServletDemo1 implements Servlet {...}
负整数:第一次被访问时创建Servlet对象
0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高
(2)初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只调用一次
(3)请求处理:每次请求Servlet时,Servlet容器都会调用service()方法对请求进行处理。
(4)服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收
//初始化方法,在Servlet被创建时执行,只执行一次
void init(ServletConfig config)
//提供服务方法, 每次Servlet被访问,都会调用该方法
void service(ServletRequest req, ServletResponse res)
//销毁方法,当Servlet被销毁时,调用该方法。在内存释放或服务器关闭时销毁Servlet
void destroy()
//获取ServletConfig对象
ServletConfig getServletConfig()
//获取Servlet信息
String getServletInfo()
4.4、Servle 体系结构
(1)Servlet体系根接口 (2)Servlet抽象实现类 (3)对HTTP协议封装的Servlet实现类
开发B/S架构的web项目,都是针对HTTP协议,所以我们自定义Servlet,会继承HttpServlet
![]() | ![]() |
HTTP 协议中,GET 和 POST 请求方式的数据格式不一样,将来要想在Servlet中处理请求参数,得在service方法中判断请求方式,并且根据请求方式的不同,分别进行处理:
4.5、Servlet 访问路径配置
一个Servlet,可以配置多个 urlPattern
@WebServlet(urlPatterns = {"/demo1", "/demo2"})
urlPattern 配置规则:
① 精确匹配(优先级最高)
配置路径 | 访问路径 |
@WebServlet("/user/select") | localhost:8080/web-demo/user/select |
② 目录匹配
配置路径 | 访问路径 |
@WebServlet("/user/*") | localhost:8080/web-demo/user/aaa localhost:8080/web-demo/user/aaa |
③ 扩展名匹配
配置路径 | 访问路径 |
@WebServlet("*.do") | localhost:8080/web-demo/aaa.do localhost:8080/web-demo/aaa.do |
④ 任意匹配
配置路径 | 访问路径 |
@WebServlet("/") @WebServlet("/*") | localhost:8080/web-demo/任意字符 |
优先级:精确路径 > 目录路径 > 扩展名路径 > /* > /
/ 和 /* 区别:
当我们的项目中的Servlet配置了“/”,会覆盖掉tomcat中的DefaultServlet,当其他的 url-pattern都匹配不上时都会走这个Servlet
当我们的项目中配置了“/*”,意味着匹配任意访问路径
4.6、XML 方式配置访问路径
Servlet 从3.0版本后开始支持使用注解配置,3.0版本前只支持 XML 配置文件的配置方式
步骤(了解):
1. 编写 Servlet类
2. 在 web.xml中配置该Servlet
五、Request&Response
5.1、Req&Res概述
Request是请求对象,Response是响应对象。这两个对象在我们使用Servlet的时候有看到:
request:获取请求数据
浏览器会发送HTTP请求到后台服务器[Tomcat]
HTTP的请求中会包含很多请求数据[请求行+请求头+请求体]
后台服务器[Tomcat]会对HTTP请求中的数据进行解析并把解析结果存入到一个对象中
所存入的对象即为request对象,所以我们可以从request对象中获取请求的相关参数
获取到数据后就可以继续后续的业务,比如获取用户名和密码就等业务操作
response:设置响应数据
业务处理完后,后台就需要给前端返回业务处理的结果即响应数据
把响应数据封装到response对象中
后台服务器[Tomcat]会解析response对象,按照[响应行+响应头+响应体]格式拼接结果
浏览器最终解析结果,把内容展示在浏览器给用户浏览
代码示例:
@WebServlet("/demo3")
public class ServletDemo3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//使用request对象 获取请求数据
String name = request.getParameter("name");//url?name=zhangsan
//使用response对象 设置响应数据
response.setHeader("content-type","text/html;charset=utf-8");
response.getWriter().write("<h1>"+name+",欢迎您!</h1>");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("Post...");
}
}
5.2、Request 继承体系
ServletRequest和HttpServletRequest是继承关系,并且两个都是接口,接口是无法创建对象
那么,Servlet中的service方法参数中的对象是谁创建的的呢?这个时候,我们就需要用到Request继承体系中的RequestFacade :
该类实现了HttpServletRequest接口,也间接实现了ServletRequest接口。
Servlet类中的service方法、doGet方法或者是doPost方法最终都是由Web服务器[Tomcat]
来调用的,所以Tomcat提供了方法参数接口的具体实现类,并完成了对象的创建。
要想了解RequestFacade中都提供了哪些方法,我们可以直接查看JavaEE的API文档中关于
ServletRequest和HttpServletRequest的接口文档,因为RequestFacade实现了其接口就需要重写接口中的方法。
@WebServlet("/demo2")
public class ServletDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println(request);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
启动服务器,运行访问http://localhost:8080/request-demo/demo2 ,得到运行结果:
5.3、Request 获取请求数据
5.3.1、获取请求行数据
请求行包含三块内容,分别是请求方式、请求资源路径、HTTP协议及版本
1.获取请求方式: GET
String getMethod()
2.获取虚拟目录(项目访问路径): /request-demo
String getContextPath()
3.获取URL(统一资源定位符): http://localhost:8080/request-demo/req1
StringBuffer getRequestURL()
4.获取URI(统一资源标识符): /request-demo/req1
String getRequestURI()
5.获取请求参数(GET方式): username=zhangsan&password=123
String getQueryString()
/*** request 获取请求数据 */
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// String getMethod():获取请求方式
String method = req.getMethod();
System.out.println(method);//GET
// String getContextPath():获取虚拟目录(项目访问路径)
String contextPath = req.getContextPath();
System.out.println(contextPath); // /request-demo
// StringBuffer getRequestURL(): 获取URL(统一资源定位符)
StringBuffer url = req.getRequestURL();
System.out.println(url.toString()); // //http://localhost:8080/request-demo/req1
// String getRequestURI():获取URI(统一资源标识符)
String uri = req.getRequestURI();
System.out.println(uri); // /request-demo/req1
// String getQueryString():获取请求参数(GET方式)
String queryString = req.getQueryString();
System.out.println(queryString); // username=zhangsan
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}
5.3.2、获取数据头数据
对于请求头的数据,格式为key: value如下:
1.据请求头名称获取对应值:
String getHeader(String name)
/*** request 获取请求数据 */
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//获取请求头: user-agent: 浏览器的版本信息
String agent = req.getHeader("user-agent");
System.out.println(agent);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}
5.3.3、获取请求体数据

1.获取字节输入流,如果前端发送的是字节数据
ServletInputStream getInputStream() //该方法可以获取字节
2.获取字符输入流,如果前端发送的是纯文本数据
BufferedReader getReader()
5.3.4、获取请求参数示例
什么是请求数据?
请求数据则是包含请求行、请求头和请求体的所有数据
请求参数和请求数据的关系是什么?
1 请求参数是请求数据中的部分内容
2 如果是GET请求,请求参数在请求行中
3 如果是POST请求,请求参数一般在请求体中
对于请求参数的获取,常用的有以下两种:
GET方式:
String getQueryString()
POST方式:
BufferedReader getReader();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/request-demo/req2" method="get">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="checkbox" name="hobby" value="1"> 游泳 <br>
<input type="checkbox" name="hobby" value="2"> 爬山 <br>
<input type="submit">
</form>
</body>
</html>
(1)发送一个GET请求并携带用户名,后台接收后打印到控制台
(2)发送一个POST请求并携带用户名,后台接收后打印到控制台
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String result = req.getQueryString();
System.out.println(result);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
BufferedReader br = req.getReader();
String result = br.readLine();
System.out.println(result);
}
}
出现问题,如下图描述:
如何解决上述重复代码的问题呢?
在doPost中调用doGet当然,也可以在doGet中调用doPost,在doPost中完成参数的获取和打印,另外需要注意的是,doGet和doPost方法都必须存在,不能删除任意一个。
GET请求方式和POST请求方式区别主要在于获取请求参数的方式不一样,需要提供提供一种统一获取请求参数的方式,从而统一doGet和doPost方法内的代码。
方案一:
使用request的getMethod()来获取请求方式,根据请求方式的不同分别获取请求参数值,这样就可以解决上述问题,代码如下:(但是以后每个Servlet都需要这样写代码,实现起来比较麻烦)
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//获取请求方式
String method = req.getMethod();
//获取请求参数
String params = "";
if("GET".equals(method)){
params = req.getQueryString();
}else if("POST".equals(method)){
BufferedReader reader = req.getReader();
params = reader.readLine();
}
//将请求参数进行打印控制台
System.out.println(params)
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
方案二:
request对象已经将上述获取请求参数的方法进行了封装,并且request提供的方法实现的功能更强大,以后只需要调用request提供的如下方法:
1.获取所有参数Map集合
Map<String,String[]> getParameterMap()
2.根据名称获取参数值(数组)
String[] getParameterValues(String name)
3.根据名称获取参数值(单个值)
String getParameter(String name)
/*** request 通用方式获取请求参数 */
@WebServlet("/req2")
public class RequestDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//GET请求逻辑
System.out.println("get....");
//1. 获取所有参数的Map集合
Map<String, String[]> map = req.getParameterMap();
for (String key : map.keySet()) {
// username:zhangsan lisi
System.out.print(key+":");
//获取值
String[] values = map.get(key);
for (String value : values) {
System.out.print(value + " ");
}
System.out.println();
}
System.out.println("获取GET请求参数中的爱好");
String[] hobbies = req.getParameterValues("hobby");
for (String hobby : hobbies) {
System.out.println(hobby);
}
System.out.println("获取GET请求参数中的用户名和密码");
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println(username);
System.out.println(password);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
}
5.4、请求参数中的乱码问题
(1)浏览器通过HTTP协议发送请求和数据给后台服务器(Tomcat)
(2)浏览器在发送HTTP的过程中会对中文数据进行URL编码
(3)在进行URL编码的时候会采用页面<meta>标签指定的UTF-8的方式进行编码,张三编码后的结果为%E5%BC%A0%E4%B8%89
(4)Tomcat接收到%E5%BC%A0%E4%B8%89后会默认按照ISO-8859-1进行URL解码
(5)由于前后编码与解码采用的格式不一样,就会导致后台获取到的数据为乱码。
POST请求获取数据乱码:
乱码原因:
POST的请求参数是通过request的getReader()来获取流中的数据
TOMCAT在获取流的时候采用的编码是ISO-8859-1
ISO-8859-1编码是不支持中文的,所以会出现乱码
解决方法:
页面设置的编码格式为UTF-8,html中 <meta charset="UTF-8">
把TOMCAT在获取流数据之前的编码设置为UTF-8
通过request.setCharacterEncoding("UTF-8")设置编码,UTF-8可小写
GET请求获取数据乱码:
GET请求获取请求参数的方式是request.getQueryString();getQueryString方法并没有通过流的方式获取数据;所以GET请求不能用设置编码的方式来解决中文乱码问题。
乱码原因:
浏览器把中文参数按照UTF-8进行URL编码
Tomcat对获取到的内容进行了ISO-8859-1的URL解码
在控制台就会出现类上å¼ ä。
解决方法:
按照ISO-8859-1编码获取乱码å¼ ä
按照UTF-8编码获取字节数组对应的字符串
/*** 中文乱码问题解决方案
Tomcat8.0之后,已将GET请求乱码问题解决,设置默认的解码方式为UTF-8*/
@WebServlet("/req4")
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//1. 解决乱码:POST,getReader()
//request.setCharacterEncoding("UTF-8");设置字符输入流的编码
//2. 获取username
String username = request.getParameter("username");
System.out.println("解决乱码前:"+username);
//3. GET,获取参数的方式:getQueryString
// 乱码原因:tomcat进行URL解码,默认的字符集ISO-8859-1
//3.1 先对乱码数据进行编码:转为字节数组
byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1);
//3.2 字节数组解码
username = new String(bytes, StandardCharsets.UTF_8);
System.out.println("解决乱码后:"+username);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
5.5、Request请求转发
请求转发(forward):一种在服务器内部的资源跳转方式。
(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求
(2)资源A处理完请求后将请求发给资源B
(3)资源B处理完后将结果响应给浏览器
(4)请求从资源A到资源B的过程就叫请求转发
请求转发实现方式:
req.getRequestDispatcher("资源B路径").forward(req,resp);
1.创建一个RequestDemo5类,接收/req5的请求,在doGet方法中打印demo5
2.创建一个RequestDemo6类,接收/req6的请求,在doGet方法中打印demo6
3.在RequestDemo5中使用req.getRequestDispatcher("/req6").forward(req,resp)进行请求转发
4.启动测试
@WebServlet("/req5")
public class RequestDemo5 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("demo5...");
//请求转发
request.getRequestDispatcher("/req6").forward(request,response);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
@WebServlet("/req6")
public class RequestDemo6 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("demo5...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
访问http://localhost:8080/request-demo/req5 ,就可以在控制台看到如下内容
demo5...
demo6...
需要使用request对象提供的三个方法:
1.存储数据到request域[范围,数据是存储在request对象]中
void setAttribute(String name,Object o);
2.根据key获取值
Object getAttribute(String name);
3.根据key删除该键值对
void removeAttribute(String name);
2.创建一个RequestDemo6类,接收/req6的请求,在doGet方法中打印demo6
@WebServlet("/req5")
public class RequestDemo5 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("demo5...");
//存储数据
request.setAttribute("msg","hello");
//请求转发
request.getRequestDispatcher("/req6").forward(request,response);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
@WebServlet("/req6")
public class RequestDemo6 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("demo5...");
//获取数据
Object msg = request.getAttribute("msg");
System.out.println(msg);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
访问http://localhost:8080/request-demo/req5 ,就可以在控制台看到如下内容
demo5...
demo6...
hello
请求转发特点:
浏览器地址栏路径不发生变化
只能转发到当前服务器的内部资源
只有一次请求,可以在转发资源间使用request共享数据
5.6、Response
5.6.1、Response 继承体系
5.6.2、设置响应数据功能
HTTP响应数据总共分为三部分内容,分别是响应行、响应头、响应体
1.设置响应行响应状态码:
void setStatus(int sc);
2.设置响应头键值对:
void setHeader(String name,String value);
3.获取响应体字符输出流:
PrintWriter getWriter();
4.获取字节输出流
ServletOutputStream getOutputStream();
5.6.3、请求重定向
Response重定向(redirect):一种资源跳转方式。
(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求
(2)资源A现在无法处理该请求,就会给浏览器响应一个302的状态码+location的一个资源B的路径
(3)浏览器接收到响应状态码为302就会重新发送请求到location对应的访问地址去访问资源B
(4)资源B接收到请求后进行处理并最终给浏览器响应结果,这整个过程就叫重定向
重定向实现方式:
resp.setStatus(302);
resp.setHeader("location","资源B的访问路径");
简化:
resposne.sendRedirect("/request-demo/resp2")
1.创建一个ResponseDemo1类,接收/resp1的请求,在doGet方法中打印resp1....
2.创建一个ResponseDemo2类,接收/resp2的请求,在doGet方法中打印resp2....
3.在ResponseDemo1的方法中使用setStatus、setHeader方法来给前端响应结果数据
4.启动测试
@WebServlet("/resp1")
public class ResponseDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("resp1....");
//重定向
//1.设置响应状态码 302
response.setStatus(302);
//2. 设置响应头
Location response.setHeader("Location","/request-demo/resp2");
//或:
resposne.sendRedirect("/request-demo/resp2")
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
@WebServlet("/resp2")
public class ResponseDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("resp2....");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
访问http://localhost:8080/request-demo/resp1,就可以在控制台看到如下内容
resp1...
resp2...
问题一,路径问题:
转发的时候路径上没有加/request-demo而重定向加了,什么时候要加?
浏览器使用:需要加虚拟目录(项目访问路径)
服务端使用:不需要加虚拟目录
是否需要添加虚拟目录
<a href='路劲'>
<form action='路径'>
req.getRequestDispatcher("路径")
resp.sendRedirect("路径")
1.超链接,从浏览器发送,需要加
2.表单,从浏览器发送,需要加
3.转发,是从服务器内部跳转,不需要加
4.重定向,是由浏览器进行跳转,需要加。
问题二,固定编码问题:
在重定向的代码中,/request-demo是固定编码的,如果后期通过Tomcat插件配置了项目的访问路径,那么所有需要重定向的地方都需要重新修改,该如何优化?
可以在代码中动态去获取项目访问的虚拟目录,具体如何获取,我们可以借助前面咱们所学习的request对象中的getContextPath()方法
@WebServlet("/resp1")
public class ResponseDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("resp1....");
//简化方式完成重定向
//动态获取虚拟目录
String contextPath = request.getContextPath();
response.sendRedirect(contextPath+"/resp2");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
5.6.4、响应字符数据
要想将字符数据写回到浏览器,我们需要两个步骤:
通过Response对象获取字符输出流: PrintWriter writer = resp.getWriter();
通过字符输出流写数据: writer.write("aaa");
@WebServlet("/resp3")
public class ResponseDemo3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//1. 获取字符输出流
PrintWriter writer = response.getWriter();
writer.write("aaa");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
//2. 返回一串html字符串,并且能被浏览器解析
PrintWriter writer = response.getWriter();
//content-type,告诉浏览器返回的数据类型是HTML类型数据,
//这样浏览器才会解析HTML标签 response.setHeader("content-type","text/html");
writer.write("<h1>aaa</h1>");
//3. 返回一个中文的字符串你好,需要注意设置响应数据的编码为utf-8
//设置响应的数据格式及数据的编码
response.setContentType("text/html;charset=utf-8");
writer.write("你好");
5.6.5、响应字节数据
要想将字节数据写回到浏览器,我们需要两个步骤:
通过Response对象获取字节输出流:ServletOutputStream outputStream =
resp.getOutputStream();
通过字节输出流写数据: outputStream.write(字节数据);
@WebServlet("/resp4")
public class ResponseDemo4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//1. 读取文件
FileInputStream fis = new FileInputStream("d://a.jpg");
//2. 获取response字节输出流
ServletOutputStream os = response.getOutputStream();
//3. 完成流的copy
byte[] buff = new byte[1024];
int len = 0;
while ((len = fis.read(buff))!= -1){
os.write(buff,0,len);
}
fis.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
六、JSP技术
6.1、JSP概述
JSP(全称:Java Server Pages):Java 服务端页面。是一种动态的网页技术,其中既可以定义 HTML、JS、CSS等静态内容,还可以定义 Java代码的动态内容。
JSP = HTML + Java,用于简化开发;JSP本质上就是一个Servlet。
假如不使用JSP,就需要在Servlet中大量使用到 writer 对象向页面写标签内容:
1. 浏览器第一次访问 hello.jsp 页面
2. tomcat 会将 hello.jsp 转换为名为 hello_jsp.java 的一个 Servlet
3. tomcat 再将转换的 servlet 编译成字节码文件 hello_jsp.class
4. tomcat 会执行该字节码文件,向外提供服务
项目所在磁盘目录下找 target\tomcat\work\Tomcat\localhost\jsp-demo\org\apache\jsp 目录,而这个目录下就能看到转换后的 servlet,打开 hello_jsp.java 文件,来查看里面的代码:
由上面的类的继承关系可以看到继承了名为 HttpJspBase 这个类,那我们在看该类的继承关系。到资料中的找如下目录:\tomcat源码\apache-tomcat-8.5.68-src\java\org\apache\jasper\runtime ,该目录下就有 HttpJspBase 类,查看该类的继承关系:
继续阅读 hello_jsp 类的代码,可以看到有一个名为 _jspService() 的方法,该方法就是每次访问 jsp 时自动执行的方法,和 servlet 中的 service 方法一样,而在 _jspService() 方法中可以看到往浏览器大量使用到 writer 对象向页面写标签的代码;以前我们自己写 servlet 时,这部分代码是由我们自己来写,现在有了 jsp 后,由tomcat完成这部分功能。
6.2、JSP快速入门
(1)导入JSP坐标:
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
(2)创建JSP文件:在项目的 webapp 下创建一个名为 hello.jsp 的页面。
(3)编写HTML和Java代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>hello jsp</h1>
<%
System.out.println("hello,jsp~");
%>
</body>
</html>
启动服务器并在浏览器地址栏输入 http://localhost:8080/jsp-demo/hello.jsp ,我们可以在页面上看到如下内容:
hello jsp
6.3、JSP脚本
JSP 脚本有如下三个分类:
<%...%>:内容会直接放到_jspService()方法之中
<%=…%>:内容会放到out.print()中,作为out.print()的参数
<%!…%>:内容会放到_jspService()方法之外,被类直接包含
<%
System.out.println("hello,jsp~");
int i = 3;
%>
//通过浏览器访问 hello.jsp 后,查看转换的 hello_jsp.java 文件,
//i 变量定义在了 _jspService() 方法中
System.out.println("hello,jsp~");
int i = 3;
<%="hello"%>
<%=i%>
//通过浏览器访问 hello.jsp 后,查看转换的 hello_jsp.java 文件,
//该脚本的内容被放在了 out.print() 中,作为参数
out.print("hello");
out.write("\r\n")
out.write(" ")
out.print(i);
<%!
void show(){}
String name = "zhangsan";
%>
//该脚本的内容被放在了成员位置,如下图
6.4、EL表达式
EL(全称Expression Language )表达式语言,用于简化 JSP 页面内的 Java 代码。
EL 表达式的主要作用是获取数据。其实就是从域对象中获取数据,然后将数据展示在页面上。
EL 表达式的语法:${expression} 。例如:${brands} 就是获取域中存储的 key 为 brands 的数据。
定义servlet,在 servlet 中封装一些数据并存储到 request 域对象中并转发到 el-demo.jsp 页面。
@WebServlet("/Demo1")
public class ServletDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//1. 准备数据
List<Brand> brands = new ArrayList<Brand>();
brands.add(new Brand(1,"三只松鼠","三只松鼠",100,"三只松鼠,好吃不上火",1));
brands.add(new Brand(2,"优衣库","优衣库",200,"优衣库,服适人生",0));
brands.add(new Brand(3,"小米","小米科技有限公司",1000,"为发烧而生",1));
//2. 存储到request域中
request.setAttribute("brands",brands);
//3. 转发到 el-demo.jsp
request.getRequestDispatcher("/el-demo.jsp").forward(request,response);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
注意: 此处需要用转发,因为转发才可以使用 request 对象作为域对象进行数据共享
在 el-demo.jsp 中通过 EL表达式 获取数据
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${brands}
</body>
</html>
在浏览器的地址栏输入 http://localhost:8080/jsp-demo/demo1 ,页面效果如下:
JavaWeb中有四大域对象,分别是(范围依次增大):
page:当前页面有效
request:当前请求有效
session:当前会话有效
application:当前应用有效
el 表达式获取数据会依次从这4个域中寻找。例如: ${brands},el 表达式获取数据,会先从page域对象中获取数据,如果没有再到 requet 域对象中获取数据,如果再没有再到 session 域对象中获取,如果还没有才会到 application 中获取数据。
6.5、JSTL标签

<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
(2)在JSP页面上引入JSTL标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 1
(3)使用标签
//<c:if> :相当于 if 判断
//属性:test,用于定义条件表达式
<c:if test="${flag == 1}">
男
</c:if>
<c:if test="${flag == 2}">
女
</c:if>
定义一个 servlet ,在该 servlet 中向 request 域对象中添加 键是 status ,值为 1 的数据
@WebServlet("/Demo2")
public class ServletDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//1. 存储数据到request域中
request.setAttribute("status",1);
//2. 转发到 jstl-if.jsp 数据
request.getRequestDispatcher("/jstl-if.jsp").forward(request,response);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}
定义 jstl-if.jsp 页面,在该页面使用 <c:if> 标签
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--c:if:来完成逻辑判断,替换java if else --%>
<c:if test="${status ==1}">
启用
</c:if>
<c:if test="${status ==0}">
禁用
</c:if>
</body>
</html>
items:被遍历的容器
var:遍历产生的临时变量
varStatus:遍历状态对象
如下代码,从域对象中获取名为 brands 数据,该数据是一个集合;遍历遍历,并给该集合中的每一个元素起名为brand ,是 Brand对象。在循环里面使用 EL表达式获取每一个Brand对象的属性值
<c:forEach items="${brands}" var="brand">
<tr align="center">
<td>${brand.id}</td>
<td>${brand.brandName}</td>
<td>${brand.companyName}</td>
<td>${brand.description}</td>
</tr>
</c:forEach>
begin:开始数
end:结束数
step:步长
<c:forEach begin="0" end="10" step="1" var="i">
${i}
</c:forEach>
七、会话技术
7.1、会话跟踪技术概述
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
从浏览器发出请求到服务端响应数据给前端后,一次会话(浏览器和服务器之间)就被建立了
会话被建立后,如果浏览器或服务端都没有被关闭,则会话就会持续建立着
浏览器和服务器就可以继续使用该会话进行请求发送和响应,上述整个过程就被称之为会话
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
服务器会收到多个请求,这多个请求可能来自多个浏览器,
服务器需要用来识别请求是否来自同一个浏览器
服务器用来识别浏览器的过程,这个过程就是会话跟踪
服务器识别浏览器后就可以在同一个会话中多次请求之间来共享数据
一个会话中的多次请求为什么要共享数据呢?有了这个数据共享 功能后能实现哪些功能呢?
例如,淘宝购物车: 加入购物车和去购物车结算是两次请求,但是后面这次请求要想展示前一次请求所添加 的商品,就需要用到数据共享。
为什么现在浏览器和服务器不支持数据共享呢?
浏览器和服务器之间使用的是HTTP请求来进行数据传输
HTTP协议是无状态的,每次浏览器向服务器请求时,服务器都会将该请求视为新的请求
HTTP协议设计成无状态的目的是让每次请求之间相互独立,互不影响
请求与请求之间独立后,就无法实现多次请求之间的数据共享
分析完具体的原因后,那么该如何实现会话跟踪技术呢? 具体的实现方式有:
(1)客户端会话跟踪技术:Cookie,存储在浏览器端
(2)服务端会话跟踪技术:Session,存储在服务器端
7.2、Cookie
7.2.1、Cookie概述
Cookie:客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问
服务端提供了两个Servlet,分别是ServletA和ServletB
浏览器发送HTTP请求1给服务端,服务端ServletA接收请求并进行业务处理
服务端ServletA在处理的过程中可以创建一个Cookie对象并将name=zs的数据存入Cookie
服务端ServletA在响应数据的时候,会把Cookie对象响应给浏览器
浏览器接收到响应数据,会把Cookie对象中的数据存储在浏览器内存中,此时浏览器和服务端就建立了一次会话
在同一次会话中浏览器再次发送HTTP请求2给服务端ServletB,浏览器会携带Cookie对象中的所有数据
ServletB接收到请求和数据后,就可以获取到存储在Cookie对象中的数据,这样同一个会话中
的多次请求之间就实现了数据共享
7.2.2、Cookie基本使用
对于Cookie的操作主要分两大类,本别是发送Cookie和获取Cookie
1.创建Cookie对象,并设置数据
Cookie cookie = new Cookie("key","value");
2.发送Cookie到客户端:使用response对象
response.addCookie(cookie);
3.获取客户端携带的所有Cookie,使用request对象
Cookie[] cookies = request.getCookies();
4.遍历数组,获取每一个Cookie对象:for;
cookie.getName();
5.使用Cookie对象方法获取数据
cookie.getValue();
示例1:在Servlet中生成Cookie对象并存入数据,然后将数据发送给浏览器
1.创建Maven项目,项目名称为cookie-demo,并在pom.xml添加依赖(略)
2.编写Servlet类,名称为AServlet
3.在AServlet中创建Cookie对象,存入数据,发送给前端
4.启动测试,在浏览器查看Cookie对象中的值
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//发送Cookie
//1. 创建Cookie对象
Cookie cookie = new Cookie("username","zs");
//2. 发送Cookie,
response response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
访问http://localhost:8080/cookie-demo/aServlet chrome浏览器查看Cookie的值
(4)启动测试,在浏览器查看Cookie对象中的值。
方式一:
设置--->隐私设置和安全性--->Cookie及其它网站数据--->查看所有Cookie和网站数据
方式二:
选中打开开发者工具或者 使用快捷键F12 或者 Ctrl+Shift+I;Application--->Cookies
示例2:在Servlet中获取前一个案例存入在Cookie对象中的数据
@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取Cookie
//1. 获取Cookie数组
Cookie[] cookies = request.getCookies();
//2. 遍历数组
for (Cookie cookie : cookies) {
//3. 获取数据
String name = cookie.getName();
if("username".equals(name)){
String value = cookie.getValue();
System.out.println(name+":"+value);
break;
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
访问http://localhost:8080/cookie-demo/bServlet 在控制台打印出获取的值
username : zs
7.2.3、Cookie原理
对于Cookie的实现原理是基于HTTP协议的,其中设计到HTTP协议中的两个请求头信息:
AServlet给前端发送Cookie,BServlet从request中获取Cookie
对于AServlet响应数据的时候,Tomcat服务器都是基于HTTP协议来响应数据
当Tomcat发现后端要返回的是一个Cookie对象之后,Tomcat就会在响应头中添加一行数据Set- Cookie:username=zs
浏览器获取到响应结果后,从响应头中就可以获取到Set-Cookie对应值username=zs ,并将数据存储在浏览器的内存中
浏览器再次发送请求给BServlet的时候,浏览器会自动在请求头中添加Cookie: username=zs
发送给服务端BServlet
Request对象会把请求头中cookie对应的值封装成一个个Cookie对象,最终形成一个数组
BServlet通过Request对象获取到Cookie[]后,就可以从中获取自己需要的数据
7.2.4、Cookie使用细节
Cookie存活时间:
默认情况下,Cookie存储在浏览器内存中,当浏览器关闭,内存释放,则Cookie被销毁
如果使用这种默认情况下的Cookie,有些需求就无法实现,比如:
网站记住用户名和密码的功能,第一次输入用户名和密码并勾选记住我然后进行登录,下次再登陆的时候,用户名和密码就会被自动填充,不需要再重新输入登录,那么如何将Cookie持久化存储?
Cookie其实已经为我们提供好了对应的API来完成这件事,这个API就是setMaxAge
设置Cookie存活时间
setMaxAge(int seconds)
参数值为:
1.正数:将Cookie写入浏览器所在电脑的硬盘,持久化存储。到时间自动删除
2.负数:默认值,Cookie在当前浏览器内存中,当浏览器关闭,则Cookie被销毁
3.零:删除对应Cookie
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//发送Cookie
//1. 创建Cookie对象
Cookie cookie = new Cookie("username","zs");
//设置存活时间 ,1周 7天
cookie.setMaxAge(60*60*24*7); //易阅读,需程序计算
//cookie.setMaxAge(604800); //不易阅读(可以使用注解弥补),程序少进行一次计算
//2. 发送Cookie,
response response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
Cookie存储中文:
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//发送Cookie
String value = "张三";
//对中文进行URL编码
value = URLEncoder.encode(value, "UTF-8");
System.out.println("存储数据:"+value);
//1. 创建Cookie对象
Cookie cookie = new Cookie("username",value);
//设置存活时间 ,1周 7天
cookie.setMaxAge(60*60*24*7); //易阅读,需程序计算
//cookie.setMaxAge(604800); //不易阅读(可以使用注解弥补),程序少进行一次计算
//2. 发送Cookie,
response response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取Cookie
//1. 获取Cookie数组
Cookie[] cookies = request.getCookies();
//2. 遍历数组
for (Cookie cookie : cookies) {
//3. 获取数据
String name = cookie.getName();
if("username".equals(name)){
String value = cookie.getValue();//获取的是URL编码后的值%E5%BC%A0%E4%B8%89
//URL解码
value = URLDecoder.decode(value,"UTF-8");
System.out.println(name+":"+value); //value解码后为 张三
break;
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
7.3、Session
7.3.1、Session概述
Session:服务端会话跟踪技术:将数据保存到服务端。
Session是存储在服务端而Cookie是存储在客户端
存储在客户端的数据容易被窃取和截获,存在很多不安全的因素
存储在服务端的数据相比于客户端来说就更安全
在服务端的AServlet获取一个Session对象,把数据存入其中
在服务端的BServlet获取到相同的Session对象,从中取出数据
就可以实现一次会话中多次请求之间的数据共享了
7.3.2、Session的基本使用
1.获取Session对象,使用的是request对象
HttpSession session = request.getSession();
Session对象提供的功能:
2.存储数据到 session 域中
void setAttribute(String name, Object o)
3.根据 key,获取值
Object getAttribute(String name)
4.根据 key,删除该键值对
void removeAttribute(String name)
示例:在一个Servlet中往Session中存入数据,在另一个Servlet中获取Session中存入的数据
1.创建名为SessionDemo1的Servlet类
2.创建名为SessionDemo2的Servlet类
3.在SessionDemo1的方法中:获取Session对象、存储数据
4.在SessionDemo2的方法中:获取Session对象、获取数据
5.启动测试
@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//存储到Session中
//1. 获取Session对象
HttpSession session = request.getSession();
//2. 存储数据
session.setAttribute("username","zs");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取数据,从session中
//1. 获取Session对象
HttpSession session = request.getSession();
//2. 获取数据
Object username = session.getAttribute("username");
System.out.println(username);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
先访问http://localhost:8080/cookie-demo/demo1 ,将数据存入Session
再访问http://localhost:8080/cookie-demo/demo2 ,从Session中获取数据
7.3.2、Session原理
Session要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取Session的对象是同一个 ,Session是如何保证在一次会话中获取的Session对象是同一个呢?
新打开一个浏览器访问demo1或者demo2,打印在控制台的Session还是同一个对象么?
(1)demo1在第一次获取session对象的时候,session对象会有一个唯一的标识,假如是id:10
(2)demo1在session中存入其他数据并处理完成所有业务后,需要通过Tomcat响应结果给浏览器
(3)Tomcat服务器发现业务处理中使用了session对象,就会把session的唯一标识id:10当做一个cookie,添加Set-Cookie:JESSIONID=10到响应头中,并响应给浏览器
(4)浏览器接收到响应结果后,会把响应头中的coookie数据存储到浏览器的内存中
(5)浏览器在同一会话中访问demo2的时候,会把cookie中的数据按照cookie: JESSIONID=10的格
式添加到请求头中并发送给服务器Tomcat
(6)demo2获取到请求后,从请求头中就读取cookie中的JSESSIONID值为10,然后就会到服务器内存中寻找id:10的session对象,找到了就直接返回该对象,如果没有则新创建一个session对象
(7)关闭打开浏览器后,因为浏览器的cookie已被销毁,所以就没有JESSIONID的数据,服务端获取到的session就是一个全新的session对象
7.3.3、Session的使用细节
Session钝化与活化:
服务器重启后,Session中的数据是否还在?这样会存在什么问题?
(1)服务器端AServlet和BServlet共用的session对象应该是存储在服务器的内存中
(2)服务器重新启动后,内存中的数据应该是已经被释放,对象也应该都销毁了
(1)用户把需要购买的商品添加到购物车,因为要实现同一个会话多次请求数据共享,所以假设把数据存入Session对象中
(2)用户正要付钱的时候接到一个电话,付钱的动作就搁浅了
(3)正在用户打电话的时候,购物网站因为某些原因需要重启
(4)重启后session数据被销毁,购物车中的商品信息也就会随之而消失
(5)用户想再次发起支付,就会出为问题
所以说对于session的数据,我们应该做到就算服务器重启了,也应该能把数据保存下来才对。
(1)先启动Tomcat服务器
(2)访问http://localhost:8080/cookie-demo/demo1将数据存入session中
(3)正确停止Tomcat服务器
(4)再次重新启动Tomcat服务器
(5)访问http://localhost:8080/cookie-demo/demo2 查看是否能获取到session中的数据
经过测试,会发现只要服务器是正常关闭和启动,session中的数据是可以被保存下来的。
钝化的数据路径为:项目目录\target\tomcat\work\Tomcat\localhost\项目名称 \SESSIONS.ser
Session销毁
session的销毁会有两种方式:
默认情况下,无操作,30分钟自动销毁,对于这个失效时间,是可以通过配置进行修改的
在项目的web.xml中配置
调用Session对象的invalidate()进行销毁
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取数据,从session中
//1. 获取Session对象
HttpSession session = request.getSession();
// 销毁
session.invalidate();
//2. 获取数据
Object username = session.getAttribute("username");
System.out.println(username);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
启动访问测试,先访问demo1将数据存入到session,再次访问demo2从session中获取数据
会报:已经无效了,取不到数据
八、Filter&Listener
8.1、Filter概述
Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一,过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
第一,过滤器一般完成一些通用的操作。比如每个资源都要写一些代码完成某个功能,我们总不能在每个资源中写这样的代码吧,而此时我们可以将这些代码写在过滤器中,因为请求每一个资源都要经过过滤器。
第二,权限控制。之前做的品牌数据管理的案例中就已经做了登陆的功能,而如果我们不登录能不能访问到数据呢?我们可以在浏览器直接访问首页,当我点击该按钮,居然可以看到品牌的数据,显然和我们的要求不符,我们希望实现的效果是用户如果登陆过了就跳转到品牌数据展示的页面;如果没有登陆就跳转到登陆页面让用户进行登陆,要实现这个效果需要在每一个资源中都写上这段逻辑,而像这种通用的操作,我们就可以放在过滤器中进行实现。这个就是权限控制。
过滤器还可以实现统一编码处理 、敏感字符处理等功能(略)。
8.2、Filter快速入门
(1)定义类,实现 Filter接口,并重写其所有方法:doFilter/init/destroy.
(2)配置Filter拦截资源的路径:在类上定义 @WebFilter 注解。
(3)在doFilter方法中编写放行逻辑
@WebFilter("/*")//注解的 value 属性值 /* 表示拦截所有的资源
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
//判断访问资源路径是否和登录注册相关
//1,在数组中存储登陆和注册相关的资源路径
String[] urls ={"/login.jsp","/imgs/","/css/","/loginServlet"};
//2,获取当前访问的资源路径
String url = req.getRequestURL().toString();
//3,遍历数组,获取到每一个需要放行的资源路径
for (String u : urls) {
//4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串 /* 比如当前访问的资源路径是 /brand-demo/login.jsp 而字符串 /brand-demo/login.jsp 包含了 字符串 /login.jsp ,所以这个字符串就需要放行 */
if(url.contains(u)){ //找到了,放行
chain.doFilter(request, response);
//break;
return;
}
}
//1. 判断session中是否有user
HttpSession session = req.getSession();
Object user = session.getAttribute("user");
//2. 判断user是否为null
if(user != null){ // 登录过了
//放行
chain.doFilter(request, response);
}else {
// 没有登陆,存储提示信息,跳转到登录页面
req.setAttribute("login_msg","您尚未登陆!");
req.getRequestDispatcher("/login.jsp").forward(req,response);
}
}
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
}
Filter拦截路径配置:
拦截路径有如下四种配置方式:
拦截具体的资源:/index.jsp:只有访问index.jsp时才会被拦截
目录拦截:/user/*:访问/user下的所有资源,都会被拦截
后缀名拦截:*.jsp:访问后缀名为jsp的资源,都会被拦截
拦截所有:/*:访问所有资源,都会被拦截
Filter执行流程:
@WebFilter("/*")//注解的 value 属性值 /* 表示拦截所有的资源
public class FilterDemo implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws ServletException, IOException {
System.out.println("Demo1");
chain.doFilter(request, response);
System.out.println("Demo3");
}
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
}
//配置JSP页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>hello jsp</h1>
<%
System.out.println("hello,jsp~");
%>
</body>
</html>
启动服务器访问 hello.jsp 页面,在控制台打印的内容如下:
Demo1
hello,jsp~
Demo3
多Filter执行流程(Filter链):
使用的是注解配置Filter,而这种配置方式的优先级是按照过滤器类名(字符串)的自然排序。 比如有如下两个名称的过滤器 : BFilterDemo 和 AFilterDemo 。则 AFilterDemo 过滤器先执行。
8.3、Listener概述
Listener 表示监听器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
监听器可以监听就是在 application , session , request 三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。request 和 session 我们学习过。而 application 是 ServletContext 类型的对象。ServletContext 代表整个web应用,在服务器启动的时候,tomcat会自动创建该对象。在服务器关闭时会自动销毁该对象。
JavaWeb提供了8个监听器
这里面只有 ServletContextListener 这个监听器后期我们会接触到, ServletContextListener 是用来监听ServletContext 对象的创建和销毁。
ServletContextListener 接口中有以下两个方法
void contextInitialized(ServletContextEvent sce) : ServletContext 对象被创建了会自动执行
void contextDestroyed(ServletContextEvent sce) : ServletContext 对象被销毁时会自动执行
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
//加载资源
System.out.println("ContextLoaderListener...");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
//释放资源
}
}
启动服务器,就可以在启动的日志信息中看到 contextInitialized() 方法输出的内容,
同时也说明了 ServletContext对象在服务器启动的时候被创建了。