随笔 - 92,  文章 - 0,  评论 - 1,  阅读 - 17万

 Servlet(Server Applet),全称Java Servlet。   对这个Servlet一直不了解,也没有学习过,现在进行各基础了解学习。

 Servlet是用Java编写的服务端程序,其主要功能在于交互式地浏览和修改数据,生成动态的Web内容。Servlet运行于支持Java的应用服务器中。从实现上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。

 Servlet的工作模式:

1:客户端发送求情到服务器

2:服务器启动并调用Servlet, Servlet根据客户端请求生成响应内容并将其传给服务器

3:服务器将响应返回给客户端

Servlet的主要相关类

其中GenericServlet下面还有一个HttpServlet class.

Servlet接口中定义的方法

复制代码
 1 public interface Servlet {
 2 
 3 public void init(ServletConfig config) throws ServletException;
 4 
 5 public ServletConfig getServletConfig();
 6 
 7 public void service(ServletRequest req, ServletResponse res)
 8             throws ServletException, IOException;
 9 
10 public String getServletInfo();
11 
12 public void destroy();
13 
14 }
复制代码

 根据上面Servlet的接口函数,描述一下Servlet的运行过程

 Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet请求访问后,

1:web服务器首先检查是否已经装在并创建了该Servlet的实例对象,如果已经创建,则直接到第四步;否则执行第二步;

2:装载并创建该servlet的一个实例对象;

3:调用Servlet实例对象的init()方法;

4:创建一个用于封装Http请求消息的HttpServletRequest对象和一个代表HttpServletResponse对象,然后调用Servlet的Service()方法并将请求和响应对象作为参数传进去。

5:web应用程序被停职或重新启动之前,servlet引擎将卸载servlet,并在卸载之前调用Servlet的destory()方法。

 Servlet 的生命周期

其中,init(), service(),destory()是servlet生命周期的方法。代表了Servlet从出生到工作,再到死亡的过程。Servlet容器(例如Tomcat)会根据下面的规则来调用这三个方法。

1,init(),当Servlet第一次被请求时,Servlet容器就会开始调用这个方法来初始化一个Servlet对象出来,但是这个方法不会再后续请求中被Servlet容器调用,就像人是能出生一次一样。我们可以利用init()方法来执行相应的初始化工作。调用这个方法时,Servlet容器会传入一个ServletConfig对象进来从而对Servlet对象进行初始化。

2,Service()方法,每当请求Servlet时,Servlet就会调用这个方法。就像人一样,需要不停的接受老板的指令并且工作。第一次请求时,Servlet容器会先调用init()方法初始化一个Servlet对象出来,然后调用它的service()方法进行工作。但在后续请求中,servlet容器只会调用service方法了。

3,destory, 当要销毁Servlet时,Servlet容器就会调用这个方法,就如人一样。到时候就会死亡。在卸载应用程序或者关闭Servlet容器时,就会发生这种情况,一般在这个方法中会写一些清除代码。

Servlet 的其它两个方法

getServletInfo( ),这个方法会返回Servlet的一段描述,可以返回一段字符串。

getServletConfig( ),这个方法会返回由Servlet容器传给init( )方法的ServletConfig对象。

 此处应该增加例子,但是在我添加例子过程中,经常出现找不到servlet,出现404错误。

后面找到真正原因后,再添加例子

按照博客中给的例子进行验证,常常出现http404错误。

 

GenericServlet抽象类

前面我们编写Servlet一直是通过实现Servlet接口来编写的,但是,这种方法必须实现接口servlet中定义的所有方法。即使有一些方法中没有任何东西也要实现。并且还需要自己手动的维护ServletConfig这个对象的引用。因此,这样去实现Servlet是比较麻烦的。幸好,GenericServlet抽象类的出现很好的解决了这个问题。本着尽可能使代码简洁的原则,GenericServlet实现了Servlet和ServletConfig接口。 GenericServlet类实现:

复制代码
 1 public abstract class GenericServlet implements Servlet, ServletConfig,
 2         java.io.Serializable {
 3    
 4  @Override
 5     public void destroy() {
 6         // NOOP by default
 7     }
 8 
 9     @Override
10     public ServletConfig getServletConfig() {
11         return config;
12     }
13 
14     @Override
15     public String getServletInfo() {
16         return "";
17     }
18 
19     @Override
20     public void init(ServletConfig config) throws ServletException {
21         this.config = config;
22         this.init();
23     }
24 
25     public void init() throws ServletException {
26         // NOOP by default
27     }
28 
29     @Override
30     public abstract void service(ServletRequest req, ServletResponse res)
31             throws ServletException, IOException;
32 
33 }
复制代码
其中,GenericServlet抽象类相比于直接实现Servlet接口,有以下几个好处:

 1.为Servlet接口中的大部分方法提供了默认的实现,则程序员需要什么就直接改什么,不再需要把所有的方法都自己实现了。

 2.将init( )方法中的ServletConfig参数赋给了一个内部的ServletConfig引用从而来保存ServletConfig对象,不需要程序员自己去维护ServletConfig了。

但是,我们发现在GenericServlet抽象类中还存在着另一个没有任何参数的Init()方法:

public void init() throws ServletException {
}

设计者的初衷到底是为了什么呢?在第一个带参数的init()方法中就已经把ServletConfig对象传入并且通过引用保存好了,完成了Servlet的初始化过程,那么为什么后面还要加上一个不带任何参数的init()方法呢?

抽象类是无法直接产生实例的,需要另一个类去继承这个抽象类,那么就会发生方法覆盖的问题,如果在类中覆盖了GenericServlet抽象类的init()方法,那么程序员就必须手动的去维护ServletConfig对象了,还得调用super.init(servletConfig)方法去调用父类GenericServlet的初始化方法来保存ServletConfig对象,这样会给程序员带来很大的麻烦。GenericServlet提供的第二个不带参数的init( )方法,就是为了解决上述问题的。这个不带参数的init()方法,是在ServletConfig对象被赋给ServletConfig引用后,由第一个带参数的init(ServletConfig servletconfig)方法调用的,那么这意味着,当程序员如果需要覆盖这个GenericServlet的初始化方法,则只需要覆盖那个不带参数的init( )方法就好了,此时,servletConfig对象仍然有GenericServlet保存着。简而言之一句话,就是覆盖的时候可以不用管servletConfig。
但是GenericServlet没有处理Service(),  HttpServlet()才是主角。

javax.servlet.http包内容

之所以所HttpServlet要比GenericServlet强大,其实也是有道理的。HttpServlet是由GenericServlet抽象类扩展而来的,HttpServlet抽象类的声明如下所示:

1 public abstract class HttpServlet extends GenericServlet {}

HttpServlet之所以运用广泛的另一个原因是现在大部分的应用程序都要与HTTP结合起来使用。这意味着我们可以利用HTTP的特性完成更多更强大的任务。Javax。servlet.http包是Servlet API中的第二个包,其中包含了用于编写Servlet应用程序的类和接口。Javax.servlet.http中的许多类型都覆盖了Javax.servlet中的类型。

HttpServlet抽象类

HttpServlet抽象类是继承于GenericServlet抽象类而来的。使用HttpServlet抽象类时,还需要借助分别代表Servlet请求和Servlet响应的HttpServletRequest和HttpServletResponse对象。

1 public interface HttpServletRequest extends ServletRequest {}
2 public interface HttpServletResponse extends ServletResponse {}

先看一下GenericServlet中service定义:

1     @Override
2     public abstract void service(ServletRequest req, ServletResponse res)
3             throws ServletException, IOException;

再来看看HttpServlet()是怎么来覆盖的:

复制代码
 1     @Override
 2     public void service(ServletRequest req, ServletResponse res)
 3         throws ServletException, IOException {
 4 
 5         HttpServletRequest  request;
 6         HttpServletResponse response;
 7 
 8         try {
 9             request = (HttpServletRequest) req;
10             response = (HttpServletResponse) res;
11         } catch (ClassCastException e) {
12             throw new ServletException("non-HTTP request or response");
13         }
14         service(request, response);
15     }
16 
17 protected void service(HttpServletRequest req, HttpServletResponse resp)
18         throws ServletException, IOException {
19 
20         String method = req.getMethod();
21 
22         if (method.equals(METHOD_GET)) {
23             long lastModified = getLastModified(req);
24             if (lastModified == -1) {
25                 // servlet doesn't support if-modified-since, no reason
26                 // to go through further expensive logic
27                 doGet(req, resp);
28             } else {
29                 long ifModifiedSince;
30                 try {
31                     ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
32                 } catch (IllegalArgumentException iae) {
33                     // Invalid date header - proceed as if none was set
34                     ifModifiedSince = -1;
35                 }
36                 if (ifModifiedSince < (lastModified / 1000 * 1000)) {
37                     // If the servlet mod time is later, call doGet()
38                     // Round down to the nearest second for a proper compare
39                     // A ifModifiedSince of -1 will always be less
40                     maybeSetLastModified(resp, lastModified);
41                     doGet(req, resp);
42                 } else {
43                     resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
44                 }
45             }
46 
47         } else if (method.equals(METHOD_HEAD)) {
48             long lastModified = getLastModified(req);
49             maybeSetLastModified(resp, lastModified);
50             doHead(req, resp);
51 
52         } else if (method.equals(METHOD_POST)) {
53             doPost(req, resp);
54 
55         } else if (method.equals(METHOD_PUT)) {
56             doPut(req, resp);
57 
58         } else if (method.equals(METHOD_DELETE)) {
59             doDelete(req, resp);
60 
61         } else if (method.equals(METHOD_OPTIONS)) {
62             doOptions(req,resp);
63 
64         } else if (method.equals(METHOD_TRACE)) {
65             doTrace(req,resp);
66 
67         } else {
68             //
69             // Note that this means NO servlet supports whatever
70             // method was requested, anywhere on this server.
71             //
72 
73             String errMsg = lStrings.getString("http.method_not_implemented");
74             Object[] errArgs = new Object[1];
75             errArgs[0] = method;
76             errMsg = MessageFormat.format(errMsg, errArgs);
77 
78             resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
79         }
80     }
复制代码

    我们发现,HttpServlet中的service方法把接收到的ServletRequsest类型的对象转换成了HttpServletRequest类型的对象,把ServletResponse类型的对象转换成了HttpServletResponse类型的对象。之所以能够这样强制的转换,是因为在调用Servlet的Service方法时,Servlet容器总会传入一个HttpServletRequest对象和HttpServletResponse对象,预备使用HTTP。因此,转换类型当然不会出错了。
转换之后,service方法把两个转换后的对象传入了自己定义的service方法进行运行。下面看怎么运行的:

 我们会发现在service方法中还是没有任何的服务逻辑,但是却在解析HttpServletRequest中的方法参数,并调用以下方法之

 一:doGet,doPost,doHead,doPut,doTrace,doOptions和doDelete。这7种方法中,每一种方法都表示一个Http方法。doGet和doPost是最常用的。所以,如果我们需要实现具体的服务逻辑,不再需要覆盖service方法了,只需要覆盖doGet或者doPost就好了。

    总之,HttpServlet有两个特性是GenericServlet所不具备的:

    1.不用覆盖service方法,而是覆盖doGet或者doPost方法。在少数情况,还会覆盖其他的5个方法。

    2.使用的是HttpServletRequest和HttpServletResponse对象

下面讲述如何解析HttpServletRequest以及如何组装HttpServletResponse

 再次先省略这两部分内容。

 

Servlet的工作流程

 

 

 Servlet线程安全

Servlet多线程体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。

 当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类,此时它贮存于内存中。。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。 这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时要注意线程安全的问题。每一个请求都是一个线程,而不是进程,因此,Servlet对请求的处理的性能非常高。

 对于Servlet,它被设计为多线程的(如果它是单线程的,你就可以想象,当1000个人同时请求一个网页时,在第一个人获得请求结果之前,其它999个人都在郁闷地等待),如果为每个用户的每一次请求都创建 一个新的线程对象来运行的话,系统就会在创建线程和销毁线程上耗费很大的开销,大大降低系统的效率。

因此,Servlet多线程机制背后有一个线程池在支持,线程池在初始化初期就创建了一定数量的线程对象,通过提高对这些对象的利用率,避免高频率地创建对象,从而达到提高程序的效率的目的。(由线程来执行Servlet的service方法,servlet在Tomcat中是以单例模式存在的, Servlet的线程安全问题只有在大量的并发访问时才会显现出来,并且很难发现,因此在编写Servlet程序时要特别注意。线程安全问题主要是由实例变量造成的,因此在Servlet中应避免使用实例变量。如果应用程设计无法避免使用实例变量,那么使用同步来保护要使用的实例变量,但为保证系统的最佳性能,应该同步可用性最小的代码路径)

 

从建立工程,到配置web.xml文件。编写Servlet,启动Tomcat等基本都没有问题。但是经常会出现http404错误。这个过程后面会在前面补例子。

 

Servlet的生命周期

Servlet的生命周期是由Tomcat容器管理。

a)客户发出请求->web服务器转发到web容器Tomcat;

b)Tomcat主线程对转发来用户的请求做出响应创建两个对象:HttpServletRequset和HttpServletResponse;

c)从请求中的URL中找到正确的Servlet,Tomcat 为其创建或者分配一个线程,同时把2创建的两个对象传递给该线程。

d)Tomcat调用Servlet的Service()方法,根据请求参数的不同调用doGet()或者doPost()方法;

e)假设是HTTP GET请求,doGet()方法生成静态页面。并组合到响应对象里;

Servlet线程结束后,Tomcat将响应对象转换为Http响应发回给客户,同时删去请求和响应对象。

从该过程中,我们可以理解Servlet的生命周期:Servlet类加载(对应第3步);调用init方法(对应第3步);调用service方法(对应第4、5步);调用destory()方法(对应第6步)。

五:Servlet生命周期的各个阶段

 

Servlet的生命周期包含了下面4个阶段: 

(1)加载和实例化

Servlet容器负责加载和实例化Servlet。当Servlet容器启动时,或者在容器检测到需要这个Servlet来响应第一个请求时,创建Servlet实例。当Servlet容器启动后,它必须要知道所需的Servlet类在什么位置,Servlet容器可以从本地文件系统、远程文件系统或者其他的网络服务中通过类加载器加载Servlet类,成功加载后,容器创建Servlet的实例。因为容器是通过Java的反射API来创建Servlet实例,调用的是Servlet的默认构造方法(即不带参数的构造方法),所以我们在编写Servlet类的时候,不应该提供带参数的构造方法。

(2)初始化

在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象。初始化的目的是为了让Servlet对象在处理客户端请求前完成一些初始化的工作,如建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只被调用一次。在初始化期间,Servlet实例可以使用容器为它准备的ServletConfig对象从Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息。在初始化期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常来通知容器。ServletException异常用于指明一般的初始化失败,例如没有找到初始化参数;而UnavailableException异常用于通知容器该Servlet实例不可用。例如,数据库服务器没有启动,数据库连接无法建立,Servlet就可以抛出UnavailableException异常向容器指出它暂时或永久不可用。

(3)请求处理

Servlet容器调用Servlet的service()方法对请求进行处理。要注意的是,在service()方法调用之前,init()方法必须成功执行。在service()方法中,Servlet实例通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。在service()方法执行期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常。如果UnavailableException异常指示了该实例永久不可用,Servlet容器将调用实例的destroy()方法,释放该实例。此后对该实例的任何请求,都将收到容器发送的HTTP 404(请求的资源不可用)响应。如果UnavailableException异常指示了该实例暂时不可用,那么在暂时不可用的时间段内,对该实例的任何请求,都将收到容器发送的HTTP 503(服务器暂时忙,不能处理请求)响应。

(4)服务终止

当容器检测到一个Servlet实例应该从服务中被移除的时候,容器就会调用实例的destroy()方法,以便让该实例可以释放它所使用的资源,保存数据到持久存储设备中。当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。如果再次需要这个Servlet处理请求,Servlet容器会创建一个新的Servlet实例。

在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次,当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务。下面给出Servlet整个生命周期过程的UML序列图

 

本节Servlet学习在这。看了好多博客,贴的有点乱,整体对serclet有了理解。后面再加一贴针对具体例子。

 

https://www.cnblogs.com/gaoxiangde/p/4339571.html-----后面部分主要参考

https://blog.csdn.net/qq_19782019/article/details/80292110------主要参考非常好,后面一部分ServletContext没有理解。

https://www.cnblogs.com/whgk/p/6399262.html-----源码解释的很清楚

https://www.cnblogs.com/xdp-gacl/p/3729033.html  ------Java web开发入门

posted on   shiyuan310  阅读(225)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人

< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5
点击右上角即可分享
微信分享提示