浅谈servlet源码

写在开头:众所周知,对于Java web项目来说,servlet是第一步,无论你使用什么框架,都是基于servlet而进行封装或者衍生的,所以很有必要研究一下servlet是个什么东东。

一.servlet的架构图

       如上图所示,可以看出servlet是一个接口,有一个基础的实现类,基本上所有的servlet都是基于这个接口展开的,接下来就来看看这个接口有什么东东。

二.servlet及其相关接口和相关实现类

1.servlet及其相关接口

如上图所示,servlet可以认为是一个web项目中的入口,首先init()初始化容器,接收一个ServletConfig参数,由容器传入,ServletConfig就是Servlet的配置,在web.xml中定义Servlet时通过init-param标签配置的参数由ServletConfig保存;然后service()处理不同的请求。

接下来看ServletConfig接口

如上图所示,ServletConfig是Servlet级别,而ServletContext是Context(也就是Application)级别,ServletContext通常利用setAttribute方法保存Application的属性。

然后就是ServletContext接口,首先要明确,一个web应用对应一个ServletContext,所以ServletContext的作用范围是整个应用,明确这点很重要,这是基础中的基础。如下是ServletContext接口的源码:

public interface ServletContext {
// 返回web应用的上下文路径,就是平时我们部署的应用的根目录名称,如部署在Tomcat上的webapps目录下的demo应用,返回的就是/demo,如果部署在ROOT目录下的话,返回空字符串“”。
此外,这个路径可以再Tomcat的server.xml里面修改path属性。
    String getContextPath();

// 方法入参是uriPath,是一个资源定位符的路径。返回一个ServletContext实例,其实就是根据资源的路径返回其servlet上下文。
    ServletContext getContext(String var1);

// 返回当前servlet容器支持的servlet规范的最高版本
    int getMajorVersion();

// 返回当前servlet容器支持的servlet规范的最低版本
    int getMinorVersion();

// 返回文件的MIME类型,MIME类型是容器配置的,可以通过web.xml进行配置
    String getMimeType(String var1);

// 根据传入的路径,列出该路径下的所有资源路径,返回的路径是相当于web应用的上下文根或者相对于WEB-INF/lib目录下的各个JAR包里面的/META-INF/resources目录。
    Set getResourcePaths(String var1);
// 将指定路径的资源封装成URL实例并返回
    URL getResource(String var1) throws MalformedURLException;
// 获取指定路径资源的输入流InputStream并返回
    InputStream getResourceAsStream(String var1);
// 将指定的资源包装成RequestDispatcher实例并返回,根据资源路径(该路径相对于当前应用上下文根)
    RequestDispatcher getRequestDispatcher(String var1);
// 将指定的资源包装成RequestDispatcher实例并返回,根据资源的名称(通过服务器控制台或者web.xml里面配置的,比如web.xml里面配置servlet的名称)。
    RequestDispatcher getNamedDispatcher(String var1);

    /** @deprecated */
    Servlet getServlet(String var1) throws ServletException;

    /** @deprecated */
    Enumeration getServlets();

    /** @deprecated */
    Enumeration getServletNames();

    void log(String var1);

    /** @deprecated */
    void log(Exception var1, String var2);
// 记录日志到servlet日志文件,servlet日志文件的路径由具体的 servlet容器自己去决定。如果你是在MyEclipse、STS这类的IDE中跑应用的话,那么日志信息将在控制台(Console)输出。如果是 发布到Tomcat下的话,日志文件是Tomcat目录下的/logs/localhost.yyyy-MM-dd.log。
    void log(String var1, Throwable var2);
// 根据资源虚拟路径,返回实际路径;比如getRealPath("index.jsp"),返回index.jsp在系统中的绝对路径
    String getRealPath(String var1);

    String getServerInfo();
// 用来获取应用的初始化参数相关数据的,根据参数名获取参数值,参数的作用域是整个应用。这个参数是在web.xml中配置的,如<context-param>标签的内容等等。
    String getInitParameter(String var1);
// 用来获取应用的初始化参数相关数据的,获取参数名集合。
    Enumeration getInitParameterNames();

    Object getAttribute(String var1);

    Enumeration getAttributeNames();

    void setAttribute(String var1, Object var2);

    void removeAttribute(String var1);

    String getServletContextName();
}

既然上面说到了RequestDispatcher这个接口,接下来也看看这个接口

public interface RequestDispatcher {
    void forward(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

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

这个接口只有两个方法,相信大家都比较熟悉这两个方法了。一个是转发,一个是包含,有点类似于jsp里面的转发和包含。当我们要进行转发的时候,只有使用这个方法即可,它会通过servlet容器去调用相关的接口实现转发,找到对应的资源文件,比如在上面介绍的一些相关的方法。

此外,还有ServletRequest & ServletResponse这两个接口。对于每一个HTTP请求,servlet容器会创建一个封装了HTTP请求的ServletRequest实例传递给servlet的service方法。ServletResponse则表示一个Servlet响应,其影藏了将响应发给浏览器的复杂性。通过ServletRequest的方法你可以获取一些请求相关的参数,而ServletResponse则可以将设置一些返回参数信息,并且设置返回内容。返回内容之前一般会调用setContentType方法设置响应的内容类型,如果没有设置,大多数浏览器会默认以html的形式响应,不过为了避免出问题,我们一般都设置该项。

值得注意的是ServletResponse中定义的getWriter方法,它返回可以将文本传给客户端的java.io.PrintWriter。在默认的情况下,PrintWriter对象使用ISO-8859-1编码,这有可能引起乱码。

2.servlet相关实现

说了几个比较常见的接口,接下来就来看看一些相关的实现类吧。第一个当然是GenericServlet,这是第一个实现了Servlet接口的类。GenericServlet是Servlet的默认实现,是与具体协议无关的。

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
    private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getInitParameter(name);
        }
    }

    public Enumeration getInitParameterNames() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getInitParameterNames();
        }
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.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) {
        this.getServletContext().log(this.getServletName() + ": " + msg);
    }

    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() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getServletName();
        }
    }
}

实现实现了ServletConfig,可以调用这里面的一些方法,比如上面说的,可以获取servlet应用名字,获取web.xml的一些参数值。然后还提供了有参的的init()方法,将init方法中的ServletConfig赋给一个类级变量,使得可以通过getServletConfig来获取。同时为避免覆盖init方法后在子类中必须调用super.init(servletConfig),GenericServlet还提供了一个不带参数的init方法,当ServletConfig赋值完成就会被带参数的init方法调用。这样就可以通过覆盖不带参数的init方法编写初始化代码,而ServletConfig实例依然得以保存。

此外,还有两个log方法:一个记录日志,一个记录异常 。

然后就是HttpServlet,这是一个基于http协议实现的Servlet基类,也在我们平时开发过程中比较常见的一个类。

首先是定义了http协议相关的几个方法名字变量,然后又根据这些http方法,定义了相应的处理方法,比较常见的就是doGet,doPost方法了,这些方法都基本都跟下面的代码差不多,只是改了一下方法名而已。

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(405, msg);
        } else {
            resp.sendError(400, msg);
        }

    }

此外,HttpServlet还重写service方法

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 对request和response进行类型强转
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException("non-HTTP request or response");
        }
 //调用Http的请求方法处理
        this.service(request, response);
    }
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求类型
        String method = req.getMethod();
        long lastModified;
//判断请求类型进行不同的http方法处理,即路由
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

相关连接:

http://www.cnblogs.com/nantang/p/5919323.html

https://www.cnblogs.com/fxust/p/7944242.html

https://www.jianshu.com/p/e9f31c783ff1

原文地址:https://www.cnblogs.com/baichendongyang/p/13235534.html