Servlet

Servlet

第一节 概述

  Servlet用于接收和响应终端用户的请求。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类。Servlet运行于支持Java的应用服务器中。从实现上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。

  所有Servlet都实现了 javax.servlet.Servlet 接口,定义了几个核心方法。大部分Servlet都继承自抽象类GenericServlet,其和Servlet接口都不依赖于具体通信协议。而抽象类HttpServlet响应HTTP请求,继承了GenericServlet,并实现了只接受HTTP请求的service方法。

针对各种HTTP方法类型的方法的空实现


第二节 实战

2.1 Maven依赖

1
2
3
4
5
6
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

2.2 创建一个Servlet

  此Servlet接受HTTP请求,并对未重写的HTTP Servlet方法返回一个HTTP状态405作为响应。对于如实现了GET方法会接受GET请求并作出响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyServlet extends HttpServlet {

//对GET请求进行响应,Web容器处理请求,并解析
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("Hello");
}

//初始化方法
@Override
public void init() throws ServletException {
System.out.println("Servlet " + this.getServletName() + " has started.");
}

//销毁方法
@Override
public void destroy() {
System.out.println("Servlet " + this.getServletName() + " has stopped.");
}
}

2.3 通过web.xml配置部署Servlet

  web.xml文件的作用是对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
<?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_3_1.xsd"
version="3.1">
<!--表示应用在服务器中的名字-->
<display-name>Hello World Application</display-name>

<!--告知Web容器创建Servlet实例-->
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>com.enix.java_web.MyServlet</servlet-class>
<!--使Servlet在Web应用程序启动后立刻启动,而不是等待第一次请求时再调用init()-->
<load-on-startup>1</load-on-startup>
</servlet>

<!--将Servlet映射到URL-->
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/greeting</url-pattern>
<url-pattern>/add</url-pattern>
<url-pattern>/select</url-pattern>
</servlet-mapping>

</web-app>

  配置IDE启用本地Tomcat实例或通过Spring Boot引入内嵌Tomcat,然后通过浏览器访问测试,观察效果:(根据自行配置的访问路径而变)http://localhost:8080/greeting

2.4 使用参数和接受表单提交

  更新代码后重启服务,并测试新的URL观察结果。

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
//此部分注解可以代替XML配置文件
@WebServlet(
name = "myServlet",
urlPatterns = {"/greeting", "/salutation", "wazzup"},
loadOnStartup = 1
)
public class MyServlet extends HttpServlet {
private static final String DEFAULT_USER = "Guest";

//对GET请求进行响应,Web容器处理请求,并解析
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//检测请求中是否包含user参数,如果未包含,就使用DEFAULT_USER常量
String user = req.getParameter("user");
if(user == null)
user = MyServlet.DEFAULT_USER;

resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");

PrintWriter writer = resp.getWriter();
//将响应的内容类型设置为text/html,并将字符编码设置为UTF-8
//从响应中获得一个PrintWriter,并输出一个兼容于HTML5的文档(注意HTML5的DOCTYPE),其中包括问候(现在它将问候某个特定的用户)和一个用于提供用户名的表单
writer.append("<!DOCTYPE html>\r\n")
.append("<html>\r\n")
.append(" <head>\r\n")
.append(" <title>Hello User Application</title>\r\n")
.append(" </head>\r\n")
.append(" <body>\r\n")
.append(" Hello, ").append(user).append("!<br/><br/>\r\n")
.append(" <form action=\"greeting\" method=\"POST\">\r\n")
.append(" Enter your name:<br/>\r\n")
.append(" <input type=\"text\" name=\"user\"/><br/>\r\n")
.append(" <input type=\"submit\" value=\"Submit\"/>\r\n")
.append(" </form>\r\n")
.append(" </body>\r\n")
.append("</html>\r\n");
}

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

  上述例子POST请求只是简单委托给了doGet方法来处理,除了这样的单值参数,当然还可以处理多值参数,参考如下代码清单。

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
@WebServlet(
name = "multiValueParameterServlet",
urlPatterns = {"/checkboxes"}
)
public class MultiValueParameterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");

PrintWriter writer = resp.getWriter();
writer.append("<!DOCTYPE html>\r\n")
.append("<html>\r\n")
.append(" <head>\r\n")
.append(" <title>Hello User Application</title>\r\n")
.append(" </head>\r\n")
.append(" <body>\r\n")
.append(" <form action=\"checkboxes\" method=\"POST\">\r\n")
.append("Select the fruits you like to eat:<br/>\r\n")
.append("<input type=\"checkbox\" name=\"fruit\" value=\"Banana\"/>")
.append(" Banana<br/>\r\n")
.append("<input type=\"checkbox\" name=\"fruit\" value=\"Apple\"/>")
.append(" Apple<br/>\r\n")
.append("<input type=\"checkbox\" name=\"fruit\" value=\"Orange\"/>")
.append(" Orange<br/>\r\n")
.append("<input type=\"checkbox\" name=\"fruit\" value=\"Guava\"/>")
.append(" Guava<br/>\r\n")
.append("<input type=\"checkbox\" name=\"fruit\" value=\"Kiwi\"/>")
.append(" Kiwi<br/>\r\n")
.append("<input type=\"submit\" value=\"Submit\"/>\n")
.append(" </form>\r\n")
.append(" </body>\r\n")
.append("</html>\r\n");
}

//通过POST请求发送多值参数请求
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String[] friuts = req.getParameterValues("fruit");
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");

PrintWriter writer = resp.getWriter();
writer.append("<!DOCTYPE html>\r\n")
.append("<html>\r\n")
.append(" <head>\r\n")
.append(" <title>Hello User Application</title>\r\n")
.append(" </head>\r\n")
.append(" <body>\r\n")
.append(" <h2>Your Selections</h2>\r\n");

if(friuts == null)
writer.append(" You did not select any fruits.\r\n");
else{
writer.append(" <ul>\r\n");
for (String fruit : friuts){
writer.append(" <li>").append(fruit).append("</li>\r\n");
}
writer.append(" </ul>\r\n");
}
writer.append(" </body>\r\n")
.append("</html>\r\n");
}
}

2.5 使用初始化参数配置应用程序

  在编写Java Web程序时,不可避免地需要提供一些配置应用程序和其中Servlet的方式。实现的技术有很多,不同的配置方式都可以完成配置任务。

2.5.1 使用上下文初始化参数

  之前的代码案例中通过注解的方式来代替了一些XML文件的Servlet声明和映射。但仍有一些配置需要通过部署描述符才能完成,上下文初始化参数就是其中之一。

  我们可以在web.xml中通过<context-param>标签来声明上下文参数。

1
2
3
4
5
6
7
8
9
<!--创建上下文初始化参数-->
<context-param>
<param-name>settingOne</param-name>
<param-value>foo</param-value>
</context-param>
<context-param>
<param-name>settingTwo</param-name>
<param-value>bar</param-value>
</context-param>

  然后在Servlet中读取参数内容,所有Servlet都能共享这些初始化参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@WebServlet(
name = "contextParamterServlet",
urlPatterns = {"/contextParameters"}
)
public class ContextParamterServlet extends HttpServlet {

//获取web.xml中配置的上下文参数
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext c = this.getServletContext();
PrintWriter writer = resp.getWriter();

writer.append("settingOne: ").append(c.getInitParameter("settingOne"))
.append(", settingTwo: ").append(c.getInitParameter("settingTwo"));
}
}

2.5.2 使用Servlet初始化参数

  有时我们需要使某些设置只作用于某个Servlet,此时需要使用Servlet初始化参数。如以下ServletParamterServlet需要获取参数database以及server。

1
2
3
4
5
6
7
8
9
10
11
public class ServletParamterServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext c = this.getServletContext();
PrintWriter writer = resp.getWriter();

writer.append("database: ").append(c.getInitParameter("database"))
.append(", server: ").append(c.getInitParameter("server"));
}
}

  需要在XML文件中添加相关配置内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--创建Servlet初始化参数-->
<servlet>
<servlet-name>servletParamterServlet</servlet-name>
<servlet-class>com.enix.java_web.ServletParamterServlet</servlet-class>
<init-param>
<param-name>database</param-name>
<param-value>CustomerSupport</param-value>
</init-param>
<init-param>
<param-name>server</param-name>
<param-value>10.0.12.5</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>servletParamterServlet</servlet-name>
<url-pattern>/servletParamters</url-pattern>
</servlet-mapping>

  当然也可以通过注解的方式来配置Servlet初始化参数,这样的配置方式更简洁方便,当然代码配置的缺点就是一旦修改就需要重新编译程序,而通过部署描述符配置初始化参数只需要改变几行XML代码,再重启应用即可。

1
2
3
4
5
6
7
8
9
10
11
@WebServlet(
name = "servletParamterServlet",
urlPatterns = {"/servletParamters"},
initParams = {
@WebInitParam(name = "database", value = "CustomerSupport"),
@WebInitParam(name = "server", value = "10.0.12.5")
}
)
public class ServletParamterServlet extends HttpServlet {
......
}

2.6 通过表单上传文件

  Apache Commons为文件上传单独设立了一个项目,即Commons FileUpload,所以接受文件上传首先要添加一个第三方依赖。Java EE 6中的Servlet 3.0添加了multipart配置选项,并为HttpServletRequest接口添加了getPart和getParts方法来支持文件上传。

  通过案例TicketServlet演示对文件上传请求的处理方式。

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
@WebServlet(
name = "ticketServlet",
urlPatterns = {"/tickets"},
loadOnStartup = 1
)
@MultipartConfig( //此注解标识当前Servlet支持文件上传
//location属性告知浏览器要在哪里存储临时文件,可忽略
fileSizeThreshold = 5_242_880, //5MB,需要多大才能写入临时目录,小于5MB的文件保存在内存,请求完成后由GC回收
maxFileSize = 20_971_520L, //20MB,限制上传文件大小
maxRequestSize = 41_943_040L //40MB,限制请求大小
)
public class TicketServlet extends HttpServlet {
private volatile int TICKET_ID_SEQUENCE = 1;
private Map<Integer, Ticket> ticketDatabase = new LinkedHashMap<>();

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = Optional.ofNullable(req.getParameter("action")).orElse("list");
//操作/执行器模式
switch (action){
case "create" :
this.showTicketForm(resp);
break;
case "view" :
this.viewTicket(req,resp);
break;
case "download" :
this.downloadAttachment(req,resp);
break;
default:
this.listTickets(resp);
break;
}
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = Optional.ofNullable(req.getParameter("action")).orElse("list");
switch (action){
case "create" :
this.createTicket(req,resp);
break;
case "download" :
default:
resp.sendRedirect("tickets"); //重定向,当action缺失或无效,重定向到tickets显示票据的页面
break;
}
}

private void createTicket(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Ticket ticket = new Ticket();
ticket.setCustomerName(req.getParameter("customerName"));
ticket.setSubject(req.getParameter("subject"));
ticket.setBody(req.getParameter("body"));

// Part filePart = req.getPart("file1");
// if(filePart != null){
// Attachment attachment = this.processAttachment(filePart);
// if(attachment != null)
// ticket.addAttachment(attachment);
// }
Optional<Part> filePart = Optional.ofNullable(req.getPart("file1"));
ThrowingFunction<Part,Attachment,IOException> function = this::processAttachment;
Optional<Attachment> attachment = filePart.map(function);
if(attachment.isPresent()){
ticket.addAttachment(attachment.get());
}

int id;
synchronized (this){
id = this.TICKET_ID_SEQUENCE++;
this.ticketDatabase.put(id,ticket);
}

resp.sendRedirect("tickets?action=view&ticketId=" + id);
}

private Attachment processAttachment(Part filePart)throws IOException{
InputStream inputStream = filePart.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

int read;
final byte[] bytes = new byte[1024];

while ((read = inputStream.read(bytes)) != -1){
outputStream.write(bytes,0,read);
}

Attachment attachment = new Attachment();
attachment.setName(filePart.getSubmittedFileName());
attachment.setContents(outputStream.toByteArray());

return attachment;
}

private void showTicketForm(HttpServletResponse resp){
......
}

private void viewTicket(HttpServletRequest req, HttpServletResponse resp){
......
}

private void downloadAttachment(HttpServletRequest req, HttpServletResponse resp){
......
}

private void listTickets(HttpServletResponse resp){
......
}
}

@FunctionalInterface
public interface ThrowingFunction<T, R, E extends Exception> extends Function<T, R> {
default R apply(T t){
try {
return applyThrows(t);
}catch (Exception ex){
System.out.println("handling an exception...");
throw new RuntimeException(ex);
}
}
R applyThrows(T t) throws E;
}

2.7 编写多线程安全的应用程序

  Web应用是天然的多线程程序,任意的时间点都可能有多人在访问同个应用。Java EE中,Web容器通常会有连接池或执行池。当容器收到请求时,会去池中寻找可用的线程,若找不到则会进入一个先进先出队列中等待。

  正常的流程中,请求和线程的关联贯穿整个生命周期,线程池集中管理线程可以减少创建和销毁线程产生的开销。如Tomcat中的最大线程池大小默认为200。

  多线程环境下的共享资源需要进行同步保护,多线程编程的相关内容这里就不多做扩展。


第三节 源码剖析

3.1 Servlet接口

1
2
3
4
5
6
7
8
9
10
11
public interface Servlet {
void init(ServletConfig var1) throws ServletException;

ServletConfig getServletConfig();

void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

String getServletInfo();

void destroy();
}

  service() 方法会处理所有到达的请求,需要根据所使用的协议来解析并处理请求中的数据,然后返回客户端可接受的响应。如果service方法在返回之前未发送任何响应数据到套接字中,客户端可能会检查到网络错误,如“connection reset”。

​ HTTP协议下,此方法应该能识别客户端发送的请求头参数,然后返回正确的HTTP响应,至少包含HTTP头。

3.2 GenericServlet抽象类

  通过代码可以得知,GenericServlet并未自己实现Servlet接口所定义的方法,大部分要么是空函数,要么是通过调用ServletConfig接口对应的实现代码,所以可以先简单理解GenericServlet为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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
private transient ServletConfig config;

public GenericServlet() {
}

//仅仅声明了一下destroy方法
public void destroy() {
}

//通过ServletConfig接口定义的同名方法来根据参数名获取参数
public String getInitParameter(String name) {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameter(name);
}
}

//通过ServletConfig接口定义的同名方法来获取参数名集合
public Enumeration<String> getInitParameterNames() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameterNames();
}
}

public ServletConfig getServletConfig() {
return this.config;
}

//通过ServletConfig接口定义的同名方法来获取ServletContext实例
public ServletContext getServletContext() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletContext();
}
}

public String getServletInfo() {
return "";
}

//指定ServletConfig参数来进行初始化
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}

//无参初始化
public void init() throws ServletException {
}

//打印日志
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}

//打印日志
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}

//抽象方法service
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

//通过ServletConfig接口定义的同名方法来获取Servlet名
public String getServletName() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletName();
}
}
}

3.3 HttpServlet抽象类

  根据HttpServlet源码可以发现其扩展了GenericServlet,只处理HTTP请求,根据HTTP特性分别扩充了对应的METHOD方法,所以我们只要继承并重写对应METHOD方法即可。

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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
public abstract class HttpServlet extends GenericServlet {
//Method方法标识
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");

public HttpServlet() {
}

//doGet请求处理GET方法
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}

}

protected long getLastModified(HttpServletRequest req) {
return -1L;
}

//doHead请求处理HEAD方法,HEAD和GET区别在于只有头信息
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//HttpServletResponse包装为NoBodyResponse
NoBodyResponse response = new NoBodyResponse(resp);
this.doGet(req, response);
response.setContentLength();
}

//doPost请求处理POST方法,提交数据请求
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}

}

//doPut请求处理PUT方法,和POST区别在PUT一般指定了资源的存放位置
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_put_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}

}

//doDelete请求处理DELETE方法,删除某个资源
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_delete_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}

}

//获取HttpServlet所有声明方法
private Method[] getAllDeclaredMethods(Class<? extends HttpServlet> c) {
Class<?> clazz = c;

Method[] allMethods;
for(allMethods = null; !clazz.equals(HttpServlet.class); clazz = clazz.getSuperclass()) {
Method[] thisMethods = clazz.getDeclaredMethods();
if (allMethods != null && allMethods.length > 0) {
Method[] subClassMethods = allMethods;
allMethods = new Method[thisMethods.length + allMethods.length];
System.arraycopy(thisMethods, 0, allMethods, 0, thisMethods.length);
System.arraycopy(subClassMethods, 0, allMethods, thisMethods.length, subClassMethods.length);
} else {
allMethods = thisMethods;
}
}

return allMethods != null ? allMethods : new Method[0];
}

//doOptions请求处理OPTIONS方法,获取当前URL支持的方法,会在头中添加一个"Allow"
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取此HttpServlet所有声明方法
Method[] methods = this.getAllDeclaredMethods(this.getClass());
boolean ALLOW_GET = false;
boolean ALLOW_HEAD = false;
boolean ALLOW_POST = false;
boolean ALLOW_PUT = false;
boolean ALLOW_DELETE = false;
boolean ALLOW_TRACE = true;
boolean ALLOW_OPTIONS = true;

//循环遍历方法集合,根据集合内容判断此HttpServlet是否允许对应METHOD
for(int i = 0; i < methods.length; ++i) {
String methodName = methods[i].getName();
if (methodName.equals("doGet")) {
ALLOW_GET = true;
ALLOW_HEAD = true;
} else if (methodName.equals("doPost")) {
ALLOW_POST = true;
} else if (methodName.equals("doPut")) {
ALLOW_PUT = true;
} else if (methodName.equals("doDelete")) {
ALLOW_DELETE = true;
}
}

//根据各METHOD的允许标识拼接allow字符串
StringBuilder allow = new StringBuilder();
if (ALLOW_GET) {
allow.append("GET");
}

if (ALLOW_HEAD) {
if (allow.length() > 0) {
allow.append(", ");
}

allow.append("HEAD");
}

if (ALLOW_POST) {
if (allow.length() > 0) {
allow.append(", ");
}

allow.append("POST");
}

if (ALLOW_PUT) {
if (allow.length() > 0) {
allow.append(", ");
}

allow.append("PUT");
}

if (ALLOW_DELETE) {
if (allow.length() > 0) {
allow.append(", ");
}

allow.append("DELETE");
}

if (ALLOW_TRACE) {
if (allow.length() > 0) {
allow.append(", ");
}

allow.append("TRACE");
}

if (ALLOW_OPTIONS) {
if (allow.length() > 0) {
allow.append(", ");
}

allow.append("OPTIONS");
}

resp.setHeader("Allow", allow.toString());
}

//doTrace请求处理TRACE方法,TRACE方法有很多漏洞,基本不会被使用,一些服务器还会禁止
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String CRLF = "\r\n";
StringBuilder buffer = (new StringBuilder("TRACE ")).append(req.getRequestURI()).append(" ").append(req.getProtocol());
Enumeration reqHeaderEnum = req.getHeaderNames();

while(reqHeaderEnum.hasMoreElements()) {
String headerName = (String)reqHeaderEnum.nextElement();
buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName));
}

buffer.append(CRLF);
int responseLength = buffer.length();
resp.setContentType("message/http");
resp.setContentLength(responseLength);
ServletOutputStream out = resp.getOutputStream();
out.print(buffer.toString());
}

//service处理所有请求,根据METHOD标识,调用对应的doXX方法,
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) { //处理GET请求
//浏览器请求成功会有Last-Modified标记资源在服务端最后被修改时间
//客户端第二次请求时就会携带"If-Modified-Since"头,用来询问此段时间资源有没有被修改
lastModified = this.getLastModified(req);
if (lastModified == -1L) {//不需要判断lastModified
this.doGet(req, resp);
} else {//需要判断lastModified
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {//未被修改
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {//资源被修改,返回304,内容为空
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) { //处理HEAD请求
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) { //处理POST请求
this.doPost(req, resp);
} else if (method.equals("PUT")) { //处理PUT请求
this.doPut(req, resp);
} else if (method.equals("DELETE")) { //处理DELETE请求
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) { //处理OPTIONS请求
this.doOptions(req, resp);
} else if (method.equals("TRACE")) { //处理TRACE请求
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}

}

private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
if (!resp.containsHeader("Last-Modified")) {
if (lastModified >= 0L) {
resp.setDateHeader("Last-Modified", lastModified);
}

}
}

//最外层service,判断是否为HTTP请求
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
this.service(request, response);
} else {
throw new ServletException("non-HTTP request or response");
}
}
}

3.4 ServletRequest接口

ServletRequest 定义了请求相关的方法,可以看作请求的顶层接口。

  • 获取请求参数getParameter() 等。
  • 确定与请求内容相关的信息getContentType()getContentLength()getCharacterEncoding() 等。
  • 获取请求的内容getReader()getIntputStream() 等。
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
public interface ServletRequest {
Object getAttribute(String var1);

Enumeration<String> getAttributeNames();

//返回请求内容的字符编码,如UTF-8或ISO-8859-1等
String getCharacterEncoding();

void setCharacterEncoding(String var1) throws UnsupportedEncodingException;

//返回请求正文的长度
int getContentLength();

//返回请求正文的长度
long getContentLengthLong();

//返回请求的MIME内容类型,如application/json、application/x-www-form-urlencoded或multipart/form-data等等
String getContentType();

//请求对象参数时若判断包含POST变量会请求InputStream解析变量,InputStream只能被读取一遍
ServletInputStream getInputStream() throws IOException;

//返回参数值,多个值时返回第一个
String getParameter(String var1);

//返回所有参数名的枚举
Enumeration<String> getParameterNames();

//返回参数值的数组
String[] getParameterValues(String var1);

//返回所有参数名-值对的映射
Map<String, String[]> getParameterMap();

String getProtocol();

String getScheme();

String getServerName();

int getServerPort();

//请求内容基于字符编码时,使用此方法来读取字符数据比较简单,如果是二进制则必须使用ServletInputStream
BufferedReader getReader() throws IOException;

String getRemoteAddr();

String getRemoteHost();

void setAttribute(String var1, Object var2);

void removeAttribute(String var1);

Locale getLocale();

Enumeration<Locale> getLocales();

boolean isSecure();

RequestDispatcher getRequestDispatcher(String var1);

/** @deprecated */
String getRealPath(String var1);

int getRemotePort();

String getLocalName();

String getLocalAddr();

int getLocalPort();

ServletContext getServletContext();

AsyncContext startAsync() throws IllegalStateException;

AsyncContext startAsync(ServletRequest var1, ServletResponse var2) throws IllegalStateException;

boolean isAsyncStarted();

boolean isAsyncSupported();

AsyncContext getAsyncContext();

DispatcherType getDispatcherType();
}

3.5 HttpServletRequest接口

  HttpServletRequest接口继承自ServletRequest,提供关于收到请求的额外的与HTTP协议相关的信息,即获取参数、头信息等内容。

​ HTTP参数有两种:查询参数/URI参数、以 application/x-www-form-urlencodedmultipart/form-data 编码的请求正文。可以通过 getParameter() 系列方法获取参数,注意请求的InputStream只能读取一次,重复读取会抛出异常。

  • 获取请求特有的数据,如URL、URI和头:getRequestURI()getRequestURL()getServletPath()getHeader()getHeaderNames() 等。
  • 获取会话和CookiesgetSession()getCookies()
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
public interface HttpServletRequest extends ServletRequest {
String BASIC_AUTH = "BASIC";
String FORM_AUTH = "FORM";
String CLIENT_CERT_AUTH = "CLIENT_CERT";
String DIGEST_AUTH = "DIGEST";

String getAuthType();

//获取Cookie数组
Cookie[] getCookies();

//返回指定头数据的有效时间戳,不能识别为日期会抛出异常
long getDateHeader(String var1);

//返回指定名字的头数据,不区分大小写
String getHeader(String var1);

Enumeration<String> getHeaders(String var1);

//返回请求中所有头数据的名字的枚举
Enumeration<String> getHeaderNames();

//返回某个指定名字的头数据的整型值,无法转整型会抛出异常
int getIntHeader(String var1);

String getMethod();

String getPathInfo();

String getPathTranslated();

String getContextPath();

String getQueryString();

String getRemoteUser();

boolean isUserInRole(String var1);

Principal getUserPrincipal();

String getRequestedSessionId();

//返回请求URL中的服务器路径部分
String getRequestURI();

//返回客户端用于创建请求的完整URI,包括协议HTTP或HTTPS、服务器名、端口号和服务器路径,但不包括后续查询字符串
StringBuffer getRequestURL();

//返回用于匹配Servlet映射的URL部分
String getServletPath();

HttpSession getSession(boolean var1);

//获取Session
HttpSession getSession();

String changeSessionId();

boolean isRequestedSessionIdValid();

boolean isRequestedSessionIdFromCookie();

boolean isRequestedSessionIdFromURL();

/** @deprecated */
boolean isRequestedSessionIdFromUrl();

boolean authenticate(HttpServletResponse var1) throws IOException, ServletException;

void login(String var1, String var2) throws ServletException;

void logout() throws ServletException;

Collection<Part> getParts() throws IOException, ServletException;

Part getPart(String var1) throws IOException, ServletException;

<T extends HttpUpgradeHandler> T upgrade(Class<T> var1) throws IOException, ServletException;
}

3.6 ServletResponse接口

​ 同 ServletRequest 一样定义了响应的一些通用函数。

  • 编写响应正文getOutputStream()getWriter()setCharacterEncoding()setContentType()setContentLength() 等。
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
public interface ServletResponse {
String getCharacterEncoding();

String getContentType();

//获取ServletOutputStream,向响应中输出数据,二进制内容必须ServletOutputStream
ServletOutputStream getOutputStream() throws IOException;

//获取PrintWriter,向响应中输出数据,对于HTML或其他基于字符编码的文本更简单,此方法不能和getOutputStream()同时使用
PrintWriter getWriter() throws IOException;

//需要在getWriter()前调用保证获取的PrintWriter获得正确的字符编码设置
void setCharacterEncoding(String var1);

void setContentLength(int var1);

void setContentLengthLong(long var1);

//需要在getWriter()前调用保证获取的PrintWriter获得正确的字符编码设置
void setContentType(String var1);

void setBufferSize(int var1);

int getBufferSize();

void flushBuffer() throws IOException;

void resetBuffer();

boolean isCommitted();

void reset();

void setLocale(Locale var1);

Locale getLocale();
}

3.7 HttpServletResponse接口

  HttpServletResponse继承自ServletResponse,扩展了Http协议相关内容。

  • 设置头和其他响应属性setHeader()addHeader()setStatus()sendError()sendRedirect() 等。
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
public interface HttpServletResponse extends ServletResponse {
//STATUS CODE 状态码
int SC_CONTINUE = 100;
int SC_SWITCHING_PROTOCOLS = 101;
int SC_OK = 200;
int SC_CREATED = 201;
int SC_ACCEPTED = 202;
int SC_NON_AUTHORITATIVE_INFORMATION = 203;
int SC_NO_CONTENT = 204;
int SC_RESET_CONTENT = 205;
int SC_PARTIAL_CONTENT = 206;
int SC_MULTIPLE_CHOICES = 300;
int SC_MOVED_PERMANENTLY = 301;
int SC_MOVED_TEMPORARILY = 302;
int SC_FOUND = 302;
int SC_SEE_OTHER = 303;
int SC_NOT_MODIFIED = 304;
int SC_USE_PROXY = 305;
int SC_TEMPORARY_REDIRECT = 307;
int SC_BAD_REQUEST = 400;
int SC_UNAUTHORIZED = 401;
int SC_PAYMENT_REQUIRED = 402;
int SC_FORBIDDEN = 403;
int SC_NOT_FOUND = 404;
int SC_METHOD_NOT_ALLOWED = 405;
int SC_NOT_ACCEPTABLE = 406;
int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
int SC_REQUEST_TIMEOUT = 408;
int SC_CONFLICT = 409;
int SC_GONE = 410;
int SC_LENGTH_REQUIRED = 411;
int SC_PRECONDITION_FAILED = 412;
int SC_REQUEST_ENTITY_TOO_LARGE = 413;
int SC_REQUEST_URI_TOO_LONG = 414;
int SC_UNSUPPORTED_MEDIA_TYPE = 415;
int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
int SC_EXPECTATION_FAILED = 417;
int SC_INTERNAL_SERVER_ERROR = 500;
int SC_NOT_IMPLEMENTED = 501;
int SC_BAD_GATEWAY = 502;
int SC_SERVICE_UNAVAILABLE = 503;
int SC_GATEWAY_TIMEOUT = 504;
int SC_HTTP_VERSION_NOT_SUPPORTED = 505;

void addCookie(Cookie var1);

boolean containsHeader(String var1);

String encodeURL(String var1);

String encodeRedirectURL(String var1);

/** @deprecated */
String encodeUrl(String var1);

/** @deprecated */
String encodeRedirectUrl(String var1);

//设置状态码,表示一条可选的错误消息将会输出到响应数据中,重定向到Web容器为客户端提供的错误页面,并清空缓存
void sendError(int var1, String var2) throws IOException;

void sendError(int var1) throws IOException;

//将客户端重定向至另一个URL
void sendRedirect(String var1) throws IOException;

//设置时间戳头数据,会覆盖
void setDateHeader(String var1, long var2);

//设置时间戳头数据,不覆盖
void addDateHeader(String var1, long var2);

//设置头数据,会覆盖
void setHeader(String var1, String var2);

//设置头数据,不覆盖
void addHeader(String var1, String var2);

//设置整型头数据,会覆盖
void setIntHeader(String var1, int var2);

//设置整型头数据,不覆盖
void addIntHeader(String var1, int var2);

//设置HTTP响应状态码
void setStatus(int var1);

/** @deprecated */
void setStatus(int var1, String var2);

//判断当前响应的状态
int getStatus();

//获取响应头
String getHeader(String var1);

Collection<String> getHeaders(String var1);

Collection<String> getHeaderNames();
}

参考博客和文章书籍等:

《JavaWeb高级编程——涵盖WebSockets、Spring Framework、JPA Hibernate和Spring Security》

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容