Servlet学习笔记 – GenericServlet、HttpServlet

导读:本篇文章讲解 Servlet学习笔记 – GenericServlet、HttpServlet,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1、前言

  在《Servlet工作原理和过程》中,我们学习到了:Servlet接口是Servlet技术的核心,所有的Servlet类都必须直接或者间接实现Servlet接口。Servlet接口定义了Servlet类与Servlet容器之间的契约。因为Servlet是一个接口的原因,所以如果编写一个Servlet类,就需要把该接口下的所有方法实现了,同时还需要维护一个ServletConfig对象,这样是非常麻烦。所以,Servlet api中提供了一个抽象类对Servlet接口进行了简单实现,下面我们就来学习这个抽象类。

2、Servlet接口的类层级结构

在这里插入图片描述
  如上图所示,GenericServlet实现了Servlet接口,然后HttpServlet又继承了GenericServlet抽象类。在实际的Web应用开发中,其实一般都是直接继承HttpServlet来实现自己的Servlet类的。

3、Servlet类的作用

  在前面的学习中已经知道了Servlet技术主要是用来实现Web应用的。这个Servlet类就是用来接收请求,处理请求,最后返回响应结果的。每一个Servlet类就对应了一类请求的处理方式。

4、GenericServlet抽象类

  GenericServlet类是一个与协议无关的通用的Servlet实现。如果编写Servlet类是应用在Web应用中,可以直接实现HttpServlet抽象类。GenericServlet类实现了Servlet和ServletConfig两个接口,其中Servle约束了Servlet程序与容器间调用的规约,ServletConfig接口中定义了初始化Servlet程序需要的信息和方法。

GenericServlet类的定义如下:

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

	//省略了其他内容……

}

1. 变量 ServletConfig 实例

private transient ServletConfig config;

  在GenericServlet类中定义一个ServletConfig的变量,当通过init()方法进行初始化的时候,就会把由Servlet容器创建来的ServletConfig实例记录到该变量中。而在GenericServlet类中关于ServletConfig接口的实现方法中,都是通过这个ServletConfig实例去实现的。需要注意的是:该变量用了transient关键字,表示该成员变量不参与序列化过程。

2. init()方法
  在GenericServlet类中有两个init()方法,带参数的init()方法是供Servlet容器来初始化Servlet类的实例,而不带参数的init()方法是供开发者来重写init()方法中的逻辑,因为这样开发者就不需要调用super.init(config);了。

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

}

3.针对ServletConfig接口中的方法实现
在GenericServlet类中,针对ServletConfig接口中方法实现方式是一样:首先通过getServletConfig()方法获取到保存的ServletConfig实例,如果ServletConfig实例为null,就抛出异常,否则就调用ServletConfig实例中对应的方法。示例如下:

public ServletContext getServletContext() {
        ServletConfig sc = getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(
                lStrings.getString("err.servlet_config_not_initialized"));
        }

        return sc.getServletContext();
    }

4.service()方法实现
该方法还是抽象方法,留给了子类去实现。在HttpServlet中,该方法被实现了。

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

5.log()方法实现
该方法是GenericServlet类新增的方法,实际上,还是通过getServletContext()方法获取ServletConfig中存储的ServletContext实例对象,然后再通过ServletContext实例对象的log()方法实现。

public void log(String msg) {
	getServletContext().log(getServletName() + ": "+ msg);
}

public void log(String message, Throwable t) {
	getServletContext().log(getServletName() + ": " + message, t);
}
5、HttpServlet抽象类

  HttpServlet类是抽象类GenericServlet进一步的实现,用于HTTP协议的Servlet程序,主要用来实现Web应用。在HttpServlet的子类中,一般至少需要实现下面的一个方法:

  • doGet 处理request的get请求
  • doPost 处理request的post请求
  • doPut 处理request的put请求
  • doDelete 处理request的doDelete请求
  • init、destroy 管理servlet生命周期中的相关资源
  • getServletInfo 获取当前servlet的相关信息

  在HttpServlet的子类中,一般不需要在重写service、doTrace、doOptions等方法,因为这些方法中已经通过解析Http的请求,根据请求的具体类型,分发到具体的doXXX方法中进行处理,所以实际中只需重写这些doXXX方法即可。
  在Servlet程序中,一般都是在多线程环境中使用的,而Servlet程序中访问的公共资源,就需要考虑如何同步访问这些资源。

5.1、service()方法
  在HttpServlet类的service()方法中,主要做了两件事:一是判断传进来的参数是否是HttpServletRequest、HttpServletResponse类型;二是根据请求类型,转发到对应的doXXX方法进行处理。HttpServlet的实现子类,一般不需要覆盖该方法。具体代码如下:

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

5.2、doXXX()方法
  doGet()和doPost()、doPut()、doDelete()等方法,实现方式基本一样,这里以doGet()方法为例来进行分析。
  doGet()方法,主要用来处理request的get、head请求,其中head请求本质上就是一个没有响应体的get请求。需要注意的是:实际上doGet()方法没有有效的逻辑,所以在真正使用的时候,还是需要根据需求进行实现具体逻辑的。在doHead()方法中,是通过NoBodyResponse、NoBodyOutputStream类实现了没有响应体的head请求,后续学习HttpServletResponse的时候,在具体分析。

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{

        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }
 protected void doHead(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        NoBodyResponse response = new NoBodyResponse(resp);
        
        doGet(req, response);
        response.setContentLength();
    }

5.3、doOptions()方法
  doOptions()方法主要用来返回当前Servlet应用支持的request请求方式,比如:“Allow: GET, HEAD, TRACE, OPTIONS”。该方法一般也不需要进行重写。
  在该方法的实现中,首先通过getAllDeclaredMethods()方法获取当前Servlet程序实例中的可用方法,然后再循环验证是否包含get、head、post、put、delete、trace、option方法,其中get和head是成对存在的,即get可用,namehead就可用,而trace、option默认就是可用的。
  代码比较简单,不再贴出。

5.4、doTrace()方法
  该方法主要是为了支持与客户端进行调试而存在的。该方法一般也不需要被重写。

protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        int responseLength;
        String CRLF = "\r\n";
        StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI())
            .append(" ").append(req.getProtocol());
            
        Enumeration<String> reqHeaderEnum = req.getHeaderNames();

        while( reqHeaderEnum.hasMoreElements() ) {
            String headerName = reqHeaderEnum.nextElement();
            buffer.append(CRLF).append(headerName).append(": ")
                .append(req.getHeader(headerName));
        }

        buffer.append(CRLF);

        responseLength = buffer.length();

        resp.setContentType("message/http");
        resp.setContentLength(responseLength);
        ServletOutputStream out = resp.getOutputStream();
        out.print(buffer.toString());
    }

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68843.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!