JavaWeb——Servlet执行流程详解

  说起Servlet的执行流程,就不得不先提及一个前提,Servlet技术的核心是Servlet,它是所有Servlet类必须直接或间接实现的一个接口,下面看一下Servlet的具体结构。
  一、首先,先来系统的了解一下Servlet的结构。

Servlet最顶层的接口——Servlet
在这里插入图片描述

   最顶层的Servlet接口中有三个比较重要的方法init(ServletConfig)、service(req,resp)和destroy(),其中

init(ServletConfig):表示当该Servlet第一次被请求时,Servlet容器回调用该方法,后续的请求不会再被调用,可以利用该方法进行相应的初始化功能(ServletConfig可以与配置文件中的init-param相对应)
service:每当请求Servlet时,Servlet容器就会调用这个方法。而该方法在该接口的其他子接口被具体化了。
destory:当要销毁Servlet时,Servlet容器就会调用该方法。通常在卸载程序,或者关闭Servlet容器就回发生调用销毁方法,一般会在这里写一下清除工作的代码。

实现Servlet接口的抽象类——GenericServlet
在这里插入图片描述

   由于要想编写Servlet就必须通过实现Servlet接口,如果是直接实现Servlet接口就必须实现Servlet接口中所定义的所有方法,即使一些方法并没有什么作用也必须得实现,因此产生了GenericServlet抽象类
   GenericServlet抽象类虽然成为抽象类,但其实现了Servlet接口中的所有方法的默认实现,因此编写Servlet不需要再将所有的方法都实现,而是根据需求实现与业务相关的方法即可。并且,GenericServlet将ServletConfig对象由系统来维护,不需要程序员手动的维护了。

实现Servlet最重要的主角——HttpServlet
在这里插入图片描述

   HttpServlet抽象类(实际上是没有抽象方法的,因此自定义类继承HttpServlet时可以一个方法也不覆写)是GenericServlet抽象类的子),其最大的作用,就是将service()方法进行了全面的升级。可以看一下HttpServlet中service()的源代码
	 @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);
	    }
   可以看到,HttpServlet通过继承GenericServlet的service()方法将ServletRequest、ServletResponse对象转换为HttpServletRequest、HttpServletResponse对象,并最终都引用了HttpServlet类自身的 service(HttpServletRequest request, HttpServletResponse response);因此,此时的核心就是这个HttpServlet类中自己编写的service()方法
	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) {
	                // servlet doesn't support if-modified-since, no reason
	                // to go through further expensive logic
	                doGet(req, resp);
	            } else {
	                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
	                if (ifModifiedSince < lastModified) {
	                    // If the servlet mod time is later, call doGet()
	                    // Round down to the nearest second for a proper compare
	                    // A ifModifiedSince of -1 will always be less
	                    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 {
	            //
	            // Note that this means NO servlet supports whatever
	            // method was requested, anywhere on this server.
	            //
	
	            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);
	        }
	    }
   由源码就可以看出,这个service()大体上所做的操作,就是根据http请求的类型,由service()方法进行分配,分配到对应的doXXX()方法中(eg:发送的get请求,由service()分配至doGet()中进行下一步的操作)
   最常用的doXXX()是doGet()doPost()

  二、此时了解了Servlet的结构,接下来看一下Servlet的具体执行流程。
   先上一张流程图

在这里插入图片描述

   流程大体分为两个部分
   1、通过url地址找到自定义类
    ①通过外部的HTTP协议请求(浏览器输入访问域名),访问到Tomcat服务器,携带了请求行、头、体
    ②Tomcat服务器访问web.xml,根据访问路径在<servlet-mapping>中查找与url中所对应的<url-pattern>,并通过<servlet-name>确定与该映射对应的<servlet>
    ③通过<servlet>中的<servlet-class>找到我们自定义的Servlet类(全限定类名)
   2、根据请求方法及参数完成自定义Servlet的流程
    ①服务器找到对应全限定类名后,通过反射创建对象,同时也创建了ServletConfig(存放了一些初始化信息,服务器只会创建一次servlet对象,因此ServletConfig只有一个)
     a)由于自定义的Servlet类继承了HttpServlet,因此若自定义Servlet覆写了init()方法,在该Servlet第一次创建的时候,会首先执行init()方法。
     b)若自定义Servlet没有覆写init()方法,就会在其父类HttpServlet中找,而HttpServlet中也没有init(),最终会在HttpServlet的父类GenericServlet中找到init()方法并执行。(GenericServlet类中的init()是一个空方法,什么也不会执行)
    ②服务器先创建两个对象:ServletRequest请求对象ServletResponse响应对象,用来封装接收浏览器的请求数据向浏览器发送的相应数据
     a)服务器会默认的在自定义Servlet类中寻找service(ServletRequest req,ServletResponse resp)方法,但自定义类中一般都不会实现这个方法,因此会在其父类HttpServlet中寻找。
     b)在父类的HttpServlet中发现有service(req,resp)方法,将创建好的两个对象传入,并在该service中将这两个对象强转为HttpServletRequest和HttpServletResponse,调用HttpServlet内部的另一个service()方法(这个方法不是继承自GenericServlet的,而是属于HttpServlet的)
     c)执行service(HttpServletRequest req, HttpServletResponse resp)方法,该方法内部对请求的方式进行判断,执行doGet()或doPost()方法,而此时doGet()或者doPost()已经被自定义Servlet覆写,因此会继续执行自定义类中对应覆写的方法。(自定义类覆写doXXX()方法时一定不能调用super().doXXX()方法,若调用会导致405错误)
     d)在自定义类中对应覆写的方法执行完后,将数据响应通过resp返回给浏览器。
   总结一下流程为

在这里插入图片描述

   流程总结参考
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐