xiaohuihui
for me

Servlet

2020-10-09 19:35:41
Word count: 7.2k | Reading time: 33min

Servlet

Servlet生命周期

image-20201010205159550

​ Servlet的整个生命周期过程的执行,均由Web服务器负责管理。即Servlet从创建到服务到销毁的整个过程中方法的调用,都是由Web服务器负责调用执行,程序员无法控制其流程。

​ 但程序员可以获取到Servlet的这些生命周期时间点,并可以指定让Servlet做一些具体业务相关的事情。

生命周期方法执行流程图

image-20201010205744853

测试:

  • 编写测试类SomeServlet.class
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
import javax.servlet.*;
import java.io.IOException;

/**
* @Author coderYang
* @Date 2020/10/11 15:14
*/
public class SomeServlet implements Servlet {

public SomeServlet() {
System.out.println("创建SomeServlet实例");
}

@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("初始化SomeServlet实例");
}

@Override
public ServletConfig getServletConfig() {
return null;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("执行SomeServlet实例的service()方法");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {
System.out.println("销毁SomeServlet实例");
}
}
  • 配置web.xml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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-name>some-servlet</servlet-name>
<servlet-class>SomeServlet</servlet-class>
</servlet>

<!--Servlet映射-->
<servlet-mapping>
<servlet-name>some-servlet</servlet-name>
<url-pattern>/some</url-pattern>
</servlet-mapping>

</web-app>
  • 开启tomcat服务器
  • 访问路径http://localhost:8080/web/some
  • 控制台输出:

image-20201011153838778

Servlet特征

  1. Servlet是单例多线程的。
  2. 一个Servlet实例只会执行一次无参构造器与init()方法,并且是在第一次访问时执行。
  3. 用户每提交一次对当前Servlet的请求,就会执行一次service()方法。
  4. 一个Servlets实例只会执行一次destroy()方法,在应用停止时执行。
  5. 由于Servlet是单例多线程的,所以为了保证其线程安全性,一般情况下是不为Servlet类定义可修改的成员变量的。因为每个线程均可修改这个成员变量,会出现线程安全问题。
  6. 默认情况下,Servlet在Web容器启动时是不会被实例化的。

Web容器启动时创建Servlet实例

当创建多个实例的时候,0的优先级最高,会先被实例化,依此往下。

image-20201011161711581

Servlet中的两个map

当Servlet实例被创建后,会得到该Servlet实例的引用,存放到一个Map集合中。该Map集合的key为url,而Value为Servlet实例的引用即Map<String,Servlet>

这里写图片描述

当Web容器从用户请求中分离出URI后,到第一个Map中没有找到其所对应的Servlet实例,则会马上查找到第二个Map,从中找到其所对应类名,再根据反射机制,创建这个Servlet实例,然后再将这个创建好的Servlet的引用放入到第一个Map中

ServletConfig的使用

ServletConfig用来获取web.xml文件中的配置信息

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
import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;

/**
* @Author coderYang
* @Date 2020/10/11 15:14
*/
public class SomeServlet implements Servlet {
private ServletConfig config;
public SomeServlet() {
System.out.println("创建SomeServlet实例");
}

@Override
public void init(ServletConfig servletConfig) throws ServletException {
this.config = servletConfig;
System.out.println("config = "+ config);
}

@Override
public ServletConfig getServletConfig() {
return config;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//获取Servlet的名称
String servletName = config.getServletName();
System.out.println("servletName = "+ servletName);
//获取ServletContext对象
ServletContext servletContext = config.getServletContext();
System.out.println("servletContext = "+servletContext);

//获取所有的初始化参数
Enumeration<String> names = config.getInitParameterNames();
//遍历枚举
while (names.hasMoreElements()){
String name = names.nextElement();
String value = config.getInitParameter(name);
System.out.println(name +"="+ value);
}
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {
System.out.println("销毁SomeServlet实例");
}
}

image-20201012145748169

ServletContext的用法

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
import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;

/**
* @Author coderYang
* @Date 2020/10/13 11:17
*/
public class ServletTest01 implements Servlet {

private ServletConfig config;

public ServletTest01() {
// System.out.println("创建Servlet实例");
}

@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
}

@Override
public ServletConfig getServletConfig() {
return config;
}

@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
//获取ServletContext对象
ServletContext sc = config.getServletContext();

//获取所有初始化参数名
Enumeration<String> names = sc.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String value = sc.getInitParameter(name);
System.out.println(name + "=" + value);
}

//设置域属性,这个域属性是全局的,在另外的Servlet中也能访问,所有Servlet共享
sc.setAttribute("email","xhh@460");
sc.setAttribute("mobile","18881546487");

//获取域属性的值
String email = (String)sc.getAttribute("email");
String mobile = (String)sc.getAttribute("mobile");

System.out.println("email="+email); //xhh@460
System.out.println("mobile="+mobile); //18881546487
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}

< url-pattern/>的设置

(1)精确路径模式:多个url可以访问一个Servlet

image-20201013175252868

(2)通配符路径模式

image-20201013175801610

(3)全路径匹配模式和后缀名匹配模式

image-20201013180919720

但是此处带斜杆的通配符路径模式匹配与后缀名模式不能同时使用,例如/a/*.do等。

urlPattern匹配原则

  • 路径优先与后缀
  • 精确路径优先
  • 最长路径优先匹配

Servlet核心

GenericServlet

​ 在通过实现Servlet接口来定义Servlet时存在一个很不方便的问题:有太多不需要的方法必须要实现。通常我们只关心service()方法,在service()方法中完成业务逻辑,但由于Servlet接口中还存在另外四个方法,所以必须也要实现。

抽象类实现接口

​ 由于Servlet中通常只使用service()方法,其它四个方法基本不用,但也需要实现。

​ 所以我们就定义一个抽象类,让其实现Servlet接口,并以简单方式对service()以外的其它方法进行实现,即要么是空方法体,要么返回null。

​ 而将service()方法声明为抽象方法。这样以后再定义Servlet时就只需要继承自这个通用的抽象类即可,无需再实现Servlet接口了。

​ 这是一种设计模式,称为”缺省构造器”设计模式。

GenericServlet.java

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
import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;

/**
* @Author coderYang
* @Date 2020/10/14 11:05
*/

// 缺省适配器设计模式
public abstract class GenericServlet implements Servlet,ServletConfig{
private ServletConfig config;

@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
}

@Override
public ServletConfig getServletConfig() {
return config;
}

@Override //抽象方法,专门让子类来实现
public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}


/***
* 以下是重写ServletConfig接口的方法
*/
@Override
public String getServletName() {
return config.getServletName();
}

@Override
public ServletContext getServletContext() {
return config.getServletContext();
}

@Override
public String getInitParameter(String name) {
return config.getInitParameter(name);
}

@Override
public Enumeration<String> getInitParameterNames() {
return config.getInitParameterNames();
}
}
GenericServlet中的init方法
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
import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;

/**
* @Author coderYang
* @Date 2020/10/14 11:05
*/

// 缺省适配器设计模式
public abstract class GenericServlet implements Servlet,ServletConfig{
private ServletConfig config;

//模板方法设计模式
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
//调用无参的init()方法
this.init();
System.out.println("======父类init======");
}

//无参init()方法,让子类来重写
public void init(){

}

@Override
public ServletConfig getServletConfig() {
return config;
}

@Override //抽象方法,专门让子类来实现
public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}


/***
* 以下是实现ServletConfig接口的方法
*/
@Override
public String getServletName() {
return config.getServletName();
}

@Override
public ServletContext getServletContext() {
return config.getServletContext();
}

@Override
public String getInitParameter(String name) {
return config.getInitParameter(name);
}

@Override
public Enumeration<String> getInitParameterNames() {
return config.getInitParameterNames();
}
}

以上只是探究源码的方式,真正开发直接继承内部GenericServlet类即可。

HttpServlet

自定义HttpServlet.java
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
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author coderYang
* @Date 2020/10/15 14:27
*/
public class HttpServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("LoginServlet");
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

service(request,response);
}

public void service(HttpServletRequest request,HttpServletResponse response){
//获取请求的提交方式
String method = request.getMethod();
System.out.println("method = " + method);

if ("POST".equals(method)) {
doPost(request, response);
} else if ("GET".equals(method)) {
doGet(request, response);
}
}


public void doGet(HttpServletRequest request, HttpServletResponse response) {

}

public void doPost(HttpServletRequest request, HttpServletResponse response) {

}
}
LoginServlet继承HttpServlet

直接使用即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author coderYang
* @Date 2020/10/14 16:52
*/
public class LoginServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response) {
System.out.println("非法用户");
}

public void doPost(HttpServletRequest request, HttpServletResponse response) {
System.out.println("进入用户验证身份阶段");
}
}
如果get和post请求都支持的话,使用以下写法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author coderYang
* @Date 2020/10/14 16:52
*/
public class LoginServlet extends HttpServlet {

public void doPost(HttpServletRequest request, HttpServletResponse response) {
System.out.println("进入用户验证身份阶段");
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}

HttpServletRequest

请求的生命周期

HttpServletRequest实例对象是什么时候创建和销毁的呢?

​ 当客户端浏览器将请求(字符序列)发送到服务器后,服务器会根据HTTP请求协议的格式对请求进行解析。同时,服务器会创建 HttpServletRequest的实现类RequestFacade的对象,即请求对象。然后调用相应的set方法,将解析出的数据封装到请求对象中。此时HttpServletRequest实例就创建并初始完毕了,也就是说,请求对象是服务器创建的。

​ 当服务器向客户端发送响应结束后,HttpServletRequest实例对象是被服务器销毁。

​ 一次请求对应一个请求对象,另外一次请求对应着另外一个请求对象,与之前的请求对象没有任何关系。HttpServletRequest实例的生命周期很短暂。

请求参数

​ HttpServletRequest对于请求中所携带的参数是以Map的形式接收的,并且该Map的key为String,value为String数组。

​ 为很么value为String数组而不是String?因为Http请求协议允许一个请求参数具有多个值的情况出现。

image-20201019114740495

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
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;
import java.util.Enumeration;
import java.util.Map;

/**
* @Author coderYang
* @Date 2020/10/15 20:53
*
* 1.请求参数是放在Map中的
* 2.这个Map的key为请求参数的名称,为String类型,
* 这个Map的value为请求参数的所有值,为String[]类型
* 3.使用最多的是getParameter()方法,其等价于getParameterValues()[0]
*/
@WebServlet(name = "RegisterServlet")
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
System.out.println("POST"); //POST
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取指定名称的请求参数值
//getParameter("username")方法,本质上等同于getParameterValues("username")[0]
String name = request.getParameter("username");
String ageStr = request.getParameter("age");
String hobby1 = request.getParameterValues("hobby")[1];

Integer age = Integer.valueOf(ageStr);
System.out.println("name = "+name); //name = admin
System.out.println("age = "+age); //age = 20
System.out.println("hobby[1] = "+hobby1); //hobby[1] = swimming

//获取所有的请求参数名称
Enumeration<String> names = request.getParameterNames();
//遍历枚举
while(names.hasMoreElements()){
String eleName = names.nextElement();
String eleValue = request.getParameter(eleName);
System.out.println(eleName + "=" +eleValue); //age=20 hobby=running
}

//获取指定参数的所有值
String[] hobby = request.getParameterValues("hobby");
for (String h :hobby){
System.out.println(h); //running swimming reading
}

//获取存放请求参数的Map
Map<String,String[]> map = request.getParameterMap();
for (String key : map.keySet()){
System.out.println(key + "==>" +request.getParameter(key)); //username==>admin age==>20 hobby==>running
}
}
}
域属性

​ 在Request中也有域属性空间,用于存放有名称的数据。该数据只在当前Request请求中进行访问。

(1)常用方法

对于Request中的域属性操作的方法有:

  • void setAttribute(String name,Object object):在Request域属性空间中放入数据。其生命周期域与Request的生命周期相同。
  • Object getAttribute(String name):获取域属性。
SomeServlet.java
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
import javax.servlet.RequestDispatcher;
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;

/**
* @Author coderYang
* @Date 2020/10/19 16:06
*/
@WebServlet(name = "SomeServlet")
public class SomeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//向请求中放入属性
request.setAttribute("username","小灰灰");
request.setAttribute("address","YunProvince");

//从请求中删除指定域属性
request.removeAttribute("address");

//将请求转发给OtherServlet
request.getRequestDispatcher("/other").forward(request,response);
}
}
OtherServlet.java
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
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;
import java.util.Enumeration;

/**
* @Author coderYang
* @Date 2020/10/19 16:14
*/
@WebServlet(name = "OtherServlet")
public class OtherServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//从请求中获取指定名称的域属性
String username = (String)request.getAttribute("username");
String address = (String)request.getAttribute("address");

//获取请求中的所有域属性名称
Enumeration<String> names = request.getAttributeNames();
while (names.hasMoreElements()){
String name = names.nextElement();
System.out.println(name+"===="+request.getAttribute(name));
/*
javax.servlet.forward.request_uri====/web/some
javax.servlet.forward.context_path====/web
javax.servlet.forward.servlet_path====/some
javax.servlet.forward.mapping====org.apache.catalina.core.ApplicationMapping$MappingImpl@2df42e1f
username====小灰灰
**/
}

System.out.println("username = "+username); //username = 小灰灰
System.out.println("address = "+address); //address = null
}
}
服务端相关信息
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
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;

/**
* @Author coderYang
* @Date 2020/10/20 13:16
*/
@WebServlet(name = "SomeServlet")
public class SomeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的URL
StringBuffer requestUrl = request.getRequestURL();
System.out.println("requestUrl ="+requestUrl); //requestUrl =http://localhost:8080/web/some

//获取请求的URL:URL去掉请求协议及主机的剩余部分
String requestURI = request.getRequestURI();
System.out.println("requestURI = "+requestURI); //requestURI = /web/some

//获取当前Web应用的根路径
String contextPath = request.getContextPath();
System.out.println("contextPath = "+contextPath); //contextPath = /web

//获取客户端ip
String remoteAddr = request.getRemoteAddr();
System.out.println("clientIp = "+remoteAddr); //clientIp = 0:0:0:0:0:0:0:1

//获取UrlPattern的精确部分
String servletPath = request.getServletPath();
System.out.println("servletPath = "+servletPath);

//获取通配符位置的路径
String pathInfo = request.getPathInfo();
System.out.println("pathInfo = "+pathInfo);
}
}

中文乱码问题

在浏览器中表单中输入的中文,一旦由浏览器经过Http协议传输,则这些数据均以字节的形式上传给服务器。因为Http协议的底层使用的是TCP传输协议。TCP(Transmission Control Protocol),传输控制协议,是一种面向连接的、可靠的、基于字节流的、端对端的通信协议。在请求中,这些字节均以%开头,并以十六进制的形式出现,如%5A%3D等。

乱码形成的原因:

​ 当用户通过浏览器提交一个包含UTF-8编码格式的两个字的中文的请求时,浏览器会将这两个中文字符变为六个字节(一般一个UTF-8占用三个字节),即形成六个类似%8E的字节表示形式,并将这六个字节上传至Tomcat服务器。

​ Tomcat服务器在接收到这六个字节之后,并不知道它们原始采用什么字符编码。而Tomcat默认的编码格式为ISO-8859-1。所以会将这六个字节按照ISO-8859-1的格式进行编码,编码后再控制台显示,所以在控制台显示,所以控制台会乱码。

解决POST提交时候的中文乱码问题
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
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;

/**
* @Author coderYang
* @Date 2020/10/20 13:43
*/
@WebServlet(name = "RegisterServlet")
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决POST提交时的中文乱码问题,但无法解决GET提交的中文乱码
//setCharacterEncoding()方法设置了请求正文的字符编码
request.setCharacterEncoding("UTF-8");

String name = request.getParameter("username");
String age = request.getParameter("age");

System.out.println("name = "+name); //name = 小灰灰
System.out.println("age = "+age); //age = 20
}
}
解决GET和POST提交时候的中文乱码问题(通用版)
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
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;

/**
* @Author coderYang
* @Date 2020/10/20 13:43
*/
@WebServlet(name = "RegisterServlet")
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决POST提交时的中文乱码问题,但无法解决GET提交的中文乱码
//setCharacterEncoding()方法设置了请求正文的字符编码
//request.setCharacterEncoding("UTF-8");

//此处接收到的username,其字符编码为ISO8859-1
String name = request.getParameter("username");
//将name字符串按照原编码进行打散
byte[] bytes = name.getBytes("ISO8859-1");
//将bytes字节组按照指定字符编码进行组装,组装为String
name = new String(bytes,"UTF-8");
String age = request.getParameter("age");

System.out.println("name = "+name); //name = 小灰灰
System.out.println("age = "+age); //age = 20
}
}

此方法的缺点是:当请求参数一大的时候,就会很浪费内存。

HttpServletResponse

​ Web服务器收到一个Http请求后,会针对每一个请求创建一个HttpServletRequest对象和HttpServletResponse对象。若需要获取客户端提交请求的相关信息,则需要从HttpServletRequest对象中获取;若需要向客户端发送数据,则需要通过HttpServletResponse对象来完成。

向客户端发送数据

​ ServletResponse接口有一个方法getWriter(),用于获取到一个输出流对象PrintWriter(),该输出流对象是专门用于向客户端浏览器中输出字符数据的,称为标准输出流。

响应乱码的解决方案

​ 若在PrintWriter流中写入中文字符,那么在客户端浏览器中将显示乱码。

解决方法如下:

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
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;
import java.io.PrintWriter;

/**
* @Author coderYang
* @Date 2020/10/20 16:53
*/
@WebServlet(name = "SomeServlet")
public class SomeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应的MIME类型,后面可指定响应体的字符编码
response.setContentType("text/html;charset=UTF-8");

//获取标准输出流
PrintWriter out = response.getWriter();
//向标准输出流写入数据,客户端浏览器就会看到这些数据
out.append("abc");
//但是中文会在浏览器中出现乱码问题
out.write("小灰灰");

//不需要手动关闭,对象销毁时会自动关闭
out.close();
}
}

请求转发与重定向

​ 通过HttpServletRequest获取到的RequestDispathcer对象的forword()方法,可以完成请求转发功能。而通过HttpServletResponse的sendRedirect()方法,可以完成重定向功能。

重定向不保留第一次请求时的数据

image-20201021113129796

image-20201021113347155

请求转发和请求重定向均指的是请求的跳转方式。用户提交请求需要访问服务器的资源1,而资源1又需要访问资源2,那么由资源1到资源2的跳转有两种方式:请求转发与重定向。

请求转发:

someServlet.java

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
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;

/**
* @Author coderYang
* @Date 2020/10/21 11:44
*/
@WebServlet(name = "SomeServlet")
public class SomeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

String name = request.getParameter("username");
String age = request.getParameter("age");

System.out.println("name = "+name); //name=小灰灰
System.out.println("age = "+age); //age

//向request域中放入属性
request.setAttribute("attrName",name);
request.setAttribute("attrAge",age);

//请求转发
request.getRequestDispatcher("/other").forward(request,response);
}
}

otherServlet.java

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
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;
import java.io.PrintWriter;

/**
* @Author coderYang
* @Date 2020/10/21 11:52
*/
@WebServlet(name = "OtherServlet")
public class OtherServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

//因为请求转发过来了,所以此处能获取到someServlet的请求
String name = request.getParameter("username");
String age = request.getParameter("age");

System.out.println("name === "+name); //name === 小灰灰
System.out.println("age === "+age); //age === 20

//从request域中获取属性
String attrName = (String)request.getAttribute("attrName");
String attrAge = (String) request.getAttribute("attrAge");
System.out.println("attrName = "+attrName); //attrName = 小灰灰
System.out.println("attrAge = "+attrAge); //attrAge = 20

PrintWriter out = response.getWriter();
out.println("this is OtherServlet");
}
}
重定向:

SomeServlet.java

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
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;

/**
* @Author coderYang
* @Date 2020/10/21 11:44
*/
@WebServlet(name = "SomeServlet")
public class SomeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//此处接收到的username,其字符编码为ISO8859-1
String name = request.getParameter("username");
String age = request.getParameter("age");

System.out.println("name = "+name);
System.out.println("age = "+age);

//向request域中放入属性
request.setAttribute("attrName",name);
request.setAttribute("attrAge",age);

//重定向
response.sendRedirect(request.getContextPath()+"/other");
}
}

OtherServlet.java

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
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;
import java.io.PrintWriter;

/**
* @Author coderYang
* @Date 2020/10/21 11:52
*/
@WebServlet(name = "OtherServlet")
public class OtherServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

//从request域中获取属性
String attrName = (String)request.getAttribute("attrName");
String attrAge = (String) request.getAttribute("attrAge");
System.out.println("attrName = "+attrName); //attrName = null
System.out.println("attrAge = "+attrAge); //attrAge = null

PrintWriter out = response.getWriter();
out.println("this is OtherServlet");
}
}

因为响应的地址已经被重定向了,所以之前的SomeServlet的请求到的数据在OtherServlet中获取不到了。

如果要在重定向后获取得到之前的数据,则采用以下方式:

1
response.sendRedirect("other?pname=xhh&page=20");
重定向时数据传递的中文乱码问题:
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
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;
import java.net.URLEncoder;

/**
* @Author coderYang
* @Date 2020/10/21 11:44
*/
@WebServlet(name = "SomeServlet")
public class SomeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//此处接收到的username,其字符编码为ISO8859-1
String name = request.getParameter("username");
String age = request.getParameter("age");

System.out.println("name = "+name);
System.out.println("age = "+age);

//向request域中放入属性
request.setAttribute("attrName",name);
request.setAttribute("attrAge",age);

//解决中文乱码问题
name = URLEncoder.encode(name,"UTF-8");

//重定向
response.sendRedirect("other?pname="+name+"&page="+age);
}
}
重定向到其它应用

​ 重定向与请求转发重要的不同点是:重定向可以跳到其它应用中,而请求转发只能在当前应用中跳转。

请求转发与重定向的对比
  1. 请求转发
    • 浏览器只发出一次请求,收到一次响应
    • 请求所转发到的资源中可以直接获取到请求中携带的数据
    • 浏览器地址栏显示的为用户所提交的请求路径
    • 只能跳转到当前应用的资源
  1. 重定向
    • 浏览器发出两次请求,接收两次响应
    • 重定向到的资源不能直接获取到用户提交请求中携带的数据
    • 浏览器地址栏显示的为重定向的请求路径,而非用户提交请求的路径。所以重定向可以防止表单重复提交
    • 重定向不仅可以跳转到当前应用的其它资源,也可以跳转到其它应用的资源
请求转发与重定向的选择
  1. 如果要跳转到其它应用,则使用重定向。
  2. 如果是处理表单数据的Servlet要跳转到其它Servlet,则需要选择重定向。为了防止表单的重复提交。
  3. 若对某一请求进行处理的Servlet的执行需要消耗大量的服务器资源,此时Servlet执行完毕后,也需要重定向。
  4. 其它情况,一般使用请求转发。

RequestDispatcher

​ RequestDispatcher接口有两个方法:forward()和include()。

include的转发模式:

image-20201022141511804

requestDispatcher的forward()和include()的区别

forward()和include()的区别:主要表现在标准输出流的开启时间不同。

forward():表示转发,当前的请求转发给了另一个组件,响应结果由另一个组件响应。所以服务器不会在此组件内打开标准输出流。所以在当前组件中写入到流的数据是不会写入到浏览器中。

include():表示包含,说明另一个组件的请求结果将会响应到当前组件之中,并跟当前组件中的流一起输出(写入到浏览器)。

show me code:

SomeServlet.java

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
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;
import java.io.PrintWriter;

/**
* @Author coderYang
* @Date 2020/10/21 16:39
*/
@WebServlet(name = "SomeServlet")
public class SomeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

/***
*无论是forward()还是include(),对于请求来说,都是一样的
* 它们的不同点主要集中在响应对象
*/

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// System.out.println("request = "+request); //request = org.apache.catalina.connector.RequestFacade@75148595
// System.out.println("response ="+response); //response =org.apache.catalina.connector.ResponseFacade@35c83e12

PrintWriter out = response.getWriter();
out.println("SomeServlet:include() before");

//请求转发
request.getRequestDispatcher("/other").include(request,response);

out.println("SomeServlet:include() after");
}
}

OtherServlet.java

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
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;
import java.io.PrintWriter;

/**
* @Author coderYang
* @Date 2020/10/21 16:55
*/
@WebServlet(name = "OtherServlet")
public class OtherServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// System.out.println("request === "+request); //request === org.apache.catalina.core.ApplicationHttpRequest@4b78c41a
// System.out.println("response === "+response); //response === org.apache.catalina.connector.ResponseFacade@35c83e12

PrintWriter out = response.getWriter();
out.println("OtherServlet:include()");
}
}

输出结果:

image-20201021172633065

Servlet线程安全问题

​ Servlet是在单例多线程环境下运行的。其运行可能出现线程安全问题。

什么时候会出现线程安全问题?
  • 存在多线程并发访问
  • 存在可修改的共享数据

当多个线程同时修改同一个共享数据的时候,后修改的数据会将前修改的数据覆盖,对数据先进行修改的用户读取到的不是自己修改后的数据,这就是线程安全问题。

JVM中可能存在线程安全问题的数据分析
A、栈内存数据分析

​ 栈内存是多例的,即JVM会为每个线程创建一个栈,所以其中的数据是不能共享的。另外,方法中的局部变量存放在Stack的栈帧的,方法执行完毕,栈帧弹栈,局部变量消失。局部变量是局部的,不能共享的。所以栈内存中的数据不存在线程安全问题。

B、堆内存数据分析

​ 一个JVM中只存在一个堆内存,堆内存是共享的。被创建出来的对象是存放在堆内存的,而存放在堆内存中的对象,实际就是对象成员变量的值的集合。即成员变量是存放在堆内存的。堆内存中的数据是多线程共享的,也就是说,堆内存中的数据是存在线程安全问题的。

C、方法区数据分析

​ 一个JVM中只存在一个方法区。静态变量与常量存放在方法区,方法区是多线程共享的。常量是不能被修改的量,所以常量不存在线程安全的问题。静态变量是多线程共享的。所以静态变量存在线程安全问题。

线程安全的解决方案:
  • 对于一般的类,不要定义为单例的。除非项目有特殊需求,或该类对象属于重量级对象。所谓重量级对象是指,创建该类对象时需要占用较大的系统资源。
  • 无论类是否为单例,尽量不要使用静态变量。
  • 若需要定义为单例类,则单例类中尽量不要使用成员变量。
  • 若单例类中必须要使用成员变量,则对成员变量的操作,可以添加串行化synchronized,实现线程同步。不过,最好不要使用线程同步机制。因为一旦操作串行化的排队状态,将大大降低程序的执行效率。

​ Servlet是单例多线程并发访问的,所以其就可能会出现线程安全问题。为了避免线程安全问题的产生,对于Servlet的使用,一般是不声明成员变量的,若要真的声明成员变量,则只能通过线程同步机制synchronized避免。

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
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;
import java.io.PrintWriter;

/**
* @Author coderYang
* @Date 2020/10/23 22:33
*/

@WebServlet(name = "LoginServlet")
public class LoginServlet extends HttpServlet {
private String username;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//对成员变量的修改

username = request.getParameter("username");
System.out.println("username = "+username);

PrintWriter out = response.getWriter();
out.println("username = "+username);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}
}

以上代码如果进行多个http请求就会出现线程安全问题,username的值就会被第二个请求覆盖。

解决方法是在代码块中加入synchronized。

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
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;
import java.io.PrintWriter;

/**
* @Author coderYang
* @Date 2020/10/23 22:33
*/

@WebServlet(name = "LoginServlet")
public class LoginServlet extends HttpServlet {
private String username;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//对成员变量的修改
synchronized (this){
username = request.getParameter("username");
System.out.println("username = "+username);
PrintWriter out = response.getWriter();
out.println("username = "+username);
}
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}
}

Author: 小灰灰

Link: http://xhh460.github.io/2020/10/09/Servlet/

Copyright: All articles in this blog are licensed.

< PreviousPost
Jsp
NextPost >
koa
CATALOG
  1. 1. Servlet
    1. 1.0.1. Servlet生命周期
      1. 1.0.1.1. 生命周期方法执行流程图
      2. 1.0.1.2. Servlet特征
      3. 1.0.1.3. Web容器启动时创建Servlet实例
      4. 1.0.1.4. Servlet中的两个map
      5. 1.0.1.5. ServletConfig的使用
      6. 1.0.1.6. ServletContext的用法
      7. 1.0.1.7. < url-pattern/>的设置
        1. 1.0.1.7.1. (1)精确路径模式:多个url可以访问一个Servlet
        2. 1.0.1.7.2. (2)通配符路径模式
        3. 1.0.1.7.3. (3)全路径匹配模式和后缀名匹配模式
      8. 1.0.1.8. urlPattern匹配原则
    2. 1.0.2. Servlet核心
      1. 1.0.2.1. GenericServlet
        1. 1.0.2.1.1. 抽象类实现接口
        2. 1.0.2.1.2. GenericServlet中的init方法
      2. 1.0.2.2. HttpServlet
        1. 1.0.2.2.1. 自定义HttpServlet.java
        2. 1.0.2.2.2. LoginServlet继承HttpServlet
        3. 1.0.2.2.3. 如果get和post请求都支持的话,使用以下写法:
      3. 1.0.2.3. HttpServletRequest
        1. 1.0.2.3.1. 请求的生命周期
        2. 1.0.2.3.2. 请求参数
        3. 1.0.2.3.3. 域属性
          1. 1.0.2.3.3.1. SomeServlet.java
          2. 1.0.2.3.3.2. OtherServlet.java
        4. 1.0.2.3.4. 服务端相关信息
      4. 1.0.2.4. 中文乱码问题
        1. 1.0.2.4.1. 解决POST提交时候的中文乱码问题
        2. 1.0.2.4.2. 解决GET和POST提交时候的中文乱码问题(通用版)
      5. 1.0.2.5. HttpServletResponse
        1. 1.0.2.5.1. 向客户端发送数据
        2. 1.0.2.5.2. 响应乱码的解决方案
      6. 1.0.2.6. 请求转发与重定向
        1. 1.0.2.6.1. 请求转发:
        2. 1.0.2.6.2. 重定向:
        3. 1.0.2.6.3. 重定向时数据传递的中文乱码问题:
        4. 1.0.2.6.4. 重定向到其它应用
        5. 1.0.2.6.5. 请求转发与重定向的对比
        6. 1.0.2.6.6. 请求转发与重定向的选择
      7. 1.0.2.7. RequestDispatcher
        1. 1.0.2.7.1. requestDispatcher的forward()和include()的区别
      8. 1.0.2.8. Servlet线程安全问题
        1. 1.0.2.8.1. 什么时候会出现线程安全问题?
        2. 1.0.2.8.2. JVM中可能存在线程安全问题的数据分析
          1. 1.0.2.8.2.1. A、栈内存数据分析
          2. 1.0.2.8.2.2. B、堆内存数据分析
          3. 1.0.2.8.2.3. C、方法区数据分析
          4. 1.0.2.8.2.4. 线程安全的解决方案: