JavaWeb-GenericServlet源码分析(适配器/模板方法)

发布于:2025-02-26 ⋅ 阅读:(9) ⋅ 点赞:(0)

类直接实现Servlet接口的弊端

Servlet接口的方法

在这里插入图片描述
上面是jakarta.servlet.Servlet接口中的方法

  • init(): 初始化Servlet对象的相关信息
  • service(): 业务的核心方法, 也是Tomcat调用该对象实现逻辑的入口
  • destroy(): 销毁Servlet对象的信息
  • getServletConfig(): 获取ServletConfig对象(这个下面再说)
  • getServletInfo(): 获取一些无用的信息(作者, 版本号之类的)

适配器设计模式

为什么类不能直接实现Servlet接口呢, 是因为对于一个Servlet对象来说, 除了service方法, 其他的对象都是不经常用的, 如果我任意一个类都去实现Servlet接口中的所有方法, 那最后的结果就是代码十分的冗余…

适配器设计模式可以类比为手机不能直接插在220V电源上, 需要一个适配器的充电头进行转换…

所以我们创建一个适配器(也就是一个抽象类), 实现这个Servlet接口中的大部分方法, 只把一些需要子类重定义的方法抽象出来, 这样就可以使得代码的冗余度大大降低, 这其实就是适配器设计模式的核心

我们创建一个适配器的抽象类代码如下

import jakarta.servlet.*;

import java.io.IOException;

/**
 * 适配器设计模式
 * 因为我们并不是所有的Servlet对象实现的时候并不需要所有的Servlet方法
 * 需要我们添加一个适配器, 实现里面的大部分方法即可(除了Service)
 */
public abstract class ServletAdapt implements Servlet{
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public abstract void service(ServletRequest request, ServletResponse response) 
            throws ServletException, IOException;

    @Override
    public String getServletInfo() {
        return "";
    }

    @Override
    public void destroy() {

    }
}

下面我们创建一个子类实现这个适配器中的抽象方法

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;

import java.io.IOException;

public class UserServlet extends ServletAdapt{
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 重写适配器的service方法对外提供服务即可
        System.out.println("UserServlet service method called");
    }
}

适配器对象的改造

我们官方都把适配器对象称之为GenericServlet(标题的由来)


关于init方法的ServletConfig对象来源

在这里插入图片描述
思考这个ServletConfig对象是谁传过来的, 我们大致说一下底层的Tomcat伪代码

class Tomcat{
    public static void main(String[] args) {
        
        // 首先通过反射机制拿到相关类对象
        Class clazz = Class.forName("全限定类名");
        
        // 构造出该类的一个实例
        Object object = clazz.getConstructor().newInstance();
        
        // 向下转型成为Servlet对象
        Servlet servletImp = (Servlet) object; 
        
        // 创建一个ServletConfig对象
        ServletConfig servletConfig = new ServletConfigImp();
        
        // 初始化这个类的实例
        servletImp.init(servletConfig);
        
        // 使用service方法提供服务
        servletImp.service(ServletRequest request, ServletResponse response);
    }
}

所以我们的ServletConfig对象, 是 Tomcat服务器创建出来的
我们想查看以下这个实现ServletConfig接口的对象的信息(改造init方法)

在这里插入图片描述

web.xml文件中添加映射信息如下

在这里插入图片描述

在浏览器中输入URL访问…

在这里插入图片描述
上面我们说了, 这个对象是 Tomcat 服务器实现的, 我们现在找到上次下载的关于 Tomcat 服务器的源码找到这个类进行分析

在这里插入图片描述
可以发现这个类实现了jakarta.servlet.ServletConfig 接口, 这同时也证明了, 我们的Tomcat 服务器实现了 Servlet规范


使用模板方法设计模式改造init方法

关于模板方法设计模式, 核心总结就是下面的一句话

  • 对拓展开放, 对修改关闭

假如, 我们想要这个 ServletConfig 对象那应该怎么办呢

init方法的其中一个参数就是ServletConfig, 所以我们可以创建一个实例变量接住这个临时变量…

在这里插入图片描述

但是假如子类继承了这个适配器, 并且尝试对其中的init方法进行重写, 那我们的config 对象不就有变为空了吗,
所以一个简单的策略是直接把init方法使用final修饰, 此时不允许子类进行重写…

在这里插入图片描述

但是还有一个问题, 假设我们的子类真的想要重写init方法, 但是又不想config对象变成空, 那我们就可以采用模板方法设计模式

在这里插入图片描述
在这里插入图片描述

这样就会在不破坏原有代码的基础上进行代码能力的扩展, 这就是模板方法设计模式

在这里插入图片描述
可以看到, 在源代码基本功能不变的基础上, 重写的方法正常执行了…


GenericServlet内置抽象类

其实我们上面的分析出来的内容, 实际上内置的类已经实现了相关方法了

GenericServlet实现了Servlet接口 & ServletConfig接口

下面是我们的GenericServlet的源码解析

在这里插入图片描述

可以看到里面init方法正是使用了模板方法设计模式进行的设计, 十分巧妙…

完整源码

package jakarta.servlet;

import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        return this.getServletConfig().getInitParameter(name);
    }

    public Enumeration<String> getInitParameterNames() {
        return this.getServletConfig().getInitParameterNames();
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }

    public String getServletInfo() {
        return "";
    }

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }

    public void log(String message) {
        ServletContext var10000 = this.getServletContext();
        String var10001 = this.getServletName();
        var10000.log(var10001 + ": " + message);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        return this.config.getServletName();
    }
}

我们先介绍其中的一部分方法, 等下学习完ServletConfig之后把全部的方法介绍一下…

在这里插入图片描述
在这里插入图片描述


ServletConfig接口

ServletConfig接口简介

我们上面学习Servlet接口的时候, 知道有一个方法getServletConfig, 这个方法的作用就是返回一个ServletConfig对象其实也就是上面我们分析的Tomcat服务器创建传入的那个对象

  • 一个Servlet对象有且仅有唯一一个ServletConfig对象
  • 保存的是该Servlet对象在web.xml中配置的servlet标签一些信息

下面是ServletConfig接口中的常见的方法
在这里插入图片描述

  • getInitParameter(String name): 返回一个name对应的参数值
  • getInitParameterNames(): 返回一个集合保存所有的name值
  • getServletContext(): 返回一个ServletContext对象
  • getServletName(): 返回配置的servlet-name

测试

我们在web.xml中配置的相关信息如下

 <servlet>
        <servlet-name>user</servlet-name>
        <servlet-class>com.qnn.servlet.UserServlet</servlet-class>
        <!--下面配置的相关参数信息, 可以通过ServletConfig对象中的方法拿到-->
        <init-param>
            <param-name>userName</param-name>
            <param-value>qiannian</param-value>
        </init-param>
        <init-param>
            <param-name>account</param-name>
            <param-value>root</param-value>
        </init-param>
        <init-param>
            <param-name>password</param-name>
            <param-value>123456</param-value>
        </init-param>
    </servlet>

关于我们Servlet对象实现的service()方法的内容如下

 @Override
    // 测试一下ServletConfig对象中保存的相关信息
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {

        // 改变输出的方法, 并获取一个输出流对象
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        // 通过Servlet接口中的方法获得一个ServletConfig对象
        ServletConfig servletConfig = this.getServletConfig();

        // 利用ServletConfig对象中的相关的方法来输出我们当前的Servlet对象在web.xml中配置的一些信息
        // 这里有一个小点就是, 这个集合类其中的元素不可以使用foreach来进行循环的遍历...
        out.print("<h3>" + servletConfig.getServletName() + "</h3>");
        out.print("<br/>");
        Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            String initParameterName = initParameterNames.nextElement();
            out.print("<br/>");
            out.print("<h3>" + initParameterName + " : " + servletConfig.getInitParameter(initParameterName) + "</h3>");
        }

    }

结果如下

在这里插入图片描述


再谈GenericServlet抽象类

有了上面的基础, 我们想要理解GenericServlet抽象类中的内容就更容易了…具体不再说了…


网站公告

今日签到

点亮在社区的每一天
去签到