秋招_Servlet概述


本文回顾一下Servlet。

1. 概述

一般情况下,Web应用就是通过网站来访问的,基于HTTP协议。那么服务端接收请求,则需要解析数据包,但是解析数据包显然程序员不能做,因此,出现了各种各样的Web服务器,负责接收数据包,并解析提取参数。解析参数则依据HTTP协议的规定。

但是仅仅提取URL以及参数还是不够的,因为后端程序员主要是负责业务功能的实现,所以必须还要知道依据这些URL调用指定的方法并传参,返回给客户端。因此这些依据就是Servlet规范。

可以看到Web服务器就是一座桥梁,负责连通两岸,而桥梁和两岸的连接处就是HTTP协议标准以及Servlet规范。

那么怎么知道某个URL来调用哪个方法呢?通过配置文件servlet-mapping来映射。找到这个类之后,就创建对象并调用该类的service方法来提供服务。

2. Servlet接口

Servlet接口有5个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Servlet{

public void init(ServletConfig config) throws ServletException{
}

public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException{

}

public void destroy(){
}

public String getServletInfo(){
}

public ServletConfig getServletConfig(){
}

}

分别是初始化、对外服务、销毁、获取Servlet信息、获取Servlet配置。

我们知道,调用方法提供服务,显然是需要先有该对象的,然后才能调用方法。那么什么时候创建Servlet对象呢?

2.1 Servlet对象的创建

默认情况下,Web服务器启动是不会创建Servlet对象,只有在被调用时,才会创建该对象。并放到一个哈希表中,后续再次需要时,则会直接调用该对象。可以设置Web服务器启动时就创建某个对象。

2.2 Servlet接口方法的调用

  1. 创建对象,首先会调用无参构造方法。【只会调用一次】
  2. 创建好对象后,立马执行init初始化方法。【只会调用一次】
  3. 当对应的请求到达后,会调用service方法。【每次请求都会调用一次】
  4. 当对象销毁时,比如服务器关闭,会调用destroy方法。【只调用一次,通常进行资源的关闭】
  5. getServletInfo()用于获得Servlet的相关信息,比如作者、版本等。
  6. getServletConfig()用于获得ServletConfig对象,即获取配置信息。

注意,上述方法都是由Web服务器调用,所以参数都是由Web服务器创建的。

3. 适配器模式封装Servlet接口

可以看到,上面的方法只有service方法是最常用的,也是最经常出现的,而其他方法则很少使用。因此,不应该每次实现Servlet接口都要实现其余四个方法。

因此,可采用适配器模式,实现Servlet接口,对于不常用的抽象方法予以实现;而对于常用的方法保留,用于子类继承实现。适配器其实就是适配不同的需求,从而保留不同的方法。

Servlet中已经实现了适配器类,即javax.servlet.GenericServlet类。而HttpServlet则是专门用于HTTP协议准备的抽象类,该类继承了GenericServlet。

4. ServletConfig接口

ServletConfig接口是作为上面Servlet的方法的参数类型,其实就是Servlet对象的配置信息所封装的对象。

配置信息其实就是web.xml配置文件中<servlet>标签中的信息。主要有以下几个方法:

方法名 描述
String getInitParameter(String name) 获取指定name的初始化配置信息。(注意,在web.xml中是可以通过<init-para>标签来设置servlet的初始化信息的
Enumeration<String> getInitParameterNames() 获取所有初始化信息的name。
ServletContext getServletContext() 获得ServletContext对象
String getServletName() 获得Servlet对象的名字

实际上GenericServlet类已经实现了ServletConfig接口,所以在service方法中,直调用this就可调用上面的四个方法。

5. ServletContext接口

上面Servlet接口以及ServletConfig接口,还有一个ServletContext接口类型。我们知道,ServletConfig指的就是在web.xml文件中,<servlet>标签对该类的配置信息。ServletContext接口其实就是提供了对整个web.xml的封装对象,通过ServletContext对象,可获取到所有web.xml中所有的配置信息,包括创建的对象。

方法名 描述
String getInitParameter(String name) 获得指定name的全局配置信息
Enumeration<String> getInitParameterNames() 获得全局配置信息的所有name
String getContextPath() 获取所在webapp项目的根路径
String getRealPath(String path) 获取path的绝对路径,这里的path指的是在webapp中的路径,以web为根路径,如/path.html.
void log(String msg) 将指定的信息写入日志文件,日志在Tomcat安装目录下的logs目录下
void log(String message, Throwable throwable) 在上述方法的基础上,也将异常信息写入进去
void setAttribute(String name, Object object) 向应用域中存数据(应用域的概念可参考5.3节)
void getAttribute(String name) 向应用域中取数据
void removeAttribute(String name) 删除应用域中的数据

从某种程度上说,这个web.xml是全局配置信息,包含了创建的对象,也就是是一块空间,被称为应用域,ServletContext接口的实现类对象就是应用域。可以向应用域对象中存储数据,即整个应用的共享变量,这个数据是放在缓存中的

6. HttpServlet

上面讲到了适配器模式,就是适配不同的需求,而保留不同的方法。其实这里还有一个模板方法模式。可以看成是一个框架,核心的执行流程已经固定写好,比如一天的流程依次是起床,工作,午饭,工作,睡觉等等。但是具体的工作内容,午饭菜品等等可自定义由子类实现。

比如上面的Servlet,只要有请求来了,就会找该Servlet对象,如果找到就调用service方法,找不到就创建对象,初始化,调用service方法。但是service方法具体实现,可自定义。

HttpServlet是专门为HTTP协议制定的一个抽象类,继承自GenericServlet。因为HTTP协议有很多种请求方式,比如put、post等等。而每种请求方式,所处理的内容、处理方式以及获取参数是不同的。

因此,HttpServlet继承了GenericServlet之后,在核心service方法中,进行判断,从而调用不同的自己实现的方法,但是自己实现的方法并没有执行具体任务,只是输出了语句【具体功能实现由程序员自定义子类实现】,这就是模板方法模式。

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
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}

if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
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 {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}

7. HttpServletRequest

其实Servlet接口中,还有ServletRequest和ServletResponse两个接口类型没说。和HttpServlet一样,HttpServletRequest继承ServletRequest,专门为了Http协议而建立的接口。

HttpServletRequest接口的实现类对象被称为请求域对象,和应用域对象类似,只不过作用范围只在一次请求中有效。

8. Cookie和Session

一次请求到达之后,服务器会获取请求的Cookie对象,如果没有,则为其创建一个Cookie对象,并把Cookie对象作为sessionid,创建一个Session对象【会话域】。这样,一个Session对象可以对应多次request请求。可以使得多次请求之间通信。

9. Listener监听器

监听器(Listener)是Servlet规范的一种扩展,是一组来自Servlet规范下的接口,共有9个接口。监听器接口用于监控【作用域对象生命周期变化时刻】以及【作用域对象共享数据变化时刻】,比如session对象的创建和销毁以及所存储数据的变化情况。

前面提到过,实际上数据库操作中最耗时的就是连接对象的创建,即二者的I/O通道创建比较耗时,所以为了提升效率,可以在服务器启动的时候创建好一批连接对象(数据库连接池,用的时候取一个,用完之后再放回),并且直到服务器关闭的时候再销毁连接对象。那么怎么知道服务器启动和关闭呢?可以监听ServletContext应用域,因为服务器启动的时候会先创建应用域对象,关闭的时候会先销毁应用域对象,那么监听二者,在二者事件触发的时候,就创建数据库连接对象以及销毁对象即可。

10. Filter过滤器

和监听器(Listener)一样,过滤器(Filter)也是来自于Servlet下的接口。过滤器主要是在Http服务器调用资源文件之前,对Http服务器进行拦截,即不是无限制的对所有请求都应答,设置一个过滤器,即增加一个门槛

开发过滤器实现类分为以下三步:

  1. 创建一个类实现Filter接口;
  2. 重写接口中的doFilter方法;
  3. 在web.xml配置文件中将实现类注册到WEB容器上。

文章作者: 浮云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 浮云 !
  目录