Servlet 您所在的位置:网站首页 人参的构造 Servlet

Servlet

2022-12-29 18:14| 来源: 网络整理| 查看: 265

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情

一:Servlet生命周期

什么是Servlet对象生命周期?

Servlet对象的生命周期表示:一个Servlet对象从出生到最后的死亡,整个过程是怎样的。

Servlet对象是由谁来维护的?

Servlet对象的创建,对象上方法的调用,对象最终的销毁,Javaweb程序员是无权干预的。Servlet对象的生命周期是由Tomcat服务器(WEB Server)全权负责的。 Tomcat服务器通常我们又称为:WEB容器。(WEB Container) WEB容器来管理Servlet对象的生命周期。

思考:我们自己new的Servlet对象受WEB容器的管理吗?

自己new的Servlet对象是不受WEB容器管理的。(自己new的Servlet对象不在容器当中) WEB容器创建的Servlet对象,这些Servlet对象都会被放到一个集合(HashMap)当中,只有放到这个HashMap集合中的Servlet才能够被WEB容器管理。 web容器底层应该有一个HashMap这样的集合,在这个集合当中存储了Servlet对象和请求路径之间的关系:

​编辑

思考:服务器在启动的Servlet对象有没有被创建出来(默认情况下)? 如何测试: 在Servlet中提供一个无参数的构造方法,启动服务器的时候看看构造方法是否执行? 只启动服务器,发现无参构造方法并没有打印输出;所以经过测试得出结论:默认情况下,服务器在启动的时候Servlet对象并不会被实例化。实际上在启动服务器时,它会解析.xml文件,把请求路径"/a"和类名“com.bjpowernode.javaweb.servlet.AServlet”放到一个HashMap集合当中。 这个设计是非常合理的。用户没有发送请求之前,如果提前创建出来所有的Servlet对象,必然是耗费内存的,并且创建出来的Servlet如果一直没有用户访问,显然这个Servlet对象是一个无用,没必要先创建。

类实现Servlet接口,并重写5个方法和写出无参构造方法

package com.bjpowernode.javaweb.servlet; import javax.servlet.*; import java.io.IOException; public class AServlet implements Servlet { public AServlet() { System.out.println("AServlet无参数构造方法执行了"); } @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } 复制代码

配置文件

aservlet com.bjpowernode.javaweb.servlet.AServlet aservlet /a 复制代码

当然也可以让服务器启动(用户没有进行访问)的时候就创建Servlet对象?

在servlet标签中添加 子标签,在该子标签中填写正整数,越小的整数优先级越高。 加上这个字标签后,构造方法就能执行了,实际上也就是实例化了Servlet对象。 aservlet com.bjpowernode.javaweb.servlet.AServlet 1 aservlet /a 复制代码

​编辑

研究一下:init()、service()、destroy()这三个方法?

默认情况下服务器启动的时候AServlet对象并没有被实例化。下面在浏览器上发送请求,进行对象的创建:http://localhost:8080/servlet02/a

package com.bjpowernode.javaweb.servlet; import javax.servlet.*; import java.io.IOException; public class AServlet implements Servlet { // 无参构造方法 public AServlet() { System.out.println("AServlet无参数构造方法执行了"); } // init()方法被翻译为初始化,init()方法只执行一次 // 在Aservlet对象第一次被创建之后执行,init()方法通常是完成初始化操作的 @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("A.Servlet's init method execute!"); } // service()方法,是处理用户请求核心方法 // 只要用户发送一次请求,service()方法必然会执行一次 @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("A.Servlet's service method execute!"); } // destroy()方法也是只执行一次 // Tomcat服务器在销毁AServlet对象之前会调用一次destroy()方法 // 注意:destroy()方法在执行的时候,AServlet对象的内存还没有被销毁,是即将被销毁的状态 @Override public void destroy() { System.out.println("A.Servlet's destroy method execute!"); } @Override public String getServletInfo() { return null; } @Override public ServletConfig getServletConfig() { return null; } } 复制代码

(1)用户发送第一次请求的时候,控制台输出了以下内容:

        AServlet无参数构造方法执行了 AServlet's init method execute! AServlet's service method execute!

根据以上输出内容得出结论:

用户在发送第一次请求的时候Servlet对象被实例化(AServlet的构造方法被执行了,并且执行的是无参数构造方法。) AServlet对象被创建出来之后,Tomcat服务器马上调用了AServlet对象的init()方法。(init方法在执行的时候,AServlet对象已经存在了。已经被创建出来了。) 用户发送第一次请求的时候,init方法执行之后,Tomcat服务器马上调用AServlet对象的service()方法。

(2)用户继续发送第二次请求,控制台输出了以下内容:

        AServlet's service method execute!

根据以上输出结果得知,用户在发送第二次,或者第三次,或者第四次请求的时候,Servlet对象并没有新建,还是使用之前创建好的Servlet对象,直接调用该Servlet对象的service()方法,这说明:

第一:Servlet对象是单例的(单实例的。但是要注意:Servlet对象是单实例的,但是Servlet类并不符合单例模式。我们称之为假单例。之所以单例是因为Servlet对象的创建我们javaweb程序员管不着,这个对象的创建只能是Tomcat来说了算,Tomcat只创建了一个,所以导致了单例,但是属于假单例。真单例模式,构造方法是私有化的!) 第二:无参数构造方法、init()方法只在第一次用户发送请求的时候执行。也就是说无参数构造方法只执行一次。init方法也只被Tomcat服务器调用一次。 第三:只要用户发送一次请求:service方法必然会被Tomcat服务器调用一次。发送100次请求,service方法会被调用100次。

(3)关闭服务器的时候,控制台输出了以下内容:

        AServlet's destroy method execute!

通过以上输出内容,可以得出以下结论:

Servlet的destroy方法只被Tomcat服务器调用一次。

destroy方法是在什么时候被调用的?

在服务器关闭的时候被调用。 因为服务器关闭的时候要销毁AServlet对象的内存。 服务器在销毁AServlet对象内存之前,Tomcat服务器会自动调用AServlet对象的destroy方法。

思考:destroy方法调用的时候,对象销毁了还是没有销毁呢?

destroy方法执行的时候AServlet对象还在,没有被销毁。 destroy方法执行结束之后,AServlet对象的内存才会被Tomcat释放。

destroy方法中可以编写销毁之前的准备,例如:服务器关闭的时候,Aservlet对象开启了一些资源(如:流、连接数据库),那么关闭服务器的时候需要关闭的这些资源的代码就可以写到destroy方法当中!

其实Servlet对象生命周期更像一个人的一生:

Servlet的无参数构造方法执行:标志着你出生了。 Servlet对象的init()方法的执行:标志着你正在接受教育。 Servlet对象的service()方法的执行:标志着你已经开始工作了,已经开始为人类提供服务了。 Servlet对象的destroy方法的执行:标志着临终。快死了,在医院里写遗嘱!

关于Servlet类中方法的调用次数?

构造方法、init()方法、destroy()方法只执行一次。 service方法:用户发送一次请求则执行一次,发送N次请求则执行N次。

当我们Servlet类中编写一个有参数的构造方法,如果没有手动编写无参数构造方法会出现什么问题?

报错:500错误。 注意:500是一个HTTP协议的错误状态码。 500一般情况下是因为服务器端的Java程序出现了异常(服务器端的错误都是500错误:服务器内部错误)! 如果没有无参数的构造方法,会导致出现500错误,无法实例化Servlet对象! 注意: 在Servlet开发当中,不建议程序员来定义构造方法,因为定义不当,就会导致无法实例化Servlet对象!

思考:Servlet的无参数构造方法是在对象第一次创建的时候执行,并且只执行一次。init方法也是在对象第一次创建的时候执行,并且只执行一次。那么这个无参数构造方法可以代替掉init方法吗?

不能。Servlet规范中有要求,作为javaweb程序员,编写Servlet类的时候,不建议手动编写构造方法,因为编写构造方法,很容易让无参数构造方法消失,这个操作可能会导致Servlet对象无法实例化。所以init方法是有存在的必要的。

init、service、destroy方法中使用最多的是哪个方法?

使用最多就是service方法,service方法是一定要实现的,因为service方法是处理用户请求的核心方法。

什么时候使用init方法呢?

init方法很少用。 通常在init方法当中做初始化操作,并且这个初始化操作只需要执行一次。例如:初始化数据库连接池,初始化线程池....

什么时候使用destroy方法呢?

destroy方法也很少用。 通常在destroy方法当中,进行资源的关闭。马上对象要被销毁了,需要抓紧时间关闭资源和资源的保存。 二: 适配器模式改造Servlet

我们编写一个Servlet类直接实现Servlet接口有什么缺点?

我们只需要service方法,其他方法大部分情况下是不需要使用的,代码很丑陋。

适配器设计模式(Adapter)

例如:手机直接插到220V的电压上,手机直接就报废了,怎么办?可以找一个充电器。这个充电器其实就是一个适配器。手机连接适配器,适配器连接220V的电压,这样问题就解决了。

例如:下面有一个MyInterface接口,只有core()方法是常用的;那么就编写一个适配器UserAdapter抽象类去实现MyInterface接口,不常用的方法都进行重写,常用的core方法还写成抽象的方法;最后让Aservlet去继承UserAdapter类,重写core方法即可!

MyInterface接口

package com.bjpowernode.javaweb.adapter; public interface MyInterface { // 在接口里面的方法,都是抽象方法 void m1(); void m2(); void m3(); void m4(); void m5(); void m6(); void m7(); // 最常用的是这个core()方法 void core(); } 复制代码

UserAdapter类实现MyInterface接口

package com.bjpowernode.javaweb.adapter; // 因为下面有抽象方法,这里也只能写成抽象类,因为抽象方法只能出现在抽象类当中 // 而抽象类中既可以有抽象方法,也可以有非抽象方法 public abstract class UserAdapter implements MyInterface { @Override public void m1() { } @Override public void m2() { } @Override public void m3() { } @Override public void m4() { } @Override public void m5() { } @Override public void m6() { } @Override public void m7() { } @Override // 把这个常用的方法写成抽象方法 public abstract void core(); } 复制代码

AServlet继承UserAdapter类,重写core()方法接口

package com.bjpowernode.javaweb.adapter; public class AServlet extends UserAdapter{ @Override public void core() { System.out.println("常用的core方法"); } } 复制代码

所以就可以编写一个通用的GenericServlet类:

GenericServle(abstract类)实现Servlet接口。 这个类是一个抽象类,其中有一个抽象方法service()。 以后编写的所有Servlet类都不要直接实现Servlet接口了,只需要继承GenericServlet类,重写service方法即可。 此时GenericServlet就是一个适配器。

GenericServle类实现Servlet接口

package com.bjpowernode.javaweb.adapter02; import javax.servlet.*; import java.io.IOException; /** * @Author:朗朗乾坤 * @Package:com.bjpowernode.javaweb.adapter02 * @Project:JavaWeb * @name:GenericServlet * @Date:2022/10/31 20:11 */ public abstract class GenericServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } // 抽象方法 public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException; @Override public String getServletInfo() { return null; } @Override public void destroy() { } } 复制代码

Login类继承GenericServlet类

package com.bjpowernode.javaweb.adapter02; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; public class Login extends GenericServlet { @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("正在登陆请稍后。。。"); } } 复制代码

web.xml配置文件

login com.bjpowernode.javaweb.adapter02.Login login /login 复制代码

在浏览器上进行访问:http://localhost:8080/servlet02/login完全没问题!

三:改造GenericServlet

思考:GenericServlet类是否需要改造一下?怎么改造?更利于子类程序的编写?

思考:提供了一个GenericServlet之后,init方法还会执行吗?

还会执行。会执行GenericServlet类中的init方法。

思考:init方法是谁调用的?

还是Tomcat服务器调用的。

思考:init方法中的ServletConfig对象是谁创建的?是谁传过来的?

都是Tomcat服务器干的。 Tomcat服务器先创建了ServletConfig对象,然后调用init方法,将ServletConfig对象传给了init方法。

思考一下Tomcat服务器伪代码:

public class Tomcat { public static void main(String[] args){ // ..... // Tomcat服务器伪代码 // 创建LoginServlet对象(通过反射机制,调用无参数构造方法来实例化Login对象) Class c = Class.forName("com.bjpowernode.javaweb.adapter02.Login"); Object obj = c.newInstance(); // 向下转型 Servlet servlet = (Servlet)obj; // 创建ServletConfig对象,在调用init方法之前肯定要先创建一个 // ServletConfig对象,因为调用init方法需要传进去一个ServletConfig对象 // Tomcat服务器负责将ServletConfig对象实例化出来。 // 多态(Tomcat服务器完全实现了Servlet规范) ServletConfig servletConfig = new org.apache.catalina.core.StandardWrapperFacade(); // 调用Servlet的init方法 servlet.init(servletConfig); // 调用Servlet的service方法 // .... } } 复制代码

思考: init方法中的ServletConfig对象是Tomcat创建好的,这个ServletConfig对象目前在init方法上,属于局部变量;那么ServletConfig对象肯定以后也要在service方法中使用,怎么才能保证ServletConfig对象在service方法中能够使用呢?

答: 使用成员变量,把这个值传出来。

改造后的GenericServlet

package com.bjpowernode.javaweb.adapter02; import javax.servlet.*; import java.io.IOException; public abstract class GenericServlet implements Servlet { // 定义一个成员变量 private ServletConfig config; @Override public void init(ServletConfig servletConfig) throws ServletException { // 进行赋值 this.config = servletConfig; } @Override public ServletConfig getServletConfig() { // 这里就不需要返回null了 // 并且可以通过getServletConmfig方法拿到config对象 return config; } // 抽象方法 public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException; @Override public String getServletInfo() { return null; } @Override public void destroy() { } } 复制代码

在service方法中获取到ServletConfig对象

package com.bjpowernode.javaweb.adapter02; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; public class Login extends GenericServlet { @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { // 这样在Login子类中调用getServletConfig方法就可以拿到ServletConfig对象 // 实际上就实现了在service方法中获取到ServletConfig对象 ServletConfig config = this.getServletConfig(); System.out.println(config);// org.apache.catalina.core.StandardWrapperFacade@d1a9b22 } } 复制代码

此时启动Tomcat服务器再去访问:http://localhost:8080/servlet02/login;就可以拿到ServletConfig对象:org.apache.catalina.core.StandardWrapperFacade@401080dc

思考: 如果Login子类需要重写init()方法,然后编写了属于自己的规则,那么就不会走父类GenericServlet类的init方法,就不能进行 this.config = servletConfig赋值;此时config就会又为null,怎么解决?

答: 一个方法要想不让子类重写就用final关键字进行修饰!

// 父类的init方法被final修饰了,所以子类就不能进行重写了 public final void init(ServletConfig servletConfig) throws ServletException { // 进行赋值 this.config = servletConfig; } 复制代码

思考: 上面加上GenerivServlet类中init()方法加上final确实做到了绝对安全,但是如果子类Login就想重写init()方法呢?

答:多写一个无参的init()方法,这个无参的构造方法在原来的有参init(ServletConfig)方法中使用this.init()进行调用,以后子类只需要重写这个无参的init()方法即可。

再次进行改造

package com.bjpowernode.javaweb.adapter02; import javax.servlet.*; import java.io.IOException; public abstract class GenericServlet implements Servlet { // 定义一个成员变量 private ServletConfig config; @Override public final void init(ServletConfig servletConfig) throws ServletException { // 进行赋值 this.config = servletConfig; // 在这里调用 this.init(); } // 在写一个init方法,让子类重写这个init方法就行了 public void init(){ } @Override public ServletConfig getServletConfig() { // 这里就不需要返回null了 // 并且可以通过getServletConmgig方法拿到config对象 return config; } // 抽象方法 public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException; @Override public String getServletInfo() { return null; } @Override public void destroy() { } } 复制代码

子类Login

package com.bjpowernode.javaweb.adapter02; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; public class Login extends GenericServlet { // 子类重写无参的init方法 public void init(){ System.out.println("init 执行!"); } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { // 这样在Login子类中调用getServletConfig方法就可以拿到ServletConfig对象 // 实际上就实现了在service方法中获取到ServletConfig对象 ServletConfig config = this.getServletConfig(); System.out.println(config); } } 复制代码

总结: 实际上GenericServlet官方已经写好了,在javax.servlet.GenericServlet有这个类,所以我们只需要直接继承GenericServlet接口就行了,但是要懂其中的原理!(实际上我们常用的是继承GenericServlet的一个子类HttpServlet,后面会讲)

直接继承官方的javax.servlet.GenericServlet类

package com.bjpowernode.javaweb; import javax.servlet.GenericServlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; public class UserServlet extends GenericServlet { @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("UserServlet service execute!"); } } 复制代码

配置文件web.xml

user com.bjpowernode.javaweb.UserServlet user /user 复制代码

进行访问http://localhost:8080/servlet02/user;可以正常打印UserServlet service execute!



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有