前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Servlet API 架构详解Servlet、GenericServlet、HttpServletServletConfigServletContext

Servlet API 架构详解Servlet、GenericServlet、HttpServletServletConfigServletContext

作者头像
desperate633
发布2018-08-22 15:49:49
6560
发布2018-08-22 15:49:49
举报
文章被收录于专栏:desperate633desperate633

Servlet、GenericServlet、HttpServlet

Servlet

对于web容易来说,所有servlet都必须有的行为,规范在servlet接口中:

代码语言:javascript
复制
package javax.servlet;

import java.io.IOException;

public interface Servlet {
    public void init(ServletConfig config) throws ServletException;
    public ServletConfig getServletConfig();
    public void service(ServletRequest req, ServletResponse res) 
                   throws ServletException, IOException;
    public String getServletInfo();
    public void destroy();
}

当我们所实例化的一个servlet被容器载入后,容器会建立该servlet的接口的实例,根据servlet设定的一些初始化和其他信息(一般在web.xml中),创建一个servletConfig(稍后会介绍servletConfig接口)的实例,然后调用servlet接口的init方法并且传入一个servletconfig实例,完成servlet的初始化。

servlet初始化完成之后,如果这时候收到了对于某个servlet的请求,那么这时候容器就会先通过urlPattern找到这个servlet,然后调用这个servlet的service方法,传入ServletRequest、ServletResponse的实例,注意这两个接口并不是http开头的,这好似因为最初设计的时候,并没有限制servlet只能用于http协议上,但实际上大到现在servlet还是只用在了http上哈哈。

当一个servlet已经没用了,容器准备回收它之前,容器会呼叫它的destroy方法,来执行一些资源的回收与释放。

GenericServlet

GenericServlet是一个抽象类,它实现了servlet接口

代码语言:javascript
复制
package javax.servlet;

import java.io.IOException;
import java.util.Enumeration;
public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable {
    private transient ServletConfig config;
    public GenericServlet() {}
    public void destroy() {}
    public String getInitParameter(String name) {
        return getServletConfig().getInitParameter(name);
    }
    public Enumeration<String> getInitParameterNames() {
        return getServletConfig().getInitParameterNames();
    }      
    public ServletConfig getServletConfig() {
        return config;
    }
    public ServletContext getServletContext() {
        return 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 msg) {
        getServletContext().log(getServletName() + ": "+ msg);
    }
    public void log(String message, Throwable t) {
        getServletContext().log(getServletName() + ": " + message, t);
    }
    public abstract void service(ServletRequest req, ServletResponse res)
                             throws ServletException, IOException;
    public String getServletName() {
        return config.getServletName();
    }
}

GenericServlet实现了servlet的接口,其中有一个接受servletconfig参数的init方法,会传入servletconfig的实例,之后才会调用无参数的init方法,所以** 如果我们重新定义servlet的初始化,不建议直接重定义接受参数的init方法,而是建议重新定义无参数的init方法 ** GenericServlet對Servlet介面的service()方法沒有實作,僅標示為abstract,service()方法的實作由子類別

HttpServlet

我们看到GenericServlet对于servlet接口的service方法没有具体实现,标记为抽象方法,这是因为service方法交给了它的子类去实现,比如具体的httpservlet:

代码语言:javascript
复制
package javax.servlet.http;

略...

public abstract class HttpServlet extends GenericServlet {
    public HttpServlet() {}

    略...

    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;
        
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }

    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) {
                doGet(req, resp);
            } else {
        略...          
    }
}

HttpServlet实现了javax.servlet.http接口,同时又继承实现了GenericServlet未实现的service方法。在web容器的环境下,实际上传入的请求和响应对象的是HttpServletRequest、HttpServletResponse接口。他们其实都是ServletRequest、ServletResponse的子接口,增加了一些关于http的操作。

image.png

ServletConfig

在web容易启动后,会读取(Annotation)或web.xml里面的设定,根据其中对每个servlet的设定将servlet载入并且实例化,并未每个servlet的配置产生一个servletconfig的对象,随后调用servlet的接口的init方法,并且将产生的servletconfig的对象当作参数传入。

servletconfig其实就是将(Annotation)或web.xml中为servlet配置的信息整合保存到一个对象中,容器会为每个servlet产生一个servlet对象和一个servletconfig对象。genericservlet同时实现了这两个接口,servlet和servletconfig。

GenericServlet主要的目的,就是将servlet调用init方法传入的config对象封装起来:

代码语言:javascript
复制
private transient ServletConfig config;
public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
}
public void init() throws ServletException {
}

GenericServlet在實作Servlet的init()方法時,也呼叫了另一個無參數的init()方法,基本上你在撰寫Servlet時,如果有一些初始時所要執行的動作,可以重新定義這個無參數的init()方法,而不是直接重新定義有ServletConfig參數的init()方法。

对于一般的对象,我们如果想在初始化之后,执行一些要执行的动作,我们一般需要重新定义构造函数,在构造函数中完成后续的动作即可。但在写servlet的时候,却不一样,如果我们想要执行一些与web资源相关的初始化操作时,我们要重新定义init方法。举个例子,如果我们想要使用servletconfig来做一些事情,我们不能在构造函数中定义,因为我们实例化servlet的时候,容器还没有执行init方法传入servletconfig对象,所以我们这时候还没有servletconfig的实例

GenericServlet也包括了Servlet與ServletConfig所定義方法的簡單實作,實作內容主要是透過ServletConfig來取得一些相關資訊,例如:

代码语言:javascript
复制
public ServletConfig getServletConfig() {
    return config;
}
public String getInitParameter(String name) {
    return getServletConfig().getInitParameter(name);
}
public Enumeration getInitParameterNames() {
    return getServletConfig().getInitParameterNames();
}   
public ServletContext getServletContext() {
    return getServletConfig().getServletContext();
}

所以当我们继承httpservlet实现一个servlet的时候,就可以直接利用这些方法取得相关的servletconfig的信息,而不用意识到servletconfig的存在。 若要使用標註設定個別Servlet的初始參數,可以在@WebServlet中使用@WebInitParam設定initParams。例如:

代码语言:javascript
复制
...
@WebServlet(name="ServletConfigDemo", urlPatterns={"/conf"},
            initParams={
                @WebInitParam(name = "PARAM1", value = "VALUE1"),
                @WebInitParam(name = "PARAM2", value = "VALUE2")
            }
)
public class ServletConfigDemo extends HttpServlet {
    private String PARAM1;
    private String PARAM2;
    
    public void init() throws ServletException {
        super.init();
        PARAM1 = getServletConfig().getInitParameter("PARAM1");
        PARAM2 = getServletConfig().getInitParameter("PARAM2");
    }
    ....
}

若要在web.xml中設定個別Servlet的初始參數,可以在<servlet>標籤之中,使用<init-param>進行設定,web.xml中的設定會覆蓋標註的設定。例如:

代码语言:javascript
复制
...
<servlet>
    <servlet-name>ServletConfigDemo</servlet-name>
    <servlet-class>cc.openhome.ServletConfigDemo</servlet-class>
    <init-param>
        <param-name>PARAM1</param-name>
        <param-value>VALUE1</param-value>
    </init-param>
    <init-param>
        <param-name>PARAM2</param-name>
        <param-value>VALUE2</param-value>
    </init-param>
</servlet>
...

由於ServletConfig必須在Web容器將Servlet實例化後,呼叫有參數的init()方法再將之傳入,是與Web應用程式資源相關的物件,所以在繼承HttpServlet後,通常會重新定義無參數的init()方法以進行Servlet初始參數的取得。GenericServlet定義了一些方法,將ServletConfig封裝起來,便於取得設定資訊,所以取得Servlet初始參數的程式碼也可以改寫為:

代码语言:javascript
复制
...
@WebServlet(name="ServletConfigDemo", urlPatterns={"/conf"},
            initParams={
                @WebInitParam(name = "PARAM1", value = "VALUE1"),
                @WebInitParam(name = "PARAM2", value = "VALUE2")
            }
)
public class AddMessage extends HttpServlet {
    private String PARAM1;
    private String PARAM2;
    public void init() throws ServletException {
        super.init();
        PARAM1 = getInitParameter("PARAM1");
        PARAM2 = getInitParameter("PARAM2");
    }
    ….
}

下面這個範例簡單地示範如何設定、使用Servlet初始參數,其中登入成功與失敗的網頁,可以由初始參數設定來決定:

代码语言:javascript
复制
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet(name = "LoginServlet", urlPatterns = {"/login.do"},
            initParams = {
                @WebInitParam(name = "SUCCESS", value = "success.view"),
                @WebInitParam(name = "ERROR", value = "error.view")
            }
)
public class LoginServlet extends HttpServlet {
    private String SUCCESS_VIEW;
    private String ERROR_VIEW;

    @Override
    public void init() throws ServletException {
        super.init();
        SUCCESS_VIEW = this.getInitParameter("SUCCESS");
        ERROR_VIEW = this.getInitParameter("ERROR");
    }

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
                            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        String name = request.getParameter("name");
        String passwd = request.getParameter("passwd");
        if ("caterpillar".equals(name) && "123456".equals(passwd)) {
            request.getRequestDispatcher(SUCCESS_VIEW)
                    .forward(request, response);
        } else {
            request.getRequestDispatcher(ERROR_VIEW)
                    .forward(request, response);
        }
    }
}

ServletContext

ServletContext接口定义了Servlet所运行程序的一个总体的行为与信息,你可以使用ServletContext实例对象來取得所请求资源的URL、应用程序的初始化參數,甚至动态的设定Servlet实例。

ServletContext本身的名字让人产生误解,因为它以Servlet作为开头,容易被误认为只是一个单独的Servlet的代表对象。但实际上,当整个Web应用程序加载进入Web容器之后,容器会生成一个ServletContext对象作为整个应用程序的代表,并设定给ServletConfig,你只要通过调用ServletConfig的getServletContext()方法就可以取得ServletContext对象。 以下我们先介绍几个容易搞错,需要注意的方法:

  • getRequestDispatcher()方法可以取得RequestDispatcher实例对象,使用时路徑的指定必须以"/"作为开头,这个斜杠代表应用程序的根目录(Context Root)。
代码语言:javascript
复制
context.getRequestDispatcher("/pages/some.jsp").forward(request, response);

以"/"作为开头有时候也可以理解为程序环境的初始化路径(Context-relative),沒有以"/"作为开头的(Request-relative)路径则成为(Request-relative)请求相对路径。事实上,HttpServletRequest 的getRequestDispatcher()方法在实例化的时候,若是环境相对路径,就直接交给ServletContext的 getRequestDispatcher();若是请求相对路径,则会先转换为环境相对路径再转发给ServletContext的 getRequestDispatcher()來取得RequestDispatcher。

  • 如果想要知道Web程序中的某个目录中有哪些档案,则可以使用getResourcePaths()方法。例如: for(String avatar : getServletContext().getResourcePaths("/")) { // 顯示 avatar 文字... }

使用時指定路徑必須以"/"作為開頭,表示相對於應用程式環境根目錄,傳回的路徑會像是:

代码语言:javascript
复制
/welcome.html
/catalog/
/catalog/index.html
/catalog/products.html
/customer/
/customer/login.jsp
/WEB-INF/
/WEB-INF/web.xml
/WEB-INF/classes/com.acme.OrderServlet.class

可以看到,这个方法会将WEB-INF的目录信息文件信息也列出来。如果是目录信息,会以“/”作为结尾。

如果想在Web程序中读取程序目录中某个文档的内容,则可以使用getResourceAsStream()方法,使用时指定路径以"/"作为开头,表示相对于应用程序的环境根目录,或者相对是/WEB-INF/lib中JAR档案里META-INF/resources的路徑(Web程序中,JAR檔案必須放在/WEB-INF/lib中,這是規定),执行结果会返回一个InputStream对象,接下来我们就可以用这个对象来读取文档的内容。底下是個讀取PDF並傳送給客戶端的範例:

代码语言:javascript
复制
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet(name="Ebook", urlPatterns={"/ebook.view"},
            initParams={
                @WebInitParam(name="PDF_FILE", value="/WEB-INF/jdbc.pdf")
            }
)
public class Ebook extends HttpServlet {
    private String PDF_FILE;

    @Override
    public void init() throws ServletException {
        super.init();
        PDF_FILE = getInitParameter("PDF_FILE");
    }
    
    protected void doGet(HttpServletRequest request,
                          HttpServletResponse response)
                      throws ServletException, IOException {
        String passwd = request.getParameter("passwd");
        if("123456".equals(passwd)) {
            response.setContentType("application/pdf");
            InputStream in = this.getServletContext()
                                 .getResourceAsStream(PDF_FILE);
            OutputStream out = response.getOutputStream();
            byte[] buffer = new byte[1024];
            int length = -1;
            while((length = in.read(buffer)) != -1) {
                out.write(buffer, 0, length);
            }
            in.close();
            out.close();
        }
    }
}

每个Web程序都会有一个相对应的ServletContext,针对「应用程序」初始化时所需用到的一些参数的信息,你可以在web.xml中摄制应用程序的初始化参数,設定時使用<context-param>标签来定义。例如:

代码语言:javascript
复制
<web-app ...>
    <context-param>
       <param-name>MESSAGE</param-name>
       <param-value>/WEB-INF/messages.txt</param-value>
    </context-param>
    …
</web-app>

在程序中可以通过调用ServletContext的getInitParameter()方法來读取初始化参数。因此Web應用程式初始參數常被稱為ServletContext初始參數。

在整個Web程序的生命周期中,Servlet所需共用的信息参数,則可以设定为ServletContext属性。由于ServletContext在Web应用程序存活的时候会一直存在,所以設定為ServletContext属性的信息,除非你主动移除,否则也是一直存在web程序中的。

可以透過ServletContext的setAttribute()方法設定物件為ServletContext屬性,之後可透過ServletContext的getAttribute()方法取出該屬性。若要移除屬性,則透過ServletContext的removeAttribute()方法。

注意!ServletContext不是线程安全的,所以必要时我们还需要注意存取变量属性时的线程安全问题。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.05.18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Servlet、GenericServlet、HttpServlet
    • Servlet
      • GenericServlet
        • HttpServlet
        • ServletConfig
        • ServletContext
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档