JaveWeb—Servlet

Servlet是什么

Servlet本质就是一个接口,在servlet-api.jar这个jar包中就定义了Servlet接口。我们知道接口的作用其实就是定义一种规范,Servlet接口定义的则是关于Servlet生命周期以及处理http请求的一种规范。Servlet中定义了5个方法,Servlet容器会通过调用这几个方法与具体的Servlet来交互,包括管理创建、销毁Servlet实例,处理Http请求等。

Servlet接口中的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Servlet {

public void init(ServletConfig config) throws ServletException;

public ServletConfig getServletConfig();

public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;

public String getServletInfo();

public void destroy();
}

Servlet容器

首先,我们从服务端编程说起,如下是使用Socket编写服务端的一个示例:

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
public class ServerSocketHandler implements Runnable {

private Socket socket;

public ServerSocketHandler(Socket socket) {
this.socket = socket;
}

@Override
public void run() {
BufferedReader bufferedReader = null;
PrintWriter printWriter = null;
try {
//SoketInputStream 读数据的方式都是阻塞的
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
printWriter = new PrintWriter(socket.getOutputStream(), true);
String line;
StringBuilder info = new StringBuilder();
//注意:如果客户端发送的一行字符串末尾没有换行符,则readline方法将一直阻塞
while ((line = bufferedReader.readLine()) != null) {
info.append(line).append("\n");
}
System.out.println("Server接收到了客户端发送的信息:\n" + info);
printWriter.println("我是服务端\n我已接收到了请求...");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (printWriter != null) {
printWriter.close();
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}
}
}


public class Server {

private static final int PORT = 9000;
private static final ExecutorService threadPool = Executors.newFixedThreadPool(5);


public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(PORT);
System.out.println("服务器启动了...");
while (true) {
//服务器在此阻塞
Socket socket = serverSocket.accept();
//提交给线程池中去处理请求
threadPool.execute(new ServerSocketHandler(socket));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

}
}

从这个简单的示例可以看出,服务端在处理客户端的网络请求时,主要做了3件事情:

  • 接收请求
  • 处理请求
  • 响应请求

Servlet一整套体系(即Java Servlet API)的引入,就是为了简化这个服务端编程的过程。Servlet容器,顾名思义里面存放着Servlet对象,当客户端发送Http请求时容器会将接收到的请求封装为ServletRequest,并且将响应封装为ServletResponse,然后找到对应的Servlet实例,调用Servlet的service方法处理请求。如下图:

上图中的Tomcat就是最常用的Servlet容器。

web.xml

为了理解地更深入一点,我们需要了解一下web.xml文件。
web.xml文件是Tomcat中Web项目中的一个配置文件,主要用于配置Filter、Listener、Servlet等,但并不是必须的,一个Java Web项目没有web.xml文件也是照样能跑起来的。Tomcat启动的时候,会根据web.xml文件中配置的Servlet、Filter等创建对应的实例,当客户端发送一个请求过来,再根据配置的请求路径找到对应的Servlet实例,将请求交给它去处理。

如下就是一个名为HelloServlet的 Servlet 在web.xml文件中的配置示例:

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.lzumetal.web.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>

@WebServlet注解

在 Servlet3.0 以后,我们也可以不用再web.xml里面配置Servlet,只需要在Servlet类上加上@WebServlet注解就可以配置该Servlet的属性了。

1
2
3
4
5
6
7
8
9
@WebServlet(name = "annoationServlet", urlPatterns = "/test/testAnnotationServlet")
public class AnnotationServlet extends HttpServlet {

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

resp.getWriter().print("from the Servlet by annotation");
}
}

比如基于SpringBoot的项目就没有web.xml文件,想要配置Servlet的话就可以通过注解的方式配置。

Servlet相关的接口和类

前面说到Servlet是接口,具体的实现类就可以处理网络请求,我们编写Servlet时一般不会直接去实现Servlet,而是去继承HttpServletHttpServlet则继承自GenericServlet这个抽象类。下面分析一下这三者。

Servlet接口的方法

方法声明 功能描述
void init(ServletConfig config) 容器在创建好 Servlet 对象后,就会调用此方法。该方法接收一个 ServletConfig 类型的参数,Servlet 容器通过该参数向 Servlet 传递初始化配置信息
ServletConfig getServletConfig() 用于获取 Servlet 对象的配置信息,返回 Servlet 的 ServletConfig 对象
String getServletInfo() 返回一个字符串,其中包含关于 Servlet 的信息,如作者、版本和版权等信息
void service (ServletRequest request,ServletResponse response) 负责响应用户的请求,当容器接收到客户端访问 Servlet 对象的请求时,就会调用此方法。
容器会构造一个表示客户端请求信息的 ServletRequest 对象和一个用于响应客户端的 ServletResponse 对象作为参数传递给 service() 方法。
在 service() 方法中,可以通过 ServletRequest 对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用 ServletResponse 对象的方法设置响应信息
void destroy() 负责释放 Servlet 对象占用的资源。当服务器关闭或者 Servlet 对象被移除时,Servlet 对象会被销毁,容器会调用此方法

GenericServlet


GenericServlet一个重要点是增加了一个成员变量ServletConfig,并且在init(ServletConfig config)方法中对它赋值,再重新定义了一个抽象的init()方法。

1
2
3
4
5
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}

GenericServlet并没有实现service (ServletRequest request,ServletResponse response)方法,需要子类去实现。

HttpServlet

HttpServlet继承了GenericServlet,本身也是个抽象类,不能直接进行实例化。这个类包含了对http协议请求的通用处理,
HttpServlet主要有两大功能,具体如下。

  1. 根据用户请求方式的不同,定义相应的doXxx()方法处理用户请求。例如,与 GET 请求方式对应的 doGet() 方法,与 POST 方式对应的 doPost() 方法。当定义的类继承 HttpServlet 后,只需要根据请求方式重写对应的 doXxx() 方法即可,而不需要重写 service() 方法。
  2. 通过 service() 方法将 HTTP 请求和响应分别强转为 HttpServletRequestHttpServletResponse 类型的对象。

BaseHttpServlet

如果在项目中使用要写Servlet,通常会写一个抽象类继承自HttpServlet,比如类名为BaseHttpServlet,并且把get、post等方法都统一用一个方法去处理。例如:

1
2
3
4
5
6
7
8
9
10

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doService(request, response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doService(request, response);
}

protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

------ 本文完 ------