Servlet规范和Servlet容器

Servlet是什么

当HTTP服务器在接收到请求就需要调用服务端的应用程序去处理,一般来说,不同请求对应不同Java类。

如果我们将处理不同请求去调用不同处理程序的逻辑写在HTTP服务器中,那么HTTP服务器的代码中就会加入许多if else语句,而且这一逻辑就相当于业务逻辑代码和HTTP服务器代码耦合在了一起,当我们新增删除我们的业务方法的时候还需要改动HTTP服务器的代码。

但是HTTP服务器就应该独立于业务逻辑,所以Servlet就出现了。首先我们可以把Servlet划分为Servlet容器和Servlet类,简单理解的话Servlet容器就是用来解决HTTP服务器和业务代码之间的耦合问题的,而Servlet类有很多中,它分别对应不同的业务代码。

Servlet的作用

对于Servlet,它是一个接口,即它是一个规范,它本身独立于HTTP服务器。但是因为我们日常开发中经常在HTTP的环境中,所以Servlet为我们实现了HttpServlet实现类,我们只需要重写doGet和doPost方法就行了。

浅析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
public interface Servlet {

// 初始化Servlet,Servlet容器在加载该Servlet的时候会调用init方法
// 我们可以再init方法中初始化一些资源
// 比如说SpringMVC中的DispacherServlet就是init方法的时候
// 初始化SpringMVC容器的
void init(ServletConfig var1) throws ServletException;

// ServletConfig是Servlet的配置类,里面存放着Servlet的一些配置信息
// 比如我们再web.xml文件中配置的一些init-param
// 通过这个方法我们可以获取我们的ServletConfig
ServletConfig getServletConfig();

// service是Servlet的核心,具体业务类在这里实现业务逻辑
// 其中ServletRequest封装了请求信息
// ServletResponse封装了相应信息
// 这两个类其实就是对通信协议(格式)的封装
// 比如HTTP协议封装的HttpServletRequest和HttpServletResponse
// 我们可以通过HttpServletRequest来获取所有请求信息,比如请求路径
// HTTP头,Cookie,请求参数等,我们还可以通过它来创建和获取Session
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

// 获取Servlet的信息
String getServletInfo();

// 销毁,我们可以执行一些资源的销毁工作
void destroy();
}

ServletConfig

我们来看一下ServletConfig接口的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface ServletConfig {
// 获取Servlet的名字
String getServletName();

// 获取ServletContext
// 在Servlet中定义了ServletContext接口来对应一个Web应用
// 在Web应用部署好之后,Servlet容器在启动时会加载Web应用
// 并为每个Web应用创建一个ServletContext对象
// ServletContext是一个全局对象,一个Web应用可能会有多个Servlet
// 这些Servlet通过全局的ServletContext来共享数据
// 比如说Web应用的初始化参数,Web应用目录下的文件资源等
// 由于ServletContext持有所有的Servlet实例
// 所以我们可以通过ServletContext来实现Servlet的请求的转发
ServletContext getServletContext();

// 获取初始化参数,在Servlet在web.xml文件中每个Servlet
// 可能会自定义一些初始化参数
String getInitParameter(String var1);

// 获取初始化参数的名字
// 上面的方法对应param-value 这里对应param-name
Enumeration<String> getInitParameterNames();
}

ServletContext

ServletContext接口源码

ServletContext中定义了很多方法,我们可以将它分类几类并且做功能说明

  1. 多个Servlet通过ServletContext对象实现数据共享。通过setAttribute等属性设置获取删除方法等

  2. 实现Servlet的请求转发。 获取分发器getRequestDispatcher方法

    这里就不得不提一下重定向和请求转发的区别了。我们知道ServletContext拥有着所有Servlet实例,所以我们可以通过ServletContext对象来实现服务器内部的请求转发,比如将这个请求交给其他Servlet去处理,这个是服务器的内部行为,所以URL是不变的。还有就是重定向,即两次request请求,所以URL是会变的。

    1
    getServletConfig().getServletContext().getRequestDispatcher("xx").forward(request,response);
  3. 获取Web应用的初始化参数,或者设置获取全局参数。 例如getInitParameter方法等

  4. 利用ServletContext对象读取资源文件(比如properties文件)

  5. 过滤器和监听器

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
public interface ServletContext {
String TEMPDIR = "javax.servlet.context.tempdir";
String ORDERED_LIBS = "javax.servlet.context.orderedLibs";

String getContextPath();

ServletContext getContext(String var1);

int getMajorVersion();

int getMinorVersion();

int getEffectiveMajorVersion();

int getEffectiveMinorVersion();

String getMimeType(String var1);

Set<String> getResourcePaths(String var1);

URL getResource(String var1) throws MalformedURLException;

InputStream getResourceAsStream(String var1);

RequestDispatcher getRequestDispatcher(String var1);

RequestDispatcher getNamedDispatcher(String var1);

/** @deprecated */
@Deprecated
Servlet getServlet(String var1) throws ServletException;

/** @deprecated */
@Deprecated
Enumeration<Servlet> getServlets();

/** @deprecated */
@Deprecated
Enumeration<String> getServletNames();

void log(String var1);

/** @deprecated */
@Deprecated
void log(Exception var1, String var2);

void log(String var1, Throwable var2);

String getRealPath(String var1);

String getServerInfo();

String getInitParameter(String var1);

Enumeration<String> getInitParameterNames();

boolean setInitParameter(String var1, String var2);

Object getAttribute(String var1);

Enumeration<String> getAttributeNames();

void setAttribute(String var1, Object var2);

void removeAttribute(String var1);

String getServletContextName();

Dynamic addServlet(String var1, String var2);

Dynamic addServlet(String var1, Servlet var2);

Dynamic addServlet(String var1, Class<? extends Servlet> var2);

Dynamic addJspFile(String var1, String var2);

<T extends Servlet> T createServlet(Class<T> var1) throws ServletException;

ServletRegistration getServletRegistration(String var1);

Map<String, ? extends ServletRegistration> getServletRegistrations();

javax.servlet.FilterRegistration.Dynamic addFilter(String var1, String var2);

javax.servlet.FilterRegistration.Dynamic addFilter(String var1, Filter var2);

javax.servlet.FilterRegistration.Dynamic addFilter(String var1, Class<? extends Filter> var2);

<T extends Filter> T createFilter(Class<T> var1) throws ServletException;

FilterRegistration getFilterRegistration(String var1);

Map<String, ? extends FilterRegistration> getFilterRegistrations();

SessionCookieConfig getSessionCookieConfig();

void setSessionTrackingModes(Set<SessionTrackingMode> var1);

Set<SessionTrackingMode> getDefaultSessionTrackingModes();

Set<SessionTrackingMode> getEffectiveSessionTrackingModes();

void addListener(String var1);

<T extends EventListener> void addListener(T var1);

void addListener(Class<? extends EventListener> var1);

<T extends EventListener> T createListener(Class<T> var1) throws ServletException;

JspConfigDescriptor getJspConfigDescriptor();

ClassLoader getClassLoader();

void declareRoles(String... var1);

String getVirtualServerName();

int getSessionTimeout();

void setSessionTimeout(int var1);

String getRequestCharacterEncoding();

void setRequestCharacterEncoding(String var1);

String getResponseCharacterEncoding();

void setResponseCharacterEncoding(String var1);
}

GenericServlet

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
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
// 定义一个config成员变量
private transient ServletConfig config;

// 不用管
public GenericServlet() {
}

// 不用管
public void destroy() {
}

// 实现一些ServletConfig的方法
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}

public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}

// 由于init先执行,所以在getServletConfig方法调用之前config已经被赋值
public ServletConfig getServletConfig() {
return this.config;
}

// 封装getServletContext,是从config中获取的
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}

// 返回为空不用管,就是一些信息
public String getServletInfo() {
return "";
}

// 上面设置了一个成员变量,这里将Tomcat传入的servletConfig赋值给它
// 将局部变量提高为全局的
public void init(ServletConfig config) throws ServletException {
// 上文中提到的在init中做一些初始化的事情比如Spring
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);
}

// 我们发现唯独servic方法为被得到实现,这个需要我们自己去实现
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

public String getServletName() {
return this.config.getServletName();
}
}

总结一下GenericServlet的作用

  1. 提升servletConfig对象的作用域,方便其他方法使用

  2. init方法中还调用了空的init方法,如果我们需要servlet创建后做一些初始化操作,我们可以继承GenericServlet并且重写init无参方法。

  3. 还是保留了service方法 未实现它。

HttpServlet

HttpServlet实现了对HTTP协议的封装

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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
public abstract class HttpServlet extends GenericServlet {
private static final long serialVersionUID = 1L;
// 这里定义了一些HTTP的方法
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";
// HTTP请求头标签IMS
// 发送请求的时候会把缓存文件的修改时间一起发送过去
// 服务端拿这个时间和自己文件的修改时间相比,如果相同则返回304(不返回内容)
// 如果不一致,返回200
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
// IMS和LM都是记录文件修改时间的,而LM是服务器传给客户端的
private static final String HEADER_LASTMOD = "Last-Modified";
// 不是很了解。。
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
// 获取一些资源提示信息吧
private static final ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");

public HttpServlet() {
}

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 首先获取协议
String protocol = req.getProtocol();
// 获取状态码错误的消息
String msg = lStrings.getString("http.method_get_not_supported");
// 查看是否是HTTP1.1如果是返回405,不是返回400
// 可以看出HttpServlet为我们实现的doGet方法很鸡肋,如果我们不重写,那么我们直接回获取错误信息
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}

}

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

protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (DispatcherType.INCLUDE.equals(req.getDispatcherType())) {
// 获取分发类型如果是INCLUDE即包含那么在调用doGet请求
this.doGet(req, resp);
} else {
// 其实就是做noBodyResponse的get请求
NoBodyResponse response = new NoBodyResponse(resp);
this.doGet(req, response);
response.setContentLength();
}

}

// 实现doPost方法和doGet一样
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方法
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);
}

}

// 实现doPut方法
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);
}

}

// 这个方法主要就是返回所有方法包括父类继承来的方法
private static Method[] getAllDeclaredMethods(Class<?> c) {
// 判断是否是HttpServlet是返回null
if (c.equals(HttpServlet.class)) {
return null;
} else {
// 获取父类的方法
Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
// 获取自己的方法
Method[] thisMethods = c.getDeclaredMethods();
// 如果为null
if (parentMethods != null && parentMethods.length > 0) {
// 则将父类method加入thisMethods数组里
Method[] allMethods = new Method[parentMethods.length + thisMethods.length];
System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length);
System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length);
thisMethods = allMethods;
}
return thisMethods;
}
}

// OPTIONS请求
// OPTIONS方法请求Web服务器告知其支持的各种功能
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取所有方法包括父类继承来的
Method[] methods = 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;
Class clazz = null;
// 通过反射创建
try {
clazz = Class.forName("org.apache.catalina.connector.RequestFacade");
Method getAllowTrace = clazz.getMethod("getAllowTrace", (Class[])null);
ALLOW_TRACE = (Boolean)getAllowTrace.invoke(req, (Object[])null);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | ClassNotFoundException var14) {
;
}
// 遍历method获取方法名并比对,如果存在相应方法则将该值值为true
for(int i = 0; i < methods.length; ++i) {
Method m = methods[i];
if (m.getName().equals("doGet")) {
ALLOW_GET = true;
ALLOW_HEAD = true;
}

if (m.getName().equals("doPost")) {
ALLOW_POST = true;
}

if (m.getName().equals("doPut")) {
ALLOW_PUT = true;
}

if (m.getName().equals("doDelete")) {
ALLOW_DELETE = true;
}
}
// 拼装成字符串
String allow = null;
if (ALLOW_GET) {
allow = "GET";
}

if (ALLOW_HEAD) {
if (allow == null) {
allow = "HEAD";
} else {
allow = allow + ", HEAD";
}
}

if (ALLOW_POST) {
if (allow == null) {
allow = "POST";
} else {
allow = allow + ", POST";
}
}

if (ALLOW_PUT) {
if (allow == null) {
allow = "PUT";
} else {
allow = allow + ", PUT";
}
}

if (ALLOW_DELETE) {
if (allow == null) {
allow = "DELETE";
} else {
allow = allow + ", DELETE";
}
}

if (ALLOW_TRACE) {
if (allow == null) {
allow = "TRACE";
} else {
allow = allow + ", TRACE";
}
}

if (ALLOW_OPTIONS) {
if (allow == null) {
allow = "OPTIONS";
} else {
allow = allow + ", OPTIONS";
}
}
// 将拼装玩的字符串存入response头部
resp.setHeader("Allow", allow);
}

// 客户端发送一个请求的时,这个请求可能要穿过防火墙,代理,网关或其他
// 每个中间节点都可能会修改原始的HTTP请求,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());
out.close();
}

// 自己实现的protected的service方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 首先我们会获取request请求的方法,是get还是post等等
String method = req.getMethod();
// 定义LM
long lastModified;
if (method.equals("GET")) {
// 将这时候servlet的LM传入该方法返回-1L
// LM是服务端的修改时间
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
// 做doGet请求
// 之所以HttpServlet希望我们重写doGet和doPost请求就是因为
// 在Servlet需要实现的service方法,HttpServlet帮我们实现了
// 但是它在里面调用了自己的service方法
// 自己的service方法又调用了doGet和doPost
// 虽然doGet和doPost不是抽象方法,但是它的实现很鸡肋我们需要重写
// 我们来看一看doGet方法
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
// 获取客户端发送请求中的IMS字段
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
// 如果没有默认赋值为-1L
ifModifiedSince = -1L;
}
// 如果IMS小于LM即客户端修改时间小于服务端修改时间
// 即客户端缓存的文件在服务端已经被修改了
if (ifModifiedSince < lastModified / 1000L * 1000L) {
// 那么将LM存入响应头中,具体看对maybeSetLastModified方法的解析
this.maybeSetLastModified(resp, lastModified);
// 继续doGet
this.doGet(req, resp);
} else {
// 当服务端这里LM小于等于IMS则返回304即不返回信息
resp.setStatus(304);
}
}
// 如果是HEAD方法
// HEAD方法和GET方法都是安全方法即不会进行任何操作
// 但是HEAD中,服务器在响应中只返回首部,不会返回实体的主体部分
// 这就允许客户端在未获得实际资源的情况下对资源的首部进行检查
// 比如判断资源类型,获取响应状态码,看某个对象是否存在,测试资源是否被修改了等
} else if (method.equals("HEAD")) {
// 所以我们会先获取LM
// 这里会直接return-1L
lastModified = this.getLastModified(req);
// 设置LM
this.maybeSetLastModified(resp, lastModified);
// 做doHEAD
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
// 方法都不匹配
// 那么返回501 客户端发起的请求超出服务器的能力范围
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}

}

// 设置LM服务端最后修改文件时间
private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
// 如果响应头中没有
if (!resp.containsHeader("Last-Modified")) {
// 且它大于0 那么就将当前传入的LM存入响应头中
if (lastModified >= 0L) {
resp.setDateHeader("Last-Modified", lastModified);
}

}
}

// HttpServlet在重写了service方法
// 这里它干了两件事情
// 1.将ServletResponse和ServletRequest转换成HttpServletResponse和HttpServletRequest
// 2.将转换来的HttpServletResponse和HttpServletRequest作为参数传入自己实现的service方法,然后我们看上面的service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}

this.service(request, response);
}
}
-------------本文结束感谢阅读-------------