zoukankan      html  css  js  c++  java
  • 浅谈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

  • 相关阅读:
    前端生成pdf文件之pdfmake.js
    vim 安装
    linux基础学习
    python 编码处理
    Ubuntu 下配置 SSH服务全过程及问题解决
    yum 安装
    Ubuntu安装MySQL
    Linux各发行版本及其软件包管理方法
    轻松学习LINUX系列教程推出
    常用命令
  • 原文地址:https://www.cnblogs.com/baichendongyang/p/13235534.html
Copyright © 2011-2022 走看看