为了账号安全,请及时绑定邮箱和手机立即绑定

Tomcat 架构分析(四) Servlet和Jsp模块

标签:
Java
Tomcat 架构分析(四) Servlet和Jsp模块图片描述

前言:

Servlet的框架是由两个Java包组成:javax.servlet和javax.servlet.http. 在javax.servlet包中定义了所有的Servlet类都必须实现或扩展的的通用接口和类.在javax.servlet.http包中定义了采用HTTP通信协议的HttpServlet类.

Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这一接口.在Servlet接口中定义了5个方法,其中有3个方法代表了Servlet的声明周期:

 - init方法,负责初始化Servlet对象
 - service方法,负责相应客户的请求
 - destory方法,当Servlet对象退出声明周期时,负责释放占有的资源

当Web容器接收到某个Servlet请求时,Servlet把请求封装成一个HttpServletRequest对象,然后把对象传给Servlet的对应的服务方法.

 HTTP的请求方式包括DELETE,GET,OPTIONS,POST,PUT和TRACE,在HttpServlet类中分别提供了相应的服务方法,它们是,doDelete(),doGet(),doOptions(),doPost(), doPut()和doTrace(). 

HttpServlet的功能

HttpServlet首先必须读取Http请求的内容。Servlet容器负责创建HttpServlet对象,并把Http请求直接封装到HttpServlet对象中,大大简化了HttpServlet解析请求数据的工作量。HttpServlet容器响应Web客户请求流程如下:

1)Web客户向Servlet容器发出Http请求;
2)Servlet容器解析Web客户的Http请求;
3)Servlet容器创建一个HttpRequest对象,在这个对象中封装Http请求信息;
4)Servlet容器创建一个HttpResponse对象;
5)Servlet容器调用HttpServlet的service方法,把HttpRequest和HttpResponse对象作为service方法的参数传给HttpServlet对象;
6)HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息;
7)HttpServlet调用HttpResponse的有关方法,生成响应数据;
8)Servlet容器把HttpServlet的响应结果传给Web客户。

二、创建HttpServlet的步骤——“四部曲”

1)扩展HttpServlet抽象类;
2)覆盖HttpServlet的部分方法,如覆盖doGet()或doPost()方法;
3)获取HTTP请求信息。通过HttpServletRequest对象来检索HTML表单所提交的数据或URL上的查询字符串;
4)生成HTTP响应结果。通过HttpServletResponse对象生成响应结果,它有一个getWriter()方法,该方法返回一个PrintWriter对象。

下面是HttpServlet的继承关系树,关于其他类源码可以到API文档查看,地址:http://tomcat.apache.org/tomcat-8.5-doc/servletapi/

图片描述

下面是对HttpServlet源码的分析

/**
 *
 * Copyright © 2017 http://blog.csdn.net/noseparte © Like the wind, like rain
 * @author Noseparte
 * @Comile 2017年12月8日--下午9:31:27
 * @Version 1.0
 * @Description      HttpServlet源码分析
 */
@SuppressWarnings("unused")
public abstract class HttpServlet extends GenericServlet {

    private static final long serialVersionUID = 1L;
    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";
    private static final String HEADER_IFMODSINCE = "If-Modified-Since";
    private static final String HEADER_LASTMOD = "Last-Modified";
    private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
    private static final ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
    /**
     * 由服务器调用(通过服务方法)来允许servlet处理GET请求。
     *
     * 覆盖此方法以支持GET请求也会自动支持HTTP HEAD请求。HEAD请求是GET请求,在响应中不返回任何主体,只有请求头字段。
     *
     * 在覆盖该方法时,读取请求数据,编写响应头,获取响应的写入器或输出流对象,最后,编写响应数据。最好是包含内容类型和编码。当使用PrintWriter对象返回响应时,在访问PrintWriter对象之前设置内容类型。
     *
     * servlet容器必须在提交响应之前写头文件,因为在HTTP中,必须在响应主体之前发送头部。
     *
     * 在可能的情况下,标题设置content - length(ServletResponse.setContentLength(int)方法),允许servlet容器使用持久连接返回响应给客户端,改善性能。如果整个响应都在响应缓冲区内,则会自动设置内容长度。
     *
     * 当使用HTTP 1.1分块编码(这意味着响应有一个传输编码头)时,不要设置内容长度的标题。
     *
     * GET方法应该是安全的,也就是说,没有任何对用户负责的副作用。例如,大多数表单查询都没有副作用。如果客户机请求用于更改存储的数据,则请求应该使用其他的HTTP方法。
     *
     * GET方法也应该是幂等的,这意味着它可以安全地重复。有时,使方法安全也使其具有强大的功能。例如,重复查询既安全又有效,但在网上购买产品或修改数据既不安全,也不具有幂等功能。
     *
     * 如果请求格式不正确,doGet将返回HTTP "Bad Request" 消息。
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    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);
        }
    }
    /**
     * 返回HttpServletRequest对象最后一次修改的时间,从1970年1月1日午夜开始,时间为毫秒。如果时间未知,该方法返回一个负数(默认值)。
     *
     * 支持HTTP GET请求的servlet可以快速确定它们的最后修改时间,应该覆盖这个方法。这使得浏览器和代理缓存更加有效,减少了服务器和网络资源的负载。
     *
     * @param req
     * @return
     */
    protected long getLastModified(HttpServletRequest req) {
        return - 1L;
    }
    /**
     * 从受保护的服务方法接收HTTP HEAD请求并处理请求。客户端发送一个HEAD请求,当它只想看到响应的头,例如内容类型或内容长度。HTTP HEAD方法计算响应中的输出字节,以便准确地设置内容长度标题。
     *
     * 如果您覆盖了这个方法,您可以避免计算响应主体,并直接设置响应标头以提高性能。请确保您所编写的doHead方法既安全又具有强大的功能(也就是说,保护自己不被多次调用一个HTTP头请求)。
     *
     * 如果HTTP HEAD请求格式不正确,doHead将返回HTTP "Bad Request" 消息。
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
        if (DispatcherType.INCLUDE.equals(req.getDispatcherType())) {
            doGet(req, resp);
        } else {
            NoBodyResponse response = new NoBodyResponse(resp);
            doGet(req, response);
            response.setContentLength();
        }
    }
    /**
     * 由服务器调用(通过服务方法)来允许servlet处理POST请求。HTTP POST方法允许客户端将无限长度的数据发送到Web服务器,并且在发布诸如信用卡号之类的信息时非常有用。
     *
     * 在覆盖该方法时,读取请求数据,编写响应头,获取响应的写入器或输出流对象,最后,编写响应数据。最好是包含内容类型和编码。当使用PrintWriter对象返回响应时,在访问PrintWriter对象之前设置内容类型。
     *
     * servlet容器必须在提交响应之前写头文件,因为在HTTP中,必须在响应主体之前发送头部。
     *
     * 在可能的情况下,标题设置content - length(ServletResponse.setContentLength(int)方法),允许servlet容器使用持久连接返回响应给客户端,改善性能。如果整个响应都在响应缓冲区内,则会自动设置内容长度。
     *
     * 当使用HTTP 1.1分块编码(这意味着响应有一个传输编码头)时,不要设置内容长度的标题。
     *
     * 这种方法不需要是安全的,也不需要强大的。通过POST请求的操作可以有副作用,例如,更新存储的数据或在线购买项目。
     *
     * 如果HTTP POST请求格式不正确,doPost将返回HTTP "Bad Request" 消息。
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_post_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }
    }
    /**
     * 由服务器调用(通过服务方法)来允许servlet处理PUT请求。PUT操作允许客户端将文件放在服务器上,类似于通过FTP发送文件。
     *
     * 在覆盖此方法时,保留与请求发送的任何内容头(包括内容长度、内容类型、内容转换编码、内容编码、内容基础、内容语言、内容位置、内容-md5和内容范围)。
     * 如果您的方法不能处理一个内容头,它必须发出一个错误消息(HTTP 501-没有实现)并丢弃该请求。有关HTTP 1.1的更多信息,请参阅RFC 2616。
     *
     * 这种方法不需要是安全的,也不需要强大的。doPut执行的操作可能会有一些副作用,用户可以对此负责。在使用这种方法时,可以在临时存储中保存受影响的URL的副本。
     *
     * 如果HTTP PUT请求格式不正确,则doPut将返回HTTP "Bad Request" 消息。
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_put_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }
    }
    /**
     * 由服务器调用(通过服务方法)来允许servlet处理一个删除请求。删除操作允许客户端从服务器中删除文档或Web页面。
     *
     * 这种方法不需要是安全的,也不需要强大的。通过删除所请求的操作可以有副作用,用户可以对此负责。在使用这种方法时,可以在临时存储中保存受影响的URL的副本。
     *
     * 如果HTTP DELETE请求格式不正确,doDelete将返回HTTP "Bad Request" 消息。
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_delete_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }
    }
    /**
     * 由服务器调用(通过服务方法)来允许servlet处理一个选项请求。选项请求确定服务器支持哪些HTTP方法,并返回一个适当的标头。
     * 例如,如果一个servlet覆盖doGet,该方法将返回以下标题:允许:GET,HEAD,TRACE,选项
     *
     * 除非servlet实现了新的HTTP方法,否则没有必要覆盖这个方法,除了HTTP 1.1实现的方法之外。
     *
     * @param c
     * @return
     */
    private static Method[] getAllDeclaredMethods(Class < ?>c) {
        if (c.equals(HttpServlet.class)) {
            return null;
        }
        Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
        Method[] thisMethods = c.getDeclaredMethods();
        if ((parentMethods != null) && (parentMethods.length > 0)) {
            Method[] allMethods = new Method[parentMethods.length + thisMethods.length];
            System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length);
            System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length);
            thisMethods = allMethods;
        }
        return thisMethods;
    }
    /**
     * 由服务器调用(通过服务方法)来允许servlet处理一个选项请求。选项请求确定服务器支持哪些HTTP方法,并返回一个适当的标头。
     * 例如,如果一个servlet覆盖doGet,该方法将返回以下标题:允许:GET,HEAD,TRACE,选项
     *
     * 除非servlet实现了新的HTTP方法,否则没有必要覆盖这个方法,除了HTTP 1.1实现的方法之外。
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
        Method[] methods = getAllDeclaredMethods(getClass());
        boolean ALLOW_GET = false;
        boolean ALLOW_HEAD = false;
        boolean ALLOW_POST = false;
        boolean ALLOW_PUT = false;
        boolean ALLOW_DELETE = false;
        boolean ALLOW_TRACE = true;
        boolean ALLOW_OPTIONS = true;
        Class < ?>clazz = null;
        try {
            clazz = Class.forName("org.apache.catalina.connector.RequestFacade");
            Method getAllowTrace = clazz.getMethod("getAllowTrace", (Class[]) null);
            ALLOW_TRACE = ((Boolean) getAllowTrace.invoke(req, (Object[]) null)).booleanValue();
        } catch(ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException localClassNotFoundException) {}
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (m.getName().equals("doGet")) {
                ALLOW_GET = true;
                ALLOW_HEAD = true;
            }
            if (m.getName().equals("doPost")) {
                ALLOW_POST = true;
            }
            if (m.getName().equals("doPut")) {
                ALLOW_PUT = true;
            }
            if (m.getName().equals("doDelete")) {
                ALLOW_DELETE = true;
            }
        }
        String allow = null;
        if (ALLOW_GET) {
            allow = "GET";
        }
        if (ALLOW_HEAD) {
            if (allow == null) {
                allow = "HEAD";
            } else {
                allow = allow + ", HEAD";
            }
        }
        if (ALLOW_POST) {
            if (allow == null) {
                allow = "POST";
            } else {
                allow = allow + ", POST";
            }
        }
        if (ALLOW_PUT) {
            if (allow == null) {
                allow = "PUT";
            } else {
                allow = allow + ", PUT";
            }
        }
        if (ALLOW_DELETE) {
            if (allow == null) {
                allow = "DELETE";
            } else {
                allow = allow + ", DELETE";
            }
        }
        if (ALLOW_TRACE) {
            if (allow == null) {
                allow = "TRACE";
            } else {
                allow = allow + ", TRACE";
            }
        }
        if (ALLOW_OPTIONS) {
            if (allow == null) {
                allow = "OPTIONS";
            } else {
                allow = allow + ", OPTIONS";
            }
        }
        resp.setHeader("Allow", allow);
    }
    /**
     * 由服务器调用(通过服务方法)来允许servlet处理跟踪请求。
     * 跟踪返回将跟踪请求发送到客户机的头,以便在调试中使用它们。没有必要覆盖这个方法。
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
        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 = (String) reqHeaderEnum.nextElement();
            buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName));
        }
        buffer.append(CRLF);
        int responseLength = buffer.length();
        resp.setContentType("message/http");
        resp.setContentLength(responseLength);
        ServletOutputStream out = resp.getOutputStream();
        out.print(buffer.toString());
        out.close();
    }
    /**
     * 从公共服务方法接收标准HTTP请求,并将它们分派到这个类中定义的doMethod方法。
     *
     * 该方法是servlet.service(javax.servlet的一个特定于http的版本。
     *
     * ServletRequest,javax.servlet.ServletResponse)方法。
     *
     * 没有必要覆盖这个方法。
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
        String method = req.getMethod();
        if (method.equals("GET")) {
            long lastModified = getLastModified(req);
            if (lastModified == -1L) {
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch(IllegalArgumentException iae) {
                    ifModifiedSince = -1L;
                }
                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);
        } else if (method.equals("POST")) {
            doPost(req, resp);
        } else if (method.equals("PUT")) {
            doPut(req, resp);
        } else if (method.equals("DELETE")) {
            doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            doOptions(req, resp);
        } else if (method.equals("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(501, errMsg);
        }
    }
    private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
        if (resp.containsHeader("Last-Modified")) {
            return;
        }
        if (lastModified >= 0L) {
            resp.setDateHeader("Last-Modified", lastModified);
        }
    }
    /**
     * 将客户端请求发送到受保护的服务方法。没有必要覆盖这个方法。
     */
    public void service(ServletRequest req, ServletResponse res) throws ServletException,IOException {
     HttpServletResponse response;
     HttpServletRequest request;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch(ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }
}

故而,我们在web容器里注册一个的继承HttpServlet的实现类,构建web.xml入口。

默认的servlet是既服务于静态资源又服务于目录列表(如果允许目录列表的话)的servlet。

它在$CATALINA_HOME/conf/web.xml中被全局声明。默认形式的声明是这样的: $CATALINA_HOME/conf/web.xml

图片描述

JSP与Servlet的关系

图片描述

JSP是Servlet的扩展,在没有JSP之前,就已经出现了Servlet技术。Servlet是利用输出流动态生成HTML页面,包括每一个HTML标签和每个在HTML页面中出现的内容。

JSP通过在标准的HTML页面中插入Java代码,其静态的部分无须Java程序控制,只有那些需要从数据库读取并根据程序动态生成信息时,才使用Java脚本控制。

事实上,JSP是Servlet的一种特殊形式,每个JSP页面就是一个Servlet实例——JSP页面由系统编译成Servlet,Servlet再负责响应用户请求。JSP其实也是Servlet的一种简化,使用JSP时,其实还是使用Servlet,因为Web应用中的每个JSP页面都会由Servlet容器生成对应的Servlet。

JSP的九大对象及其作用域:

   对象名称         对象释义                实现类          作用域
   request         请求对象    javax.servlet.ServletRequest   Request
   response        响应对象    javax.servlet.SrvletResponse   Page
  pageContext    页面上下文对象 javax.servlet.jsp.PageContext  Page
  session         会话对象       javax.servlet.http.HttpSession  session
  application     应用程序对象   javax.servlet.ServletContext  Application
     out          输出对象     javax.servlet.jsp.JspWriter  Page
   config         配置对象     javax.servlet.ServletConfig   Page
    page          页面对象     javax.lang.Object             Page
  exception       异常对象    javax.lang.Throwable         Page

HttpServletRequest常用方法

获得客户机信息
getRequestURL() 方法返回客户端发出请求时的完整URL。
getRequestURI() 方法返回请求行中的资源名部分。
getQueryString() 方法返回请求行中的参数部分。
getRemoteAddr() 方法返回发出请求的客户机的IP地址
getRemoteHost() 方法返回发出请求的客户机的完整主机名
getRemotePort() 方法返回客户机所使用的网络端口号
getLocalAddr() 方法返回WEB服务器的IP地址。
getLocalName() 方法返回WEB服务器的主机名
getMethod() 得到客户机请求方式

获得客户机请求头
getHead(name)方法
getHeaders(String name)方法
getHeaderNames() 方法

获得客户机请求参数(客户端提交的数据)
getParameter(name):获取指定名称的参数值。这是最为常用的方法之一。
getParameterValues(String name):获取指定名称参数的所有值数组。它适用于一个参数名对应多个值的情况。如页面表单中的复选框,多选列表提交的值。
getParameterNames():返回一个包含请求消息中的所有参数名的Enumeration对象。通过遍历这个Enumeration对象,就可以获取请求消息中所有的参数名。
getParameterMap():返回一个保存了请求消息中的所有参数名和值的Map对象。Map对象的key是字符串类型的参数名,value是这个参数所对应的Object类型的值数组 
调用RequestDispatcher.forward() 方法的请求转发过程结束后,浏览器地址栏保持初始的URL地址不变。
HttpServletResponse.sendRedirect() 方法对浏览器的请求直接作出响应,响应的结果就是告诉浏览器去重新发出对另外一个URL的访问请求;
RequestDispatcher.forward() 方法在服务器端内部将请求转发给另外一个资源,浏览器只知道发出了请求并得到了响应结果,并不知道在服务器程序内部发生了转发行为。
RequestDispatcher.forward() 方法的调用者与被调用者之间共享相同的request对象和response对象,它们属于同一个访问请求和响应过程;
而HttpServletResponse.sendRedirect() 方法调用者与被调用者使用各自的request对象和response对象,它们属于两个独立的访问请求和响应过程。

HttpSession获取Session,常用方法:

    * setAttribute(String name,Object value) 将value对象以name名称绑定到会话
    * getAttribute(String name) 取得name的属性值,如果属性不存在则返回null
    * removeAttribute(String name) 从会话中删除name属性,如果不存在不会执行,也不会抛处错误.
    * getAttributeNames() 返回和会话有关的枚举值
    * invalidate() 使会话失效,同时删除属性对象
    * isNew() 用于检测当前客户是否为新的会话
    * getCreationTime() 返回会话创建时间
    * getLastAccessedTime() 返回在会话时间内web容器接收到客户最后发出的请求的时间
    * getMaxInactiveInterval() 返回在会话期间内客户请求的最长时间.秒
    * setMasInactiveInterval(int seconds) 允许客户客户请求的最长时间
    * ServletContext getServletContext() 返回当前会话的上下文环境,ServletContext对象可以使Servlet与web容器进行通信
    * getId() 返回会话期间的识别号
点击查看更多内容
3人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
7112
获赞与收藏
545

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消