Servlet 源码解析

版权声明:作者:N3verL4nd 出处: https://blog.csdn.net/lgh1992314/article/details/79941355

Servlet 继承层次

这里写图片描述

Servlet 工作原理

这里写图片描述

  1. Web Client(如nginx)向 Servlet 容器(如Tomcat)发出Http请求;
  2. Servlet 容器接收 Web Client 的请求;
  3. Servlet 容器创建一个 HttpRequest 对象,将 Web Client 请求的信息封装到这个对象中;(对于Tomcat实现:org.apache.coyote.Request -> org.apache.catalina.connector.Request -> org.apache.catalina.connector.RequestFacade;后两个实现了 HttpServletRequest 接口,最后一个使我们在Servlet 拿到的 request实例)
  4. Servlet 容器创建一个 HttpResponse 对象;
  5. Servlet 容器调用 HttpServlet 对象的 service 方法,把 HttpRequest 对象与 HttpResponse 对象作为参数传给 service 方法;
  6. HttpServlet 调用 HttpRequest 对象的有关方法,获取Http请求信息;
  7. HttpServlet 调用 HttpResponse 对象的有关方法,生成响应数据;
  8. Servlet 容器把 HttpServlet 的响应结果传给 Web Client;

在整个 Servlet 的生命周期过程中,创建 Servlet 实例、调用实例的 init() 和destroy() 方法都只进行一次,当初始化完成后,Servlet容器通过调用相应的 Servlet 的 service() 方法,为接收到的请求提供服务。

Servlet 创建

  1. 默认情况下,在Servlet容器启动后:客户首次向 Servlet 发出请求,Servlet容器会判断内存中是否存在指定的 Servlet 对象,如果没有则创建它,然后根据客户的请求创建HttpRequest、HttpResponse对象,从而调用Servlet对象的service方法;
  2. Servlet容器启动时:当web.xml文件中如果<servlet>元素中指定了<load-on-startup>子元素时并且其值大于等于0,Servlet容器在启动web服务器时,将按照顺序创建并初始化Servlet对象;
  3. Servlet 的类文件被更新后,重新创建Servlet。

可以看到 Servlet 是单例的。

Servlet 销毁

当Web服务器认为 Servlet 实例没有存在的必要了,比如应用重新装载,或服务器关闭,以及 Servlet 很长时间都没有被访问过。服务器可以从内存中销毁(也叫卸载)该实例。Web服务器必须保证在卸载 Servlet 实例之前调用该实例的 destroy() 方法,以便回收 Servlet 申请的资源或进行其它的重要的处理。
Web服务器必须保证调用 destroy() 方法之前,让所有正在运行在该实例的service() 方法中的线程退出或者等待这些线程一段时间。一旦 destroy() 方法已经执行,Web服务器将拒绝所有的新到来的对该 Servlet 实例的请求,destroy() 方法退出,该 Servlet 实例即可以被垃圾回收。


核心类

ServletConfig

/**
 * servlet 容器使用的 servlet 配置对象
 * 在初始化期间将信息传递给 servlet
 */
 public interface ServletConfig {

    /**
     * 返回此 servlet 实例的名称
     * 在 web.xml 对应于 servlet-name 结点(<servlet><servlet-name></servlet-name></servlet>)
     * 或者对应于注解 @WebServlet(value = "/hello", name = "HelloServlet") 中的 name 属性
     */
    public String getServletName();

    /**
     * 返回调用者正在执行的 ServletContext 的引用
     */
    public ServletContext getServletContext();

    /**
     * 获取具有给定名称的初始化参数的值
     * 在 web.xml 对应于 <init-param><param-name></param-name><param-value></param-value></init-param>
     * 使用注解 @WebServlet(value = "/hello", name = "HelloServlet", initParams = {@WebInitParam(name = "name", value = "lgh")})
     */
    public String getInitParameter(String name);

    /**
     * 以 Enumeration<String> 形式返回 Servlet 的初始化参数的名称
     * 如果 Servlet 没有初始化参数,则返回一个空的 Enumeration
     */
    public Enumeration<String> getInitParameterNames();
}

Servlet

Servlet

public interface Servlet {

    /**
     * 负责初始化Servlet对象
     * 在Servlet的生命周期中,该方法执行一次
     * 该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题
     */
    public void init(ServletConfig config) throws ServletException;   

    /**
     * 返回 Servlet 配置和初始化参数
    */
    public ServletConfig getServletConfig();    

    /**
     * 负责响应客户的请求
     * 为了提高效率,Servlet规范要求一个 Servlet 实例必须能够同时服务于多个客户端请求
     * 即 service() 方法运行在多线程的环境下,Servlet 开发者必须保证该方法的线程安全性
     */
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    /**
     * 返回有关 servlet 的信息,例如作者/版本/版权
    */
    public String getServletInfo();   

    /**
     * 当 Servlet 对象退出生命周期时,负责释放占用的资源
     */
    public void destroy();
}

本质上讲,init(ServletConfig config)destroy() 就相当于构造函数和析构函数(回调方法)。

GenericServlet

public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable{}

GenericServlet 实现了 ServletServletConfig 里的所有方法,并且额外提供了一个初始化方法 init()//实现为空 和两个打印日志的方法log(String msg)log(String message, Throwable t)

GenericServlet 里的 service 是一个抽象方法需要我们实现。

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

GenericServlet 里的其他大部分操作依赖于 ServletConfig
private transient ServletConfig config;

HttpServlet

public abstract class HttpServlet extends GenericServlet
{
    //...
    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 {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            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 {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

    @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 = (HttpServletRequest) req;
        response = (HttpServletResponse) res;

        service(request, response);
    }
    //...
}

我们在使用 Servlet 过程中,一般都是继承 javax.servlet.http.HttpServlet 然后重写相关的 doXXX 方法。

HttpServlet 里的 service 方法接收标准HTTP请求,并将它们分派到此类中定义的doXXX方法。 此方法是 Servlet.service 方法的HTTP特定版本。 没有必要重写此方法。

ServletContext

web容器在启动时,它会为每个web程序都创建一个对应的ServletContext对象,它代表当前的web应用。
常用的方法介绍:

getInitParameter(String name)  获取name名称的初始化参数的值

getResourceAsStream(String path)  输入流的形式返回path对应的资源,path参数必须以“/”

getInitParameterNames()  获取所有初始化参数的名称,返回的结果是一个枚举值

getRequestDispatcher(String name)  根据转发的地址获取 RequestDispatcher对象

setAttribute()  设置属性值

getAttributeNames()  获取所有属性值名称

参考:
https://my.oschina.net/xianggao/blog/395327
https://my.oschina.net/xianggao/blog/395020
https://www.ibm.com/developerworks/cn/java/j-lo-servlet/

猜你喜欢

转载自blog.csdn.net/lgh1992314/article/details/79941355