JavaWeb
Servlet
- 是什么?
JavaWeb-项目创建
-
基本创建流程
| 创建流程 |
| :———————————————————-: |
|
|
-
Servlet实现步骤 * - 创建普通
Java类 - 实现
Servlet的规范,继承 HttpServlet类 - 重写
service方法,用来处理请求 - 设置注解,指定访问的路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.coderitl.javaservlet;
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;
public class ServletTest extends HttpServlet {
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello Servlet");
System.out.println("Hello Servlet");
}
} - 创建普通
生命周期
-
servlet没有 main()方法,不能独立运行,它的运行完全由 Servlet引擎来控制和调度,所谓生命周期,指的是 Servlet容器何时创建 servlet实例,何时调用其方法进行请求处理,何时并销毁其实例的整个过程 -
实例和初始化时机
- 当请求达到容器时,容器查找该
servlet对象是否存在,如果不存在,则会创建并实例并进行初始化
- 当请求达到容器时,容器查找该
-
就绪
/ 调用 / 服务阶段 - 有请求达到容器时,容器调用
servlet对象的 service()方法,处理请求的方法在整个生命周期中可以被多次调用, HttpServlet的 service方法, 会依据请求方式来调用 doGet()或者 doPost方法,但是,这两个 do方法默认情况下,会抛出异常,需要子类去 override
- 有请求达到容器时,容器调用
-
销毁时机
- 当容器关闭时,会将程序中的
Servlet实例进行销毁,上述的生命周期可以通过 Servlet中的生命周期来观察,在 Servlet中有三个生命周期,不由用户手动调用,而是在特定的时机有容器自动调用,观察者三个生命周期方法,即可观察到 servlet的生命周期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65package com.coderitl.javaservlet;
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;
/**
* 实现 Servlet
* 1. 创建普通 Java 类
* 2. 实现 Servlet 的规范,继承 HttpServlet 类
* 3. 重写 service 方法,用来处理请求
* 4. 设置注解 指定访问的路径
*/
public class ServletTest extends HttpServlet {
/**
* 就绪/ 服务方法 (处理请求数据)
* 系统方法,服务器自动调用
* 当有请求到达 servlet时,就会调用该方法
* 该方法可以被多次调用
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("调用...");
}
/**
* 销毁方法
* 系统方法,服务器会自动调用
* 当服务器关闭或应用程序停止时,调用该方法
* 方法只会执行一次
*/
public void destroy() {
super.destroy();
System.out.println("销毁...");
}
/**
* 初始化方法
* 系统方法,服务器自动调用
* 当请求达到 servlet 容器时,Servlet 容器会判断该 servlet 对象是否存在,如果不存在,则创建实例并初始化
* 方法只会执行一次
*
* @throws ServletException
*/
public void init() throws ServletException {
System.out.println("创建...");
}
} - 当容器关闭时,会将程序中的
Servlet-HttpServlet 的两个对象
HttpServletRequest 对象
HtepServletRequest
-
常用方法
方法名称 作用 getRequestURL()获取客户端发出请求时的完整 URLgetRequestURI获取请求行中的资源名称部分 (项目名称开始) getQueryString()获取请求行中的参数部分 getMethod()获取客户端请求方式 getProtocol()获取 HTTP版本号 getContexPath()获取 webapp名字 String getParameter(String name)根据表单组件名称获取提交数据 void setCharacterEncoding(String charset)指定每个请求的编码
HttpServletResponse 对象
-
常用方法
方法名称 作用 setHeader(name,value)设置响应信息头 setContentType(String)设置响应文件类型、响应式的编码格式 setCharacterEncoding(String)设置服务器端响应内容编码格式 getWriter()获取字符输出流
Servlet-JDBC-Pool
-
数据库表结构
1
2
3
4
5
6
7
8
9mysql> desc userinfo;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(10) | NO | | NULL | |
| password | varchar(18) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
3 rows in set (0.12 sec) -
源码阅读
request 实现转发跳转
-
转发作用在服务器,
将请求发送给服务器上的其他资源,以共同完成一次请求的处理 -
页面跳转
在调用业务逻辑的
Servlet中, 编写一下代码: 1
2
3request.getRequestDisaptcher("/utl-pattern").forward(request,response);
注意: 使用 forward 跳转时,是在服务器内部跳转,地址栏不发生变化,属于同一次请求 -
数据传递
forward表示一次请求,是在服务器内部跳转,可以共享同一次 request作用域中的数据 request作用域: 拥有存储数据的空间,作用范围是一次请求有效 - 可以将数据存入
request后,在一次请求过程中的任何位置进行获取 - 可传递任何数据
(基本数据类型,对象,数组,集合等)
- 可以将数据存入
- 存数据:
request.setAttribuite(key,value),以键值对存储在 request作用域中, key为 string类型, value为 Object类型 - 取数据:
request.getAttribute(key)- 通过
String类型的 key访问 Object类型的 value
- 通过
-
转发的特点
- 转发是服务器行为
- 转发是浏览器只做了一次访问请求
- 转发浏览器地址不变
- 转发两次跳转之间传输的信息不会丢失,所以可以通过
request进行数据传递 - 转发只能将请求转发给同一个
web应用中的组件
重定向
-
重定向作用在客户端,客户端将请求发送给服务器后,服务器响应给客户端一个新的请求地址,客户端重新发送请求
-
页面跳转
-
在调用业务逻辑的
Servlet中,编写以下代码 1
response.sendRedirect("/
目标 uri");
-
-
数据传递
sendRedirect跳转时,地址栏发生变换,代表客户端重新发送新的请求,属于两次请求 response没有作用域,两次 request请求中的数据无法共享 - 传递数据: 通过
uri的拼接进行数据传递 /b?xxx=value - 获取数据:
request.getParameter("key")
Servlet 线程安全问题
-
线程安全问题
Servlet在访问之后,会执行实例化操作,创建一个 Servlet对象,而我们 Tomcat容器可以同时多个线程并发访问同一个 Servlet,如果在方法中对成员变量做修改操作,就会有线程安全的问题 -
如何保证线程安全
synchronized-
将存在线程安全问题的代码放在同步代码块中
-
- 实现
singleThreadMode接口 Servlet实现 singleThreadMode接口后,每个线程都会创建 Servlet实例,这样每个客户端请求就不存在共享资源的问题,但是 Servlet响应客户端请求的效率太低,所以已经淘汰 -
尽可能使用局部变量
状态管理
-
分类
- 客户端状态管理技术: 将状态保存在客户端,代表性的是
Cookie技术 - 服务器状态管理技术: 将状态保存在服务器端,代表性的是
Session技术 (服务器传递 session时需要使用 sessionID时需要使用 Cookie的方式) 和 application
- 客户端状态管理技术: 将状态保存在客户端,代表性的是
Cookie 的使用
1 |
package com.coderitl.cookie; |

1 |
|
1 |
|
Cookie 默认不支持中文,只能包含ASCII
Cookie
Unicode
- 编码可以使用
java.net.URLEncoder类的 encode(String str,String endcoding)方法 - 解码使用
java.net.URLDecoder类的 decode(String str,String encoding)方法
1 |
// 创建时编码 |
- 优点:
- 可配置到期规则
- 简单性:
Cookie是一种基于文本的轻量结构,包含简单的键值对 - 数据持久性:
Cookie默认在过期之前是可以一直存在客户端浏览器上的
- 缺点:
- 大小受限制,大多数浏览器对
Cookie的大小有 4k,8k字节的限制 - 用户默认为禁用: 有些用户禁用了浏览器或客户端设备接受
Cookie的能力,因此限制了这一功能 - 潜在的安全风险:
Cookie可能会被篡改, 回对安全性造成潜在风险或者导致依赖于 Cookie的应用程序失败
- 大小受限制,大多数浏览器对
-
前端页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<hanla></hanla>登录<hanla></hanla>
<style>
body {
line-height: 35px;
background-color: #fff;
}
.container {
width: 560px;
margin: 100px auto;
}
.container form {
padding: 25px;
/* 边框 */
border: 1px solid rgba(204, 204, 204, .2);
/* 阴影 */
box-shadow: 0 0 10px 10px rgba(204, 204, 204, .3);
/* 圆角 */
border-radius: 10px;
text-align: right;
padding-right: 100px;
}
.btn {
position: relative;
bottom: 10px;
left: 50%;
margin-top: 50px;
transform: translate(-50%);
display: block;
outline: none;
width: 150px;
color: #fff;
background-color: #409eff;
border-color: #409eff;
}
input {
background-color: #fff;
border-radius: 4px;
box-sizing: border-box;
color: #606266;
display: inline-block;
outline: none;
padding: 0 15px;
transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
width: 60%;
height: 40px;
line-height: 40px;
cursor: pointer;
border: 1px solid #dcdfe6;
}
/* input 获取焦点后 */
input:focus {
outline: none;
border-color: #409eff;
}
form h1 {
text-align: center;
margin-left: 20px;
}
form .remember-me {
text-align: left;
margin-left: 108px;
}
form .remember-me input {
width: 60px;
margin: 16px 0px 16px -20px;
height: 20px;
line-height: 20px;
border-radius: 3px;
vertical-align: center;
padding-top: 2px;
}
.code {
width: 25%;
}
.capt img {
vertical-align: middle;
}
</style>
</head>
<body>
<h1>
测试获取 cookie : ${cookie.username.value}
</h1>
<div class="container">
<form action="/loginServlet" method="post">
<h1> 登 录 </h1>
"text" name="username" value="${cookie.username.value}"></p>用户名:
"password" name="password" value="${cookie.password.value}"></p>密 码:
<p class="capt">验证码: "text" name="code" class="code">
<img src="/ValidateCodeServlet" width="130" height="35" id="validateCodeImg" title="点击刷新"
alt="点击刷新">
</p>
<p class="remember-me">记住我: "checkbox" name="remember-me" value="1"></p>
<input type="submit" value="登录" class="btn">
</form>
</div>
<script>
// 点击刷新验证码
let validateCodeImgId = document.querySelector("#validateCodeImg");
validateCodeImgId.onclick = function () {
validateCodeImgId.src = "ValidateCodeServlet?m=" + Math.random();
console.log("点击");
}
</script>
</body>
</html> -
自动登录
控制器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66package com.example.controller;
import com.example.constant.Constant;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录请求处理
*/
public class LoginController extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取请求参数 username && password
String username = req.getParameter("username");
String password = req.getParameter("password");
String code = req.getParameter("code");
// remember-me
String rememberMe = req.getParameter("remember-me");
// 日志记录
log.error("username: [{}]", username);
log.error("password: [{}]", password);
log.error("code: [{}]", code);
// 获取 session 作用域中存储的 vCode
String vCode = req.getSession().getAttribute("vCode").toString();
if (username != null && password != null && code != null) {
// 判断复选框是否选中
if ("1".equalsIgnoreCase(rememberMe)) {
// 勾选复选框
Cookie remember_me_username = new Cookie("username", username);
Cookie remember_me_password = new Cookie("password", password);
// 设置存活时间 60(s) * 60(min) * 24(h) * 7(day)
remember_me_username.setMaxAge(60 * 60 * 24 * 7);
remember_me_password.setMaxAge(60 * 60 * 24 * 7);
// 相应到客户端
resp.addCookie(remember_me_username);
resp.addCookie(remember_me_password);
}
// TODO: 修改为自己所需要的信息 比较用户是否存在
if (Constant.LOGIN_USERNAME.equals(username) && Constant.LOGIN_PASSWORD.equals(password) && vCode.equalsIgnoreCase(code)) {
// 存储用户名
req.getSession().setAttribute("username", username);
// 转发: /index 携带数据(用户名)
req.getRequestDispatcher("/index.jsp").forward(req, resp);
}
} else {
// 再次回到 login.jsp
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
} -
访问
勾选 记住我,实现自动登录原理:生成用户名和密码的 cookie

Session 的使用
-
概述
-
Session用于记录用户的状态,Session指的是在一段时间内,单个客户端与Web服务器的一连串相关的交互过程 -
在一个
Session中,客户可能会多次请求访问同一个资源,也有可能请求访问各种不同的服务器资源
-
-
Session
原理 -
服务器会为每一次会话分配一个
Session对象 -
同一个浏览器发起的多次请求,同属于一次会话
( Session) -
首次使用到
Session时,服务器会自动创建 Sessoin,并创建Cookie存储 SessionID发送回客户端
-
-
Session
的使用 Session作用域: 拥有存储数据的空间,作用范围是一次会话有效 - 一次会话是使用同一浏览器发送的多次请求,一旦浏览器关闭,则会话结束
- 可以将数据存入
Session中,在一次会话的任意位置进行获取 - 可传递任何数据
(基本数据类型,对象,集合,数组)
-
基本使用
1
2
3HttpSession session = request.getSession();
// 获取 SessionID
System.out.println(session.getId());1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 设置
HttpSession session = request.getSession();
// 1. 使用 Session 保存数据
session.setAttribute("username", "coder-itl");
// 获取 sessionId
System.out.println(session.getId());
// 获取 Session
HttpSession session = request.getSession();
String username = (String) session.getAttribute("username");
System.out.println("从Session 中获得了: " + username);
// 移除 Session
HttpSession session = request.getSession();
session.removeAttribute("username"); -
Session
与 Request 应用区别 -
request是一次请求有效,请求改变,则 request改变 -
session是一次会话有效,浏览器改变,则 session改变
-
-
Session
的生命周期 -
开始: 第一次使用到
Session的请求产生,则创建 Session -
结束:
-
浏览器关闭,则失效
-
Session超时,则失效session.setMaxInactiveInterval(seconds);// 设置最大有效时间(单位: 秒)
-
手工销毁,则失效
-
session.invalidate(); // 登录退出,注销1
2
3
4
5
6
7
8
9
10
11
12// 设置生命周期 lifeServlet
session.setMaxInactiveInterval(10);
System.out.println(session.getId()); // 10s之前: F1F211AA96207EE7EA51F66D83131C70
// GetServlet
HttpSession session = request.getSession();
// 查看 10s 后的 sessionId
System.out.println(session.getId()); // 10s之前: F1F211AA96207EE7EA51F66D83131C70
再次获取:
10s之后: 80F991614ED7C969A895D5952B4C4693
-
-
-
浏览器禁用 Cookie 解决方案
-
浏览器禁用
Cookie的后果 服务器在默认情况下,会使用
Cookie 的方式将 SessionId发送给浏览器,如果用户禁用 Cookie,则 sessionId不会被浏览器保存,此时,服务器可以使用 URL充血这样的方式来发送 SessionId -
URL 重写
浏览器在访问服务器上的某个地址时,不再使用原来的那个地址,而是使用经过改写的地址,
在原来的地址后面加上sessionId -
实现
URL 重写 1
2
3
4
5
6
7
8
9
10response.encodeRedirectURL(String url) 生成重写的 URL
-----------------------------------------------------
HttpSession session = request.getSession();
// 获取新的 url
String newUrl = response.encodeRedirectURL("MavenProject_war_exploded/getS");
System.out.println(newUrl);
// 重定向到带有 sessionId 的URL
response.sendRedirect(newUrl);
ServletContext 对象
获取 ServletContext 对象的三种方式
-
获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 获取 ServletContext 对象 推荐
ServletContext servletContextThis = this.getServletContext();
// 推荐写法
ServletContext servletContextReq = req.getServletContext();
HttpSession session = req.getSession();
ServletContext servletContextSession = session.getServletContext();
/* org.apache.catalina.core.ApplicationContextFacade@42d820 */
System.out.println(servletContextThis);
/* org.apache.catalina.core.ApplicationContextFacade@42d820 */
System.out.println(servletContextReq);
/* org.apache.catalina.core.ApplicationContextFacade@42d820 */
System.out.println(servletContextSession);
ServletContext 作用
-
获取项目真实路径
1
2
3
4
5
6获取当前项目在服务器发布的真实路径
/* 获取项目真实路径 */
String realPath = servletContextThis.getRealPath("/");
/* This project realPath input: E:\Code\Java\VerificationCode\target\VerificationCode-1.0-SNAPSHOT\ */
System.out.println("This project realPath input: " + realPath); -
获取项目上下文路径
1
2
3
4
5
6
7获取当前项目上下文路径 (应用程序名称)
/* 获取上下文路径 */
String contextPath = request.getContextPath();
String contextPath = servletContextThis.getContextPath();
/* Get context path output: /VerificationCode_war_exploded */
System.out.println("Get context path output: " + contextPath); -
全局容器
1
ServletContext 拥有作用域,可以存储数据到全局容器中
- 存储数据:
servletContextThis.setAttribute("name",value); - 获取数据:
servletContextThis.getAttribute("name"); - 移除数据:
servletContextThis.removeAttribute("name");
- 存储数据:
-
ServletContext特点 - 唯一性: 一个应用对应一个
ServletContext - 生命周期: 只要容器不关闭或者应用不卸载,
ServletContext就一直在
- 唯一性: 一个应用对应一个
Filter 使用
-
含义
过滤器
( Filter)是处于客户端与服务器目标资源之间的一道过滤技术 -
执行流程

- 执行地位在
Servlet之前, 客户端发送请求时, 会经过 Filter,再达到目标Servlet中, 响应时, 会根据执行流程再次反向执行 Filter- 可以解决多个
Servlet共性代码的冗余问题 ( Eg:)乱码处理、登录验证
Servlet API中提供了一个Filter接口, 开发人员会编写一个 Java类实现这个接口即可, 这个 Java类称为 过滤器
-
实现过程
- 编写
Java类实现 Filter接口 - 在
doFilter方法中编写拦截逻辑 - 设置拦截路径
- 编写
-
基本使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20Filter:
package com.example.filter;
// Filter包在 javax.servlet.
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
public class FilterTest implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("------Filter-----------");
// 放行
chain.doFilter(request, response);
System.out.println("----------end--------------");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22Servlet:
package com.example.controller;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("--------------Servlet------------");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
} -
实际执行流程分析

-
xml配置 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15web.xml:
<filter>
<!-- 名称 -->
<filter-name>FilterName</filter-name>
<!-- 过滤器类全称 -->
<filter-class>com.example.filter.XMLFilter</filter-class>
</filter>
<!-- 映射路径配置 -->
<filter-mapping>
<filter-name>FilterName</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping> -
执行分析

-
过滤器链
客户端在服务器请求后,服务器调用
Servlet之前会执行一组过滤器 (多个过滤器), 那么这组过滤器就称为一条 过滤器链每个过滤器实现某个特定的功能,
当第一个 Filter的 doFilter方法被调用时, Web服务器会创建一个代表 Filter链的 FilterChain对想传递给该方法, 在 doFilter方法中, 开发人员如果调用了 FilterChain对想的 doFilter方法, 则 Web服务器会检查 FilterChain对象中是否还有 filter,如果有, 则调用第 2个 filter,如果没有,则调用目标资源 -
过滤器链执行流程
