Java中的Servlet你了解吗?

☆* o(≧▽≦)o *☆嗨~我是小奥
个人博客:小奥的博客
CSDN:个人CSDN
Github:传送门
面经分享(牛客主页):传送门
文章作者技术和水平有限,如果文中出现错误,希望大家多多指正!
如果觉得内容还不错,欢迎点赞收藏关注哟! ❤️

文章目录

  • Servlet详解
    • 1. Servlet接口
      • (1) ServletConfig
      • (2) ServletContext
      • (3) 使用
    • 2.GenericServlet
    • 3. HttpServlet

Servlet详解

Servlet是Server + Applet的缩写,表示一个服务器应用。
其实,Servlet就是一套规范,我们按照这套规范写出来的代码就可以直接在Java服务器上运行。

Servlet3.1中的Servlet结构如下图所示:
Java中的Servlet你了解吗?_第1张图片

1. Servlet接口

说到规范,那当然还是接口最重要了,我们看一下接口的定义:

package javax.servlet;

import java.io.IOException;

/**
 * 定义所有servlet必须实现的方法。
 * servlet是在Web服务器中运行的小Java程序。servlet通常通过HTTP(超文本传输协议)接收和响应来自Web客户机的请求。
 * 要实现这个接口,您可以编写一个扩展javax.servlet.GenericServlet的通用servlet,或者编写一个扩展javax.servlet.http.HttpServlet的HTTP servlet。
 * 该接口定义了初始化servlet、服务请求和从服务器中删除servlet的方法。这些被称为生命周期方法,按以下顺序调用:
 * 先构造servlet,然后用init方法进行初始化。
 * 从客户机到服务方法的任何调用都将被处理。
 * servlet退出服务,然后使用destroy方法销毁,然后进行垃圾收集并最终完成。
 * 除了生命周期方法之外,该接口还提供了getServletConfig方法和getServletInfo方法,
 * servlet可以使用该方法获取任何启动信息,getServletInfo方法允许servlet返回关于自身的基本信息,例如作者、版本和版权。
 */
public interface Servlet {

    /**
     * 由servlet容器调用,以指示servlet正在被放入服务中。
     * servlet容器在实例化servlet之后只调用init方法一次。init方法必须成功完成,servlet才能接收任何请求。
     * 如果使用init方法,servlet容器不能将servlet放入服务中:
     * 1.抛出ServletException
	 * 2.没有在Web服务器定义的时间段内返回
     */
    public void init(ServletConfig config) throws ServletException;
    
    /**
     *
     * 返回一个ServletConfig对象,其中包含此servlet的初始化和启动参数。
     * 返回的ServletConfig对象是传递给init方法的对象。
     * 这个接口的实现负责存储ServletConfig对象,以便这个方法可以返回它。实现这个接口的GenericServlet类已经做到了这一点。
     */
    public ServletConfig getServletConfig();
    
    /**
     * 由servlet容器调用,以允许servlet响应请求。
     * 此方法仅在servlet的init()方法成功完成后调用。
     * 响应的状态码应该始终为抛出或发送错误的servlet设置。
     * servlet通常运行在多线程servlet容器中,可以并发处理多个请求。
     * 开发人员必须注意同步访问任何共享资源,如文件、网络连接以及servlet的类和实例变量。
     */
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
	
    /**
     * 返回有关servlet的信息,如作者、版本和版权。
     * 此方法返回的字符串应该是纯文本,而不是任何类型的标记(如HTML、XML等)。
     */
    public String getServletInfo();
    
    /**
     *
     * 由servlet容器调用,以指示servlet正在退出服务。
     * 此方法仅在servlet服务方法中的所有线程都退出或超时时间过后才调用。
     * 在servlet容器调用此方法之后,它将不会在此servlet上再次调用该服务方法。
     * 此方法为servlet提供了一个机会来清理任何被占用的资源(例如,内存、文件句柄、线程),
     * 并确保任何持久状态都与servlet在内存中的当前状态同步。
     */
    public void destroy();
}

简单介绍一下几个方法的作用:

  • init:init方法在容器启动时被容器调用(当load-on-startup设置位负数或者不设置时会在Servlet第一次用到时才会被调用),只会调用一次;
  • getServletConfig:用于获取ServletConfig,即Servlet的配置;
  • service:用于具体处理一个请求;
  • getServletInfo:获取一些Servlet相关的信息,如作者、版权等,默认返回空串;
  • destroy:用于在Servlet销毁时释放资源,也只会调用一次;

(1) ServletConfig

ServletConfig指的是Servlet的配置,也就是我们在web.xml中定义的Servlet时通过init-param标签配置的参数就是通过ServletConfig来保存的。

比如定义Spring MVCServlet时指定配置文件位置的contextConfigLocation参数就保存在ServletConfig中,如下:

<servlet>
	<servlet-name>demoDispatcherservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    <init-param>
    	<param-name>contextConfigLocationparam-name>
        <param-value>demo-servlet.xmlparam-value>
    init-param>
    <load-on-startup>1load-on-startup>
servlet>

下面我们看一下ServletConfig接口的定义:

package javax.servlet;

import java.util.Enumeration;

/**
 * 一个servlet配置对象,由servlet容器使用,在初始化期间向servlet传递信息。
 */
 public interface ServletConfig {
    
    /**
     * 返回此servlet实例的名称。该名称可以通过服务器管理提供,在web应用程序部署描述符中分配,
     * 或者对于未注册(因此未命名)的servlet实例,它将是servlet的类名。
     */
    public String getServletName();

    /**
     * 返回对调用者正在其中执行的ServletContext的引用。
     */
    public ServletContext getServletContext();
    
    /**
     * 获取具有给定名称的初始化参数的值。
     */
    public String getInitParameter(String name);

    /**
     * 返回servlet初始化参数的名称,作为String对象的枚举,如果servlet没有初始化参数,则返回空枚举。
     */
    public Enumeration<String> getInitParameterNames();
}

简单介绍一下几个方法的作用:

  • getServletName:用于获取Servlet的名字,也就是我们在web.xml中定义的servlet-name
  • getServletContext:获取ServletContext,表示我们这个应用本身;
  • getInitParameter:获取init-param配置的参数;
  • getInitParameterNames:获取配置的所有init-param的名字集合;

(2) ServletContext

ServletContext非常重要,代表的其实就是我们应用本身。

那么自然,ServletContext中设置的参数可以被我们当前应用的所有Servlet共享,也就是我们在写功能时,把一些参数保存在Application中,其实就是保存在了ServletContext中了。

这里只是简单了解一下ServletContext

(3) 使用

ServletConfigServletContext最常见的使用之一是传递初始化参数



<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1" metadata-complete="true">
    <display-name>initParam Demodisplay-name>
    <context-param>
    	<param-name>contextConfigLocationparam-name>
        <param-value>application-context.xmlparam-value>
    context-param>
    <servlet>
    	<servlet-name>DemoServletservlet-name>
        <servlet-class>com.excelib.DemoServletservlet-class>
        <init-param>
        	<param-name>contextConfigLocationparam-name>
            <param-value>demo-servlet.xmlparam-value>
        init-param>
    servlet>
    ...
web-app>
  • 通过context-param配置的contextConfigLocation配置到了servletContext
  • servlet下面的init-param配置的contextConfigLocation配置到了ServletConfig

在Servlet中可以分别通过它们的getInitParameter方法进行获取,比如:

	String contextLocation = getServletConfig().getServletContext().getInitParameter("contextConfigLocation");
   String servletLocation = getServletConfig().getInitParameter("contextConfigLocation");

另外**ServletContext中非常常用的用法就是保存Application级别的属性**,比如:

	getServletContext().setAttribute("contextConfigLocation", "new path");

需要注意的是,这里设置的同名的Attribute并不会覆盖initParameter中的参数值,它们是两套数据,互不干扰。

ServletConfig不可以设置属性

2.GenericServlet

GenericServletServlet的默认实现,主要做了三件事情:

  • 实现了ServletConfig接口,我们可以直接调用该接口里面的方法;
  • 提供了无参的init方法;
  • 提供了log方法;

实现了ServletConfig接口

这样我们在需要调用ServletConfig中方法的时候可以直接调用,而不需要先获取ServletConfig了。其实是在底层帮我们内部调用了,源码如下:

    /**
     * 返回对servlet在其中运行的ServletContext的引用。
     * 提供这种方法是为了方便。它从servlet的ServletConfig对象获取上下文。
     */
    public ServletContext getServletContext() {
        ServletConfig sc = getServletConfig(); // 先获取ServletConfig
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getServletContext();
    }

提供了无参的init方法

GenericServlet实现了Servlet的init(ServletConfig config)方法,在里面将config设置给了内部变量config,然后调用无参的init方法,该方法是一个模板方法,在子类中可以通过覆盖它来完成自己的初始化工作,如下:

    /**
     * 由servlet容器调用,以指示servlet正在被放入服务中。
     * 这个实现存储了它从servlet容器接收到的ServletConfig对象,以供以后使用。
     * 当重写这种形式的方法时,调用super.init(config)。
     */
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    /**
     *一个方便的方法,可以被重写,这样就不需要调用super.init(config)。
     * 而不是重写init(ServletConfig),只需重写这个方法,它将被GenericServlet调用。
     * init (ServletConfig配置)。ServletConfig对象仍然可以通过getServletConfig来检索。
     */
    public void init() throws ServletException {

    }

这样做有三个好处:

  • 将参数config设置给内部属性config,这样就可以在ServletConfig的接口方法中直接调用config的相应方法来执行;
  • 之后写Servlet时只需要处理自己的初始逻辑,不需要再关注config
  • 重写init方法时也不需要再调用super.init(config)

提供了log方法

  • 记录日志的log
  • 记录异常的log

具体实现是通过传给ServletContext的日志实现的。

    /**
     * 将指定的消息写入servlet日志文件,并以servlet的名称作为前置。
     */     
    public void log(String msg) {
    getServletContext().log(getServletName() + ": "+ msg);
    }
   
   
    /**
     * 将给定Throwable异常的解释性消息和堆栈跟踪写入servlet日志文件,并以servlet名称为前缀。
     */   
    public void log(String message, Throwable t) {
    getServletContext().log(getServletName() + ": " + message, t);
    }

完整源码如下:

package javax.servlet;

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

/**
 * 定义一个通用的、独立于协议的servlet。要编写用于Web的HTTP servlet,请扩展javax.servlet.http.HttpServlet。
 * GenericServlet实现Servlet和ServletConfig接口。GenericServlet可以由servlet直接扩展,
 * 尽管更常见的是扩展特定于协议的子类,如HttpServlet。
 * GenericServlet使编写servlet更容易。它提供了生命周期方法init和destroy的简单版本,以及ServletConfig接口中的方法。
 * GenericServlet还实现了在ServletContext接口中声明的日志方法。
 * 要编写通用servlet,只需要重写抽象服务方法。
 */
public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable
{
    private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
    private static ResourceBundle lStrings =
        ResourceBundle.getBundle(LSTRING_FILE);

    private transient ServletConfig config;
    

    /**
     *
     * 什么也不做。所有servlet初始化都是由其中一个init方法完成的。
     *
     */
    public GenericServlet() { }
    
    
    /**
     * 由servlet容器调用,以指示servlet正在退出服务。
     */
    public void destroy() {
    }
    
    
    /**
     * 返回一个包含命名初始化参数值的字符串,如果参数不存在则返回null。
     * 提供这种方法是为了方便。它从servlet的ServletConfig对象获取指定参数的值。
     */ 
    public String getInitParameter(String name) {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getInitParameter(name);
    }
    
    
   /**
    * 以String对象的枚举形式返回servlet初始化参数的名称,如果servlet没有初始化参数,则返回空枚举。
    * 提供这种方法是为了方便。它从servlet的ServletConfig对象获取参数名。
    */
    public Enumeration<String> getInitParameterNames() {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getInitParameterNames();
    }   
     

    /**
     * 返回servlet的ServletConfig对象。
     */    
    public ServletConfig getServletConfig() {
   		return config;
    }
 
    
    /**
     * 返回对servlet在其中运行的ServletContext的引用。
     * 提供这种方法是为了方便。它从servlet的ServletConfig对象获取上下文。
     */
    public ServletContext getServletContext() {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getServletContext();
    }


    /**
     * 返回有关servlet的信息,如作者、版本和版权。默认情况下,此方法返回一个空字符串。重写此方法以使其返回一个有意义的值。
     */    
    public String getServletInfo() {
        return "";
    }


    /**
     * 由servlet容器调用,以指示servlet正在被放入服务中。
     * 这个实现存储了它从servlet容器接收到的ServletConfig对象,以供以后使用。
     * 当重写这种形式的方法时,调用super.init(config)。
     */
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }


    /**
     *一个方便的方法,可以被重写,这样就不需要调用super.init(config)。
     * 而不是重写init(ServletConfig),只需重写这个方法,它将被GenericServlet调用。
     * init (ServletConfig配置)。ServletConfig对象仍然可以通过getServletConfig来检索。
     */
    public void init() throws ServletException {

    }
    

    /**
     * 将指定的消息写入servlet日志文件,并以servlet的名称作为前置。
     */     
    public void log(String msg) {
    getServletContext().log(getServletName() + ": "+ msg);
    }
   
   
    /**
     * 将给定Throwable异常的解释性消息和堆栈跟踪写入servlet日志文件,并以servlet名称为前缀。
     */   
    public void log(String message, Throwable t) {
    getServletContext().log(getServletName() + ": " + message, t);
    }
    
    
    /**
     * 由servlet容器调用,以允许servlet响应请求。
     * 这个方法被声明为抽象的,所以子类,比如HttpServlet,必须覆盖它。
     */

    public abstract void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException;
    

    /**
     * 返回此servlet实例的名称。
     */
    public String getServletName() {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getServletName();
    }
}

3. HttpServlet

HttpServlet是用HTTP协议实现的Servlet的基类,写Servlet时直接继承它就可以了,不需要再从头实现Servlet接口。

HttpServlet是跟协议有关的,所以我们就关注一下如何处理请求。

HttpServlet的重点主要在service中,在service方法中首先将ServletRequestServletResponse转换为HttpServletRequestHttpServletResponse,然后根据Http请求的类型不同将请求路由到了不同的处理方法

    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;
        // 如果请求类型不相符,则抛出异常
        if (!(req instanceof HttpServletRequest &&
                res instanceof HttpServletResponse)) {
            throw new ServletException("non-HTTP request or response");
        }
		// 转换request和response类型
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
		// 调用http的处理方法
        service(request, response);
    }   

	/**
     * 从公共服务方法接收标准HTTP请求,并将其分派给该类中定义的doXXX方法。
     * 该方法是Servlet的http特定版本Servlet. service方法。不需要重写这个方法。
     */
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        // 获取请求类型
        String method = req.getMethod();
		// 将不同的请求类型路由到不同的处理方法
        if (method.equals(METHOD_GET)) {
            // 获取最后修改时间 
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // 如果servlet不支持if-modified-since,则无需进一步执行昂贵的逻辑
                doGet(req, resp);
            } else {
                // 获取请求头中的if-modified-since时间
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // 如果servlet修改时间较晚,则调用doGet()
                    // 向下舍入最近的秒,以进行正确比较 
                    // ifModifiedSince为-1将始终小于lastModified
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    // 返回状态码为304(未修改)
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            // 获取最后修改时间
            long lastModified = getLastModified(req);
            // 设置响应头中的最后修改时间
            maybeSetLastModified(resp, lastModified);
            // 调用doHead()
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
            
        } else {
            // 注意,这意味着没有servlet支持该服务器上任何地方请求的任何方法。
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            // 返回状态码为501(未实现)
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

你可能感兴趣的:(有趣的问题,java,servlet)