Servlet | Servlet生命周期、适配器模式改造Servlet
创始人
2024-04-04 12:12:47

✅作者简介:一位材料转码农的选手,希望一起努力,一起进步!

📃个人主页:@每天都要敲代码的个人主页

🔥系列专栏:Web后端 | Servlet

💬推荐一款模拟面试、刷题神器,从基础到大厂面试题👉点击跳转刷题网站进行注册学习

目录

一:Servlet生命周期

二: 适配器模式改造Servlet

三:改造GenericServlet

结束语


一: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无参数构造方法执行了");}@Overridepublic void init(ServletConfig servletConfig) throws ServletException {}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {}@Overridepublic String getServletInfo() {return null;}@Overridepublic void destroy() {}
}

配置文件


aservletcom.bjpowernode.javaweb.servlet.AServletaservlet/a

当然也可以让服务器启动的时候创建Servlet对象?

  • 在servlet标签中添加子标签,在该子标签中填写正整数,越小的整数优先级越高。

  • 加上这个字标签后,构造方法就执行了,实际上也就是实例化了Servlet对象。

aservletcom.bjpowernode.javaweb.servlet.AServlet1

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()方法通常是完成初始化操作的@Overridepublic void init(ServletConfig servletConfig) throws ServletException {System.out.println("A.Servlet's init method execute!");}// service()方法,是处理用户请求核心方法// 只要用户发送一次请求,service()方法必然会执行一次@Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("A.Servlet's service method execute!");}// destroy()方法也是只执行一次// Tomcat服务器在销毁AServlet对象之前会调用一次destroy()方法// 注意:destroy()方法在执行的时候,AServlet对象的内存还没有被销毁,是即将被销毁的状态@Overridepublic void destroy() {System.out.println("A.Servlet's destroy method execute!");}@Overridepublic String getServletInfo() {return null;}@Overridepublic 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 {@Overridepublic void m1() {}@Overridepublic void m2() {}@Overridepublic void m3() {}@Overridepublic void m4() {}@Overridepublic void m5() {}@Overridepublic void m6() {}@Overridepublic void m7() {}@Override// 把这个常用的方法写成抽象方法public abstract void core();
}

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

package com.bjpowernode.javaweb.adapter;public class AServlet extends UserAdapter{@Overridepublic 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 {@Overridepublic void init(ServletConfig servletConfig) throws ServletException {}@Overridepublic ServletConfig getServletConfig() {return null;}// 抽象方法public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)throws ServletException, IOException;@Overridepublic String getServletInfo() {return null;}@Overridepublic 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 {@Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("正在登陆请稍后。。。");}
}

web.xml配置文件


logincom.bjpowernode.javaweb.adapter02.Loginlogin/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;@Overridepublic void init(ServletConfig servletConfig) throws ServletException {// 进行赋值this.config = servletConfig;}@Overridepublic ServletConfig getServletConfig() {// 这里就不需要返回null了// 并且可以通过getServletConmfig方法拿到config对象return config;}// 抽象方法public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)throws ServletException, IOException;@Overridepublic String getServletInfo() {return null;}@Overridepublic 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 {@Overridepublic 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赋值;此时coonfig就会又为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;@Overridepublic final void init(ServletConfig servletConfig) throws ServletException {// 进行赋值this.config = servletConfig;// 在这里调用this.init();}// 在写一个init方法,让子类重写这个init方法就行了public void init(){}@Overridepublic ServletConfig getServletConfig() {// 这里就不需要返回null了// 并且可以通过getServletConmgig方法拿到config对象return config;}// 抽象方法public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)throws ServletException, IOException;@Overridepublic String getServletInfo() {return null;}@Overridepublic 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 执行!");}@Overridepublic 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 {@Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("UserServlet service execute!");}
}

配置文件web.xml


usercom.bjpowernode.javaweb.UserServletuser/user

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

结束语

今天的分享就到这里啦!快快通过下方链接注册加入刷题大军吧!

各种大厂面试真题在等你哦!
💬刷题神器,从基础到大厂面试题👉点击跳转刷题网站进行注册学习

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...