[JavaWeb] Servlet详解

Servlet本质

Servlet接口定义一套处理网络请求的规范,所有实现Servlet的类都需要实现它的五个方法,其中最主要的是两个生命周期方法init() destory(),以及一个处理请求的service(),也就是说所有实现Servlet接口的类都需要有以下三个部分:

  • 初始化时所作的工作
  • 销毁时所作的工作
  • 接收到请求做出的响应
// jdk中的Servlet接口
public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

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

    String getServletInfo();

    void destroy();
}

Servlet与Tomcat

Tomcat Servlet的交互

Tomcat主要是一个Java servlet容器。

利用其Java Servlet和JSP api的实现,Tomcat能够接收来自客户机的请求,动态编译容器管理的Java类来处理相关应用程序上下文中指定的请求,并将结果返回给客户机。这种生成动态内容的方法支持非常快的、线程化的、独立于平台的请求处理。

而且,由于Java Servlet规范是为与所有其他主要Java web技术的互操作性而设计的,所以托管在Tomcat服务器上的Servlet能够利用Tomcat提供给它的任何资源。Tomcat的嵌套层次化XML配置文件允许非常细粒度的资源访问控制,同时保持松散耦合、易于部署和逻辑清晰、易于阅读的体系结构描述。

Tomcat如何与Servlet协同

Servlet规范的一个关键需求是,它们只需要处理整个数据事务处理过程的某些部分。例如,servlet代码本身不会监听某个端口上的请求,也不会直接与客户机通信,也不负责管理其对资源的访问。相反,这些东西是由Tomcat (servlet容器)管理的。

这允许在各种环境中重用servlet,或者在组件之间进行异步开发——只要不做重大更改,就可以重构连接器以提高效率,而无需对servlet代码本身进行任何更改。

Servlet生命周期

  1. Tomcat通过它的一个连接器接收来自客户机的请求。

  2. Tomcat将此请求映射到适当的引擎进行处理。这些引擎包含在其他元素中,比如主机和服务器,这限制了Tomcat搜索正确引擎的范围。

  3. 一旦请求被映射到适当的servlet, Tomcat将检查该servlet类是否已加载。如果没有,Tomcat将servlet编译成Java字节码(JVM可执行),并创建servlet的实例。

  4. Tomcat通过调用它的init方法来初始化servlet。servlet包含的代码可以读取Tomcat配置文件并相应地采取行动,还可以声明它可能需要的任何资源,这样Tomcat就可以以一种有序的、受管理的方式创建它们。

  5. 一旦servlet被初始化,Tomcat就可以调用servlet的服务方法来处理请求,该请求将作为响应返回。

  6. 在servlet的生命周期中,Tomcat和servlet可以通过使用侦听器类进行通信,侦听器类监视servlet的各种状态变化。Tomcat可以以各种方式检索和存储这些状态更改,并允许其他servlet访问它们,允许给定上下文的各种组件跨单个或多个用户会话维护和访问状态。此功能的一个实际示例是电子商务应用程序,它记住用户添加到购物车的内容,并能够将此数据传递给付款流程。

  7. Tomcat调用servlet的destroy方法来平稳地删除servlet。该操作由正在侦听的状态更改触发,或者由传递给Tomcat的外部命令触发,该命令用于取消部署servlet的上下文或关闭服务器。

编写Servlet

Servlet源码实现

Interface Servlet

// jdk中的Servlet接口
public interface Servlet {
	// Tomcat反射创建Servlet后Tomcat调用init传入对象
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();
	// 解析、响应HTTP请求Tomcat负责完成,封装成网络请求对象供Servlet使用;
	// Servlet处理后得到的结果使用response.write()方法写入response内部缓冲区,Tomcat在Servlet除了完成后拿到response,组装成HTTO响应发给客户端
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

Servlet里主要写的代码都是业务逻辑代码。和原始的、底层的解析、连接,网络请求等没有丝毫关系。该部分由Tomcat负责实现并以ServletConfig对象的形参形式传递给Servlet

GenericServlet

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) {
        this.getServletContext().log(this.getServletName() + ": " + 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();
    }
}

GenericServlet主要对Servlet接口做了如下优化:

  • 提升init方法中原本是形参的ServletConfig对象的作用域方便其他方法使用
  • init方法中调用了一个init空参方法,可以继承后对其进行定制
  • 添加了getServletConfig方法,其他方法中也可获取到ServletConfig

HttpServlet

浏览器的请求方式有Get/Post等,如果直接重写Servlet接口处理service的方法过于复杂,HttpServlet中实现了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 doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    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 {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            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);
        }
    }

Q :HttpServlet中没有一个抽象方法却是抽象类?
A :不希望被实例化,要求程序员继承后重写不同请求类型的处理函数doGet、doPost…

设计模式
上面的HttpServlet中使用了模板方法模式

总结

实现Servlet的关键在于:

  • 继承HttpServlet抽象类并重写doGet() doPost()等方法
参考资料:

【Servlet与Tomcat交互与生命周期】https://www.mulesoft.com/tcat/tomcat-servlet
【Servlet源码分析】servlet的本质是什么,它是如何工作的? - bravo1988的回答 - 知乎
https://www.zhihu.com/question/21416727/answer/690289895

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值