Servlet | Servlet生命周期、适配器模式改造Servlet

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

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

系列专栏: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_第1张图片

  • 思考:服务器在启动的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赋值;此时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;

    @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!

你可能感兴趣的:(第三步:JavaWeb,servlet,java,tomcat)