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开发入门
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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训练数据并当服务器共享给他人