一、Servlet
1.1 概念
- Servlet:Server Applet的简称,是服务器端的程序(代码、功能实现),可交互式的处理客户端发送到服务端的请求,并完成操作响应。
- 动态网页技术
- JavaWeb程序开发的基础,JavaEE规范(一套接口)的一个组成部分。
1.1.1 Servlet作用
- 接收客户端请求,完成操作。
- 动态生成网页(页面数据可变)。
- 将包含操作结果的动态网页响应给客户端。
1.2 Servlet 开发步骤
1.2.1 环境搭建
- 新建一个普通的java项目,File–>New–>Project–>java–>next–>next–>修改项目名称–>finnish
- 我这里使用的是Module,所以新建一个Module
- 鼠标右键点击当前项目,点击Add Framework Support
接下来…
此时项目结构会多出web目录,在web目录下的WEB-INF目录下新建一个lib目录(注意是在WEB-INF下创建lib目录)
- 将
servlet-api.jar
(这个jar包在Tomcat安装目录下的lib目录下有,直接复制进来即可)放入lib文件夹下,将servlet-api.jar
添加到项目库,
点击ok结束。
1.2.2 编写Servlet
MyServlet.java
package com.ahao.servlet;
import javax.servlet.*;
import java.io.IOException;
//实现Servlet接口
public class MyServlet implements Servlet {
/**
* servlet 初始化的方案,即第一次加载 servlet 的时候,会执行该方法
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("执行了init...............");
}
/**
* 获取 servlet 的配置信息,这个配置信息其实就是init方法中的参数的值
*/
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 所有的请求(get、post、put、delete。。。)都在 service 方法中进行处理
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("执行了service..........");
}
/**
* 获取 servlet 的信息
*/
@Override
public String getServletInfo() {
return null;
}
/**
* 销毁 servlet 的时候会触发
*/
@Override
public void destroy() {
System.out.println("执行了destory..........");
}
}
1.2.3 配置web.xml
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 注册 servlet,所有的 servlet 都需要注册-->
<servlet>
<!--首先给自定义的 servlet 取一个名字-->
<servlet-name>myServlet</servlet-name>
<!--指定自定义的 servlet 在哪里-->
<servlet-class>com.ahao.servlet.MyServlet</servlet-class>
</servlet>
<!--配置 servlet 的映射,即访问哪个 url 地址可以进入到当前 servlet-->
<servlet-mapping>
<!--servlet-name 需要跟前面的保持一致-->
<servlet-name>myServlet</servlet-name>
<!--访问路径
注意,这个 url-pattern 只能以 / 或者 * 开始,不能以其他的字符开始
当浏览器访问到 /my 这个路径的时候,首先被 servlet-mapping 拦截下来,拦截下来后,看到 /my 应该由一个名叫 MyServlet 的类去处理,接下来就去 servlet 节点中查找 myServlet
-->
<url-pattern>/my</url-pattern>
</servlet-mapping>
</web-app>
1.2.4 idea中配置Tomcat服务器及项目部署
-
step-01:
-
step-02
-
step-03
-
step-04
-
step-05
-
step-06
-
step-07:点击启动
-
step-08: 项目启动完默认加载index.jsp页面
-
输入自己配置的地址:
-
浏览器输入请求地址后会调用相应的Servlet
二、Http
2.1 什么是HTTP
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,是一个基于请求与响应模式的、无状态的、应用层的协议,运行于TCP协议基础之上。
2.2 HTTP协议特点
-
支持客户端(浏览器)/服务器模式。
-
简单快速:客户端只向服务器发送请求方法(get、post、put、delete、trace、head、options)和路径,服务器即可响应数据,因而通信速度很快。请求方法常用的有GET、POST等。
-
灵活:HTTP允许传输任意类型的数据,传输的数据类型由Content-Type标识。
-
无连接:无连接指的是每次TCP连接只处理一个或多个请求,服务器处理完客户的请求后,即断开连接。采用这种方式可以节省传输时间。
- HTTP1.0版本是一个请求响应之后,直接就断开了。称为短连接。
- HTTP1.1版本不是响应后直接就断开了,而是等几秒钟,这几秒钟之内有新的请求,那么还是通过之前的连接通道来收发消息,如果过了这几秒钟用户没有发送新的请求,就会断开连接。称为长连接。
-
无状态:HTTP协议是无状态协议。
-
无状态是指协议对于事务处理没有记忆能力。
2.3 HTTP协议通信流程
- 客户与服务器建立连接(三次握手)。
- 客户向服务器发送请求。
- 服务器接受请求,并根据请求返回相应的文件作为应答。
- 客户与服务器关闭连接(四次挥手)。
2.4 请求报文和响应报文
2.4.1 HTTP请求报文
当浏览器向Web服务器发出请求时,它向服务器传递了一个数据块,也就是请求信息(请求报文),HTTP请求信息由4部分组成:
- 请求行 请求方法/地址 URI协议/版本
- 请求头(Request Header)
- 空行
- 请求正文
2.4.2 HTTP响应报文
HTTP响应报文与HTTP请求报文相似,HTTP响应也由4个部分组成:
- 状态行
- 响应头(Response Header)
- 空行
- 响应正文
2.4.3 常见状态码
状态代码 | 状态描述 | 说明 |
---|---|---|
200 | OK | 客户端请求成功 |
302 | Found | 临时重定向 |
403 | Forbidden | 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因 |
404 | Not Found | 请求的资源不存在,例如,输入了错误的URL。 |
500 | Internal Server Error | 服务器发生不可预期的错误,导致无法完成客户端的请求。 |
三、Servlet详解
3.1 Servlet核心接口和类
在Servlet体系结构中,除了实现Servlet接口,还可以通过继承GenericServlet 或 HttpServlet类,完成编写。
3.1.1 Servlet接口
在Servlet API中最重要的是Servlet接口,所有Servlet都会直接或间接的与该接口发生联系,或是直接实现该接口,或间接继承自实现了该接口的类。
该接口包括以下五个方法:
init(ServletConfig config)
ServletConfig getServletConfig()
service(ServletRequest req,ServletResponse res)
String getServletInfo()
destroy( )
3.1.2 GenericServlet抽象类
GenericServlet 使编写 Servlet 变得更容易。它提供生命周期方法 init 和 destroy 的简单实现,要编写一般的 Servlet,只需重写抽象 service 方法即可。
3.1.3 HttpServlet类
HttpServlet是继承GenericServlet的基础上进一步的扩展。
提供将要被子类化以创建适用于 Web 站点的 HTTP servlet 的抽象类。HttpServlet 的子类至少必须重写一个方法,该方法通常是以下这些方法之一:
doGet,如果 servlet 支持 HTTP GET 请求
doPost,用于 HTTP POST 请求
doPut,用于 HTTP PUT 请求
doDelete,用于 HTTP DELETE 请求
3.2 Servlet两种创建方式
3.2.1 实现接口Servlet
- 该方式比较麻烦,需要实现接口中所有方法。该方法在上面的案例中实现。
3.2.2 继承HttpServlet(推荐)
/**
* Servlet implementation class HelloServlet
* Servlet的第二种创建方式,继承HttpServlet.也是开发中推荐的
*
*/
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print("welcome use servlet");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
3.2.3 常见错误
HTTP Status 404 资源找不到 。
- 第一种情况:地址书写错误。
- 第二种情况:地址没有问题,把IDEA项目中out目录删除,然后重新运行。
Servlet地址配置重复。both mapped to the url-pattern [/helloservlet] which is not permitted。
Serlvet地址配置错误。比如没有写/ Invalid [helloservlet2] in servlet mapping。
3.3 Servlet两种配置方式
servlet-api 2.5 之前(含)
javax.servlet-api
Servlet2.5 之前(Tomcat7 之前),只能在 web.xml 中进行配置。
3.3.1 使用web.xml(Servlet2.5之前使用)
<?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配置 -->
<servlet>
<!--名称 -->
<servlet-name>helloServlet</servlet-name>
<!--Servlet的全称类名 -->
<servlet-class>com.ahao.servlet.HelloServlet</servlet-class>
<!--启动的优先级,数字越小越先起作用 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--映射配置 -->
<servlet-mapping>
<!--名称 -->
<servlet-name>helloServlet</servlet-name>
<!--资源的匹配规则:精确匹配 -->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
3.3.2 配置属性
url-pattern定义匹配规则,取值说明:
精确匹配 /具体的名称 只有url路径是具体的名称的时候才会触发Servlet
后缀匹配 *.xxx 只要是以xxx结尾的就匹配触发Servlet
通配符匹配 /* 匹配所有请求,包含服务器的所有资源
通配符匹配 / 匹配所有请求,包含服务器的所有资源,不包括.jsp
load-on-startup
1元素标记容器是否应该在web应用程序启动的时候就加载这个servlet。
2它的值必须是一个整数,表示servlet被加载的先后顺序。
3如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
4如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
3.3.3 使用注解 (Servlet3.0后支持,推荐)
/**
* Servlet implementation class HelloServlet
* 演示Servlet注解式配置
*/
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print("OK");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
3.3.4 @WebServlet注解常用属性
-
name: Serlvet名字 (可以不写)
-
value: 配置url路径,可以配置多个
-
urlPatterns:配置url路径 ,和value作用一样,不能同时使用
-
loadOnStartup:配置Servlet的创建的时机, 如果是0或者正数 启动程序时创建,如果是负数,则访问时创建。 数子越小优先级越高。
四、Servlet 应用
4.1 request对象
- 在Servlet中用来处理客户端请求需要用doGet或doPost方法的request对象
request |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OqP6XHdy-1648727042888)(Pictures/request对象.png)] |
4.1.1 get和post区别
- get提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连
- get方式明文传递,数据量小,不安全
- 效率高,浏览器默认请求方式为GET请求
- 对应的Servlet的方法是doGet
- post方法是把提交的数据放在HTTP包的Body中
- 密文传递数据,数据量大,安全
- 效率相对没有GET高
- 对应的Servlet的方法是doPost
4.1.2 request主要方法
方法名 | 说明 |
---|---|
String getParameter(String name) | 根据表单组件名称获取提交数据 |
void setCharacterEncoding(String charset) | 指定每个请求的编码 |
4.1.3 请求参数问题
1.第一种请求传参
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(urlPatterns = "/my01")
public class MyServlet01 extends HttpServlet {
/**
* @param req 浏览器发送的请求对象,请求的参数等各种信息都在这个里边
* @param resp 响应对象,服务器给浏览器的响应对象
* @throws ServletException
* @throws IOException
*
* 1. 请求参数放在地址栏中 http://localhost:8080/myweb02/my01?id=99&name=xxx&age=xxx(GET、POST、PUT、DELETE 都支持)
* 2. 请求的参数可以放在请求体中,相比第一种,参数的数据量更大,而且安全性也更好,这个可以在 POST、PUT 中使用。由于 HTTP 协议本身并没有规范 GET 请求是否可以带请求体,这就导致在不同的实现中,有的服务器是支持 get 带请求体的,有的则不支持。
* 参数放在请求体中,也可以有不同的格式:
* 2.1 key-value 形式
* 2.2 json 格式
* 2.3 文件上传的
* 无论哪种格式,本质上都是 IO 流。
* 3. 参数放在地址栏中 http://localhost:8080/myweb02/my01/99/zhangsan/lisi(GET、POST、PUT、DELETE 都支持)
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//请求参数放在地址栏中 http://localhost:8080/myweb02/my01?username=阿豪&password=123456(GET、POST、PUT、DELETE 都支持)
//根据参数的 key 获取参数具体的值
req.setCharacterEncoding("UTF-8");
System.out.println("doGet................");
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("username = " + username );
System.out.println("password = " + password );
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost................");
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("username = " + username );
System.out.println("password = " + password );
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doDelete................");
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("username = " + username );
System.out.println("password = " + password );
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPut................");
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("username = " + username );
System.out.println("password = " + password );
}
}
- 第二种请求传参
@WebServlet(urlPatterns = "/my02/*")
public class MyServlet02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/**
* http://localhost:8080/myweb02/my01/99,get post put delete 都支持这种传参方式
*
* 请求中的地址分为三个部分:以 http://localhost:8080/myweb02/my01/99 为例:
*
* http://localhost:8080 协议+域名+端口,这个是固定的,不算在三部分中
*
* 1. /myweb03 这个叫做上下文路径,即项目的名字 contextPath
* 2. /my01,这个叫做 servletPath
* 3. /99 叫做 pathInfo
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
String contextPath = req.getContextPath();
String servletPath = req.getServletPath();
String pathInfo = req.getPathInfo();
System.out.println("contextPath = "+contextPath);
System.out.println("servletPath = "+servletPath);
System.out.println("pathInfo = "+pathInfo);
}
}
4.1.4 获取参数问题
package com.ahao.servlet;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.print.Book;
import java.io.IOException;
@WebServlet("/my04")
public class MyServlet04 extends HttpServlet {
/**
* 如果前端的参数是 key-value 形式的,那么可以直接使用 getParameter 方法来读取
*
* 虽然这个方法和处理URL地址参数中的方法一样,但是内部的执行逻辑不一样
* 这里是 servlet 自动解析了 IO 流,并且将从 IO 流中读取的数据存入到一个 Map 中,然后我们可以从 getParameter 方法获取。
*
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置请求体的编码,防止中文乱码,注意,需要在读取参数之前设置
req.setCharacterEncoding("UTF-8");
// BufferedReader reader = req.getReader();
// String s = reader.readLine();
// System.out.println("s = " + s);
//这里的数据是从 IO 流中读取的,IO 流只能读取一次。
String name = req.getParameter("name");
String age = req.getParameter("age");
System.out.println("name = " + name);
System.out.println("age = " + age);
}
/**
* 这里来解析 JSON 参数
*
* PUT 请求比较特殊,特殊在 servlet 没有自动帮我们解析 IO 流,所以在 PUT 请求中,无法通过 getParameter 方法获取参数。如果要获取 key-value 的参数,可以自行解析 IO 流 name=xxx&age=xxx
*
* 所以在 PUT 请求中,推荐使用 JSON。
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置请求体的编码,防止中文乱码,注意,需要在读取参数之前设置
req.setCharacterEncoding("UTF-8");
//读取 IO 流,并将之转为 book 对象
ObjectMapper om = new ObjectMapper();
Book book = om.readValue(req.getReader(), Book.class);
System.out.println("book = " + book);
}
}
4.1.5 response主要方法
方法名称 | 作用 |
---|---|
setHeader(name,value) | 设置响应信息头 |
setContentType(String) | 设置响应文件类型、响应式的编码格式 |
setCharacterEncoding(String) | 设置服务端响应内容编码格式 |
getWriter() | 获取字符输出流 |
4.1.6 解决输出中文乱码
- 设置服务器端响应的编码格式
- 设置客户端响应内容的头内容的文件类型及编码格式
response.setCharacterEncoding("utf-8");//设置响应编码格式为utf-8
response.setHeader("Content-type","text/html;charset=UTF-8");
- 同时设置服务端的编码格式和客户端响应的文件类型及响应时的编码格式(推荐使用)
response.setContentType("text/html;charset=UTF-8");
五、转发与重定向
5.1 转发
转发的作用在服务器端,将请求发送给服务器上的其他资源,以共同完成一次请求的处理。
1.1.1 页面跳转
在调用业务逻辑的Servlet中,编写以下代码:
request.getRequestDispatcher("/目标URL-pattern").forward(request, response);
1.1.2 数据传递
forward表示一次请求,是在服务器内部跳转,可以共享同一次request作用域中的数据
-
request作用域:拥有存储数据的空间,作用范围是一次请求有效(一次请求可以经过多次转发)
- 可以将数据存入request后,在一次请求过程中的任何位置进行获取
- 可传递任何数据(基本数据类型、对象、数组、集合等)
-
存数据:request.setAttribute(key,value);
-
以键值对形式存储在request作用域中。key为String类型,value为Object类型
-
取数据:request.getAttribute(key);
-
通过String类型的key访问Object类型的value
1.1.3 转发特点
- 转发是服务器行为
- 转发是浏览器只做了一次访问请求
- 转发浏览器地址不变
- 转发两次跳转之间传输的信息不会丢失,所以可以通过request进行数据的传递、
- 转发只能将请求转发给同一个Web应用中的组件
5.2 重定向
重定向作用在客户端,客户端将请求发送给服务器后,服务器响应给客户端一个新的请求地址,客户端重新发送新请求。
5.2.1 页面跳转
在调用业务逻辑的Servlet中,编写以下代码
response.sendRedirect("目标URI");
5.2.2 数据传递
sendRedirect跳转时,地址栏改变,代表客户端重新发送的请求。属于两次请求
- response没有作用域,两次request请求中的数据无法共享
- 传递数据:通过URI的拼接进行数据传递;
- 获取数据:request.getParameter();
5.2.3 重定向特点
-
重定向是客户端行为。
-
重定向是浏览器做了至少两次的访问请求。
-
重定向浏览器地址改变。
-
重定向两次跳转之间传输的信息会丢失(request范围)。
-
重定向可以指向任何的资源,包括当前应用程序中的其他资源、同一个站点上的其他应用程序中的资源、其他站点的资源。
5.4 转发、重定向总结
客户端跳转(重定向):
- 一共发送了两次请求
- 可以跳转到站外
- 跳转路径需要包含 context-path
- 实现方案:
- resp.setStatus(302);resp.addHeader(“Location”,“客户端路径,需要包含context-path”);
- resp.sendRedirect(“客户端路径,需要包含context-path”);
服务端跳转:
-
一共只发送了一次请求
-
不可以跳转到站外,只能在当前网站跳转
-
跳转路径不需要包含 context-path
-
实现方案:
- req.getRequestDispatcher(“跳转路径,不需要 context-path”).forward(req,resp);
-
当两个Servlet需要传递数据时,选择forward转发。不建议使用sendRedirect进行传递
六、 Servlet生命周期
6.1 生命周期四个阶段
6.1.1 实例化(构造方法的调用)
当用户第一次访问Servlet时,由容器调用Servlet的构造器创建具体的Servlet对象。也可以在容器启动之后立刻创建实例。使用如下代码可以设置Servlet是否在服务器启动时就创建。
<load-on-startup>1</load-on-startup>
- 注意:只执行一次
6.1.2 初始化
在初始化阶段,init()方法会被调用。这个方法在javax.servlet.Servlet接口中定义。其中,方法以一个ServletConfig类型的对象作为参数。
- 注意:init方法只被执行一次
6.1.3 服务
当客户端有一个请求时,容器就会将请求ServletRequest与响应ServletResponse对象转给Servlet,以参数的形式传给service方法。
- 此方法会执行多次
6.1.4 销毁
当Servlet容器停止或者重新启动都会引起销毁Servlet对象并调用destroy方法。
- destroy方法执行一次
七 、 servlet 特性
7.1 线程安全问题
Servlet在访问之后,会执行实例化操作,创建一个Servlet对象。而我们Tomcat容器可以同时多个线程并发访问同一个Servlet,如果在方法中对成员变量做修改操作,就会有线程安全的问题。
7.2 如何保证线程安全
synchronized
- 将存在线程安全问题的代码放到同步代码块中
实现SingleThreadModel接口
- servlet实现SingleThreadModel接口后,每个线程都会创建servlet实例,这样每个客户端请求就不存在共享资源的问题,但是servlet响应客户端请求的效率太低,所以已经淘汰。
尽可能使用局部变量
八、Cookie
8.1 什么是cookie
-
Cookie是在浏览器访问Web服务器的某个资源时,由Web服务器在HTTP响应消息头中附带传送给浏览器的一小段数据。
-
一旦Web浏览器保存了某个Cookie,那么它在以后每次访问该Web服务器时,都应在HTTP请求头中将这个Cookie回传给Web服务器。
-
一个Cookie主要由标识该信息的名称(name)和值(value)组成。
8.2 创建cookie
@WebServlet("/cookie")
public class CookieServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//响应数据的时候,设置 cookie
Cookie cookie = new Cookie("username","zhangsan");
//对于中文,需要手动编码
Cookie cookie1 = new Cookie("address", URLEncoder.encode("广州", "UTF-8"));
//0 表示这个 cookie 会被删除,如果想要通过服务端删除一个 cookie,就设置该 cookie 的有效期为 0,那么当浏览器收到响应后,就会自动删除该 cookie
cookie.setMaxAge(-1);
//由于我们可以通过服务端往浏览器的 cookie 中写入数据,也可以通过 js 往浏览器的 cookie 中写入数据,当设置 httpOnly 为 true 的时候,就表示这个 cookie 一旦写入到浏览器之后,无法通过 js 操作它。
cookie.setHttpOnly(true);
//将 cookie 添加到响应中
resp.addCookie(cookie);
//可以设置多个 cookie 对象
resp.addCookie(cookie1);
resp.getWriter().write("Hello Cookie");
}
}
8.3 获取Cookie
@WebServlet("/getCookie")
public class GetCookieServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//以后的请求,都会自动携带上当前网站已有的 cookie
//由于浏览器的 cookie 可能有多个,所以这里收到的是一个 cookie 数组
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
if ("address".equals(name)) {
//需要对 value 的中文进行解码
String decode = URLDecoder.decode(value, "UTF-8");
System.out.println(name + "--->" + decode);
} else {
System.out.println(name + "--->" + value);
}
}
}
}
8.4 Cookie优点和缺点
8.4.1 优点
-
可配置到期规则。
-
简单性:Cookie 是一种基于文本的轻量结构,包含简单的键值对。
-
数据持久性:Cookie默认在过期之前是可以一直存在客户端浏览器上的。
8.4.2 缺点
-
大小受到限制:大多数浏览器对 Cookie 的大小有 4K、8K字节的限制。
-
用户配置为禁用:有些用户禁用了浏览器或客户端设备接收 Cookie 的能力,因此限制了这一功能。、
-
潜在的安全风险:Cookie 可能会被篡改。会对安全性造成潜在风险或者导致依赖于Cookie 的应用程序失败。
九、session对象
9.1 Session概述
- Session用于记录用户的状态。Session指的是在一段时间内,单个客户端与Web服务器的一连串相关的交互过程。
- 在一个Session中,客户可能会多次请求访问同一个资源,也有可能请求访问各种不同的服务器资源。
9.2 Session原理
-
服务器会为每一次会话分配一个Session对象
-
同一个浏览器发起的多次请求,同属于一次会话(Session)
-
首次使用到Session时,服务器会自动创建Session,并创建Cookie存储SessionId发送回客户端
9.3 Session使用
- Session作用域:拥有存储数据的空间,作用范围是一次会话有效
- 一次会话是使用同一浏览器发送的多次请求。一旦浏览器关闭,则结束会话
- 可以将数据存入Session中,在一次会话的任意位置进行获取
- 可传递任何数据(基本数据类型、对象、集合、数组)
9.3.1 获取Session
session是服务器端自动创建的,通过request对象获取
//获取Session对象
HttpSession session=request.getSession();
System.out.println("Id:"+session.getId());//唯一标记,
9.3.2 Session保存数据
setAttribute(属性名,Object)保存数据到session中
session.setAttribute("key",value);//以键值对形式存储在session作用域中。
9.3.3 Session获取数据
getAttribute(属性名);获取session中数据
session.getAttribute("key");//通过String类型的key访问Object类型的value
9.3.4 Session移除数据
removeAttribute(属性名);从session中删除数据
session.removeAttribute("key");//通过键移除session作用域中的值
9.4 Session与Request应用区别
- request是一次请求有效,请求改变,则request改变
- session是一次会话有效,浏览器改变,则session改变
9.5 Session的生命周期
-
开始:第一次使用到Session的请求产生,则创建Session
-
结束:
- 浏览器关闭,则失效
- Session超时,则失效
- session.setMaxInactiveInterval(seconds);//设置最大有效时间(单位:秒)
-
手工销毁,则失效
- session.invalidate();//登录退出、注销
十、ServletContext对象
10.1 ServletContext概述
-
全局对象,也拥有作用域,对应一个Tomcat中的Web应用
-
当Web服务器启动时,会为每一个Web应用程序创建一块共享的存储区域(ServletContext)。
-
ServletContext在Web服务器启动时创建,服务器关闭时销毁。
10.2 获取ServletContext对象
-
GenericServlet提供了getServletContext()方法。(推荐) this.getServletContext();
-
HttpServletRequest提供了getServletContext()方法。(推荐)
-
HttpSession提供了getServletContext()方法。
10.3 ServletContext作用
10.3.1 获取项目真实路径
获取当前项目在服务器发布的真实路径
String realpath=servletContext.getRealPath("/");
10.3.2 获取项目上下文路径
获取当前项目上下文路径(应用程序名称)
System.out.println(servletContext.getContextPath());//上下文路径(应用程序名称)
System.out.println(request.getContextPath());
10.3.3 全局容器
ServletContext拥有作用域,可以存储数据到全局容器中
-
存储数据:servletContext.setAttribute(“name”,value);
-
获取数据:servletContext.getAttribute(“name”);
-
移除数据:servletContext.removeAttribute(“name”);
10.4 ServletContext特点
- 唯一性: 一个应用对应一个ServletContext。
- 生命周期: 只要容器不关闭或者应用不卸载,ServletContext就一直存在。
十一、过滤器
三大基础组件:
- Servlet
- Filter
- Listener
11.1 概念
过滤器(Filter)是处于客户端与服务器目标资源之间的一道过滤技术。
11.2 过滤器的作用
-
执行地位在Servlet之前,客户端发送请求时,会先经过Filter,再到达目标Servlet中;响应时,会根据执行流程再次反向执行Filter
-
可以解决多个Servlet共性代码的冗余问题(例如:乱码处理、登录验证)
11.3 编写过滤器
Servlet API中提供了一个Filter接口,开发人员编写一个Java类实现了这个接口即可,这个Java类称之为过滤器(Filter)
11.4 实现过程
编写Java类实现Filter接口
在doFilter方法中编写拦截逻辑
设置拦截路径
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/myservlet1")//过滤路径
public class MyFilter1 implements Filter {
//初始化过滤器
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化了........init... "+filterConfig);
}
//执行过滤
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("过滤前........doFilter ");
//放行
chain.doFilter(request, response);
System.out.println("过滤后.......doFilter");
}
//销毁
@Override
public void destroy() {
System.out.println("销毁了.....destroy");
}
}
11.5 过滤器配置
11.5.1 注解配置
在自定义的Filter类上使用注解@WebFilter(value=“/过滤目标资源”)
11.5.2 xml配置
<!--过滤器的xml配置 -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<!-- 先给过滤器取一个名字 -->
<filter-name>encodingFilter</filter-name>
<!-- 配置过滤器完整的类路径 -->
<filter-class>com.ahao.filter.EncodingFilter</filter-class>
<!-- 配置 filter 的初始化参数 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<!-- 配置过滤器的拦截规则 -->
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<!-- 注册一个监听器 -->
<listener-class>com.ahao.listener.MyRequestListener</listener-class>
</listener>
</web-app>
11.5.3 过滤器路径
过滤器的过滤路径通常有三种形式:
精确过滤匹配 ,比如/index.jsp /myservlet1
后缀过滤匹配,比如*.jsp、*.html、*.jpg
通配符过滤匹配/*,表示拦截所有。注意过滤器不能使用/匹配。
/aaa/bbb/* 允许
11.6 过滤器链和优先级
11.6.1 过滤器链
客户端对服务器请求之后,服务器调用Servlet之前会执行一组过滤器(多个过滤器),那么这组过滤器就称为一条过滤器链。
每个过滤器实现某个特定的功能,当第一个Filter的doFilter方法被调用时,Web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则Web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
11.6.2 过滤器优先级
在一个Web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
优先级:
如果为注解的话,是按照类全名称的字符串顺序决定作用顺序
如果web.xml,按照 filter-mapping注册顺序,从上往下
web.xml配置高于注解方式
如果注解和web.xml同时配置,会创建多个过滤器对象,造成过滤多次。
11.7 过滤器的应用
11.7.1 解决编码问题
/**
* 这是一个专门用来处理请求体编码的过滤器
*/
public class EncodingFilter implements Filter {
//首先定义一个全局的变量,假设默认的编码格式就是 UTF-8
private String encoding = "UTF-8";
/**
* 在过滤器的初始化方法中,去读取用户配置的编码格式。
* 如果用户配置了,就使用用户配置的编码格式,否则使用默认的编码格式。
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("EncodingFilter");
//获取用户配置的编码格式
String encoding = filterConfig.getInitParameter("encoding");
if (encoding != null) {
//说明用户配置了编码格式
this.encoding = encoding;
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//为所有请求的请求体设置编码格式
servletRequest.setCharacterEncoding(encoding);
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
11.7.2 权限验证
/**
* 权限拦截过滤器
* <p>
* 注意,这里用 @WebFilter 去注册过滤器,这里的 urlPatterns 表示要拦截的请求,/* 表示拦截所有请求
*/
@WebFilter(urlPatterns = "/*")
public class PermissionFilter implements Filter {
/**
* 这个是过滤器的初始化参数,类似于 Servlet 的 init 方法
*
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
/**
* 具体拦截请求的方法,所有的请求方法都会经过 doFilter
*
* @param servletRequest 就是当前的请求对象
* @param servletResponse 就是当前的响应对象
* @param filterChain 过滤器链
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//这里要做一个简单的区分,跟登录相关的请求,不需要拦截
HttpServletRequest request = (HttpServletRequest) servletRequest;
//获取当前请求地址
String requestURI = request.getRequestURI();
if (requestURI.contains("login")) {
//如果请求地址中包含 login,那么就有可能是 login.html 页面,也有可能是 login 接口
//这些地址不需要拦截,这些继续向下走即可
filterChain.doFilter(servletRequest, servletResponse);
} else {
//进到这里,说明这个请求需要拦截
//从 session 中读取 username 属性,如果 session 中有 username,表示用户已经登录,否则表示用户没有登录
Object username = request.getSession().getAttribute("username");
if (username == null) {
//说明用户没有登录,跳转到登录页面
HttpServletResponse resp = (HttpServletResponse) servletResponse;
resp.sendRedirect("/filter/login.html");
} else {
//说明用户已经登录,已经登录,当前请求就继续向下执行
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
/**
* 销毁 filter 的时候会触发,可以在这里释放资源
*/
@Override
public void destroy() {
}
}
十二、监听器
12.1 请求监听器
/**
* 请求监听器
*/
public class MyRequestListener implements ServletRequestListener {
/**
* 当一个请求被销毁的时候,会触发该方法
* @param servletRequestEvent
*/
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println("requestDestroyed");
}
/**
* 当一个请求被创建的时候,会触发该方法
* @param servletRequestEvent
*/
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("requestInitialized");
}
}
12.2 session监听器
@WebListener
public class MySessionListener implements HttpSessionListener, HttpSessionAttributeListener {
/**
* 创建一个 session 时,该方法会被触发
* @param httpSessionEvent
*/
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println("sessionCreated");
}
/**
* 销毁一个 session 时,该方法会被触发
*
* 两种情况该方法会被触发:
* 1. 注销登录时调用 session#invalidate() 方法
* 2. session 有效期到了
* @param httpSessionEvent
*/
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("sessionDestroyed");
}
/**
* 调用 session#setAttribute 方法时,该方法会被触发
* @param httpSessionBindingEvent
*/
@Override
public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("attributeAdded");
}
/**
* 调用 session#removeAttribute 方法时,该方法会被触发
* @param httpSessionBindingEvent
*/
@Override
public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("attributeRemoved");
}
/**
* 调用 session#setAttribute 方法时,该方法会被触发(set之前对应的 key 已经有值了)
* @param httpSessionBindingEvent
*/
@Override
public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("attributeReplaced");
}
}
12.3 SerlvetContext 监听器
/**
* 监听 SerlvetContext 的创建与销毁
*/
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("contextDestroyed");
}
}