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()
获取客户端发出请求时的完整 URL
getRequestURI
获取请求行中的资源名称部分 (项目名称开始) 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
,如果没有,则调用目标资源 -
过滤器链执行流程