• Servlet生命周期


    引言

    编写一个HTTP服务器,要先编写基于TCP协议的服务,然后一个TCP连接中读取HTTP请求发送HTTP响应即可。要编写一个完善的HTTP服务器,以HTTP/1.1为例,需要考虑的包括:

    • 识别正确和错误的HTTP请求;
    • 识别正确和错误的HTTP头;
    • 复用TCP连接;
    • 复用线程;
    • IO异常处理;

    这些基础工作需要耗费大量的时间,并且经过长期测试才能稳定运行。如果我们只需要输出一个简单的HTML页面,就不得不编写上千行底层代码,那就根本无法做到高效而可靠地开发。

    Servlet

    JavaEE平台上,处理TCP连接,解析HTTP协议这些底层工作统统扔给现成的Web服务器去做,我们只需要把自己的应用程序跑在Web服务器上。为了实现这一目的,JavaEE提供了Servlet API,我们使用Servlet API编写自己的Servlet来处理HTTP请求,Web服务器实现Servlet API接口,实现底层功能:

    1. ┌───────────┐
    2. │My Servlet │
    3. ├───────────┤
    4. │Servlet API│
    5. ┌───────┐ HTTP ├───────────┤
    6. │Browser│<──────>│Web Server │
    7. └───────┘ └───────────┘

    简单来讲,一个Web App就是由一个或多个Servlet组成的,每个Servlet通过注解说明自己能处理的路径。一个Webapp完全可以有多个Servlet,分别映射不同的路径。例如:HelloServlet能处理/hello.do这个路径的请求。 

    一个简单的Servlet: 

    1. // WebServlet注解表示这是一个Servlet,并映射到地址 hello.do
    2. @WebServlet(urlPatterns = "/hello.do")
    3. public class HelloServlet extends HttpServlet {
    4. protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    5. throws ServletException, IOException {
    6. // 设置响应类型:
    7. resp.setContentType("text/html");
    8. // 获取输出流:
    9. PrintWriter pw = resp.getWriter();
    10. // 写入响应:
    11. pw.write("

      Hello, world!

      "
      );
    12. // 最后使用flush强制输出:
    13. pw.flush();
    14. }
    15. }

    在浏览器输入http://localhost:8080/项目名称/hello.do,即可看到HelloServlet的输出:

     

    一个Servlet总是继承自HttpServlet,然后覆写doGet()doPost()方法。注意到doGet()方法传入了HttpServletRequestHttpServletResponse两个对象,分别代表HTTP请求和响应。我们使用Servlet API时,并不直接与底层TCP交互,也不需要解析HTTP协议,因为HttpServletRequestHttpServletResponse就已经封装好了请求和响应。以发送响应为例,我们只需要设置正确的响应类型,然后获取PrintWriter,写入响应即可。

    要想运行Servlet,就要使用支持ServletAPI的Web容器(Web服务器)。常用的服务器有:

    • Tomcat:由Apache开发的开源免费服务器;
    • Jetty:由Eclipse开发的开源免费服务器;
    • GlassFish:一个开源的全功能JavaEE服务器。

    还有一些收费的商用服务器,如Oracle的WebLogic,IBM的WebSphere

    Servlet容器中运行的Servlet具有如下特点

    • 无法在代码中直接通过new创建Servlet实例,必须由Servlet容器自动创建Servlet实例;
    • Servlet容器只会给每个Servlet类创建唯一实例;
    • Servlet容器会使用多线程执行doGet()doPost()方法。 

    Servlet生命周期

    在通过一个URL路径发起对一个Servlet请求的过程中,其本质是在调用执行Servlet实例的doXXX()方法。该Servlet实例创建和使用的过程,被称为Servlet的生命周期。整个生命周期包括:实例化、初始化、服务、销毁。

    实例化

    根据Servlet请求的路径(例如:home.do),查找该Servlet实例。如果实例不存在,则通过调用构造方法,完成Servlet实例的创建。

    1. //WebServlet注解表示这是一个Servlet,并映射到地址 hello.do
    2. @WebServlet(urlPatterns = "/hello.do")
    3. public class HelloServlet extends HttpServlet {
    4. //记录客户端(服务器)发起请求的次数
    5. int count ;
    6. //定义其无参构造方法
    7. public HelloServlet(){
    8. System.out.println("Servlect实例化开始,对象被创建!");
    9. }
    10. protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    11. throws ServletException, IOException {
    12. System.out.println("收到客户端的请求:"+(++count)+"次");
    13. PrintWriter pw = resp.getWriter();
    14. // 写入响应:
    15. pw.write("

      Hello, world!

      "
      );
    16. pw.flush();
    17. }
    18. }

     客户端(服务器)第一次发起请求时Tomcat没有找到Servlet实例,所以就调用构造方法,完成对Servlect的实例化,当再次客户端再次发起请求时,由于Servlet对象已经存在,故不再调用构造方法!

    初始化

    通过该Servlet实例,调用init()方法,执行初始化的逻辑。

    还是上面HelloServlet类中的代码,在此类中定义无参的构造方法并且重写init()方法,当有客户端发起请求,运行结果如下:

    服务

    通过该Servlet实例,调用service()方法,如果子类没有重写该方法,则调用HttpServlet父类的service()方法,在父类的该方法中进行请求方式的判断,如果是GET请求,则调用doGet()方法;如果是POST请求,则调用doPost()方法;如果子类重写doXXX()方法,则调用子类重写后的doXXX()方法;如果子类没有重写doXXX()方法,则调用父类的doXXX()方法,在父类的方法实现中,返回一个405状态码的错误页面。

    源代码:

    1. protected void service(HttpServletRequest req, HttpServletResponse resp)
    2. throws ServletException, IOException {
    3. String method = req.getMethod();
    4. if (method.equals(METHOD_GET)) {
    5. long lastModified = getLastModified(req);
    6. if (lastModified == -1) {
    7. // servlet doesn't support if-modified-since, no reason
    8. // to go through further expensive logic
    9. doGet(req, resp);
    10. } else {
    11. long ifModifiedSince;
    12. try {
    13. ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
    14. } catch (IllegalArgumentException iae) {
    15. // Invalid date header - proceed as if none was set
    16. ifModifiedSince = -1;
    17. }
    18. if (ifModifiedSince < (lastModified / 1000 * 1000)) {
    19. // If the servlet mod time is later, call doGet()
    20. // Round down to the nearest second for a proper compare
    21. // A ifModifiedSince of -1 will always be less
    22. maybeSetLastModified(resp, lastModified);
    23. doGet(req, resp);
    24. } else {
    25. resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
    26. }
    27. }
    28. } else if (method.equals(METHOD_HEAD)) {
    29. long lastModified = getLastModified(req);
    30. maybeSetLastModified(resp, lastModified);
    31. doHead(req, resp);
    32. } else if (method.equals(METHOD_POST)) {
    33. doPost(req, resp);
    34. } else if (method.equals(METHOD_PUT)) {
    35. doPut(req, resp);
    36. } else if (method.equals(METHOD_DELETE)) {
    37. doDelete(req, resp);
    38. } else if (method.equals(METHOD_OPTIONS)) {
    39. doOptions(req,resp);
    40. } else if (method.equals(METHOD_TRACE)) {
    41. doTrace(req,resp);
    42. } else {
    43. //
    44. // Note that this means NO servlet supports whatever
    45. // method was requested, anywhere on this server.
    46. //
    47. String errMsg = lStrings.getString("http.method_not_implemented");
    48. Object[] errArgs = new Object[1];
    49. errArgs[0] = method;
    50. errMsg = MessageFormat.format(errMsg, errArgs);
    51. resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    52. }
    53. }

     如果我们在HelloServlet类中简单重写service()方法,那么当有客户端发起请求,服务器执行service()方法,就不会做出响应。

     运行结果:

    销毁

    服务器关闭或重启时,会调用Servlet实例的destroy()方法,会销毁所有的Servlet实例。

    从一个客户端(浏览器)发起请求,到服务器做出响应的过程如图:

     

  • 相关阅读:
    3.ICMP
    kubectl插件管理工具krew
    Python语言核心编程
    DLG4NLP
    拒绝水文!八大排序(三)【适合初学者】快速排序
    52单片机独立键盘控制数码管计数
    Spring Cloud Alibaba —— 服务注册与配置中心
    Elasticsearch 集群状态详解
    JS7day(事件对象,事件流,事件捕获和冒泡,阻止事件流动,事件委托)
    神经网络研究主要内容,神经网络的起源和发展
  • 原文地址:https://blog.csdn.net/m0_59340907/article/details/126442765