天天看點

深入了解tomcat中servlet的建立方式深入了解tomcat中servlet的建立方式

深入了解tomcat中servlet的建立方式

一、 什麼是servlet

1.1、用官方的話解釋:

Servlet是oracle公司提供的一門用于開發動态web資源的技術,屬于javaEE體系中的一種核心規範。

通俗解釋一下:就是我們開發人員所編寫的一個類,必須直接或者間接實作這個javaEE的核心規範,也就是實作Servlet接口,因為這種類産生的對象可以被浏覽器通路到,是以稱之為Servlet,并且javaEE中規定了隻有Servlet的實作類産生的對象才可以被浏覽器通路,就是Servlet.(也就是說這個類要直接或者間接實作了Servlet接口)

二、開始進入servlet的建立

2.1、通過前面介紹,我們知道了一個什麼樣的類建立的對象可以被浏覽器通路,首先我們直接上代碼:

package com.briup.web;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class FirstWay implements Servlet {
	public FirstWay() {
		System.out.println("對象建立了");
	}
	@Override
	public void init(ServletConfig config) throws ServletException {
		// TODO Auto-generated method stub
		System.out.println("我是init:我被調用了");
	}
	@Override
	public ServletConfig getServletConfig() {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		// TODO Auto-generated method stub
		System.out.println("我是service,我被調用了");	
	}
	@Override
	public String getServletInfo() {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		System.out.println("我是destory:我被調用了");
	}

}

           

那麼,一個滿足servlet的類已經建立好了,接下來抛出疑問

  • servet對象由誰建立?
  • 裡面實作的接口方法,哪些會調用,什麼時候調用,調用幾次?

    第一個疑問: 既然是servlet類,由我們開發人員自己手動建立對象,顯然是不合理,是以這個對象的建立,是交給tomcat建立的,我們開發人員隻需要告訴 tomcat,讓他建立,讓他什麼時候建立就行了;

    如何告訴?

1、方法一:通過配置webxml的方式。(極其不推薦使用)

對于整個動态web項目而言,web.xml是最先加載的配置檔案,是以在web.xml的方式配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
  <display-name>firstWay</display-name>
  <servlet>
  		<servlet-name>FirstWay</servlet-name>
  		<servlet-class>com.briup.web.FirstWay</servlet-class>
  		<!-- <load-on-startup>1</load-on-startup> -->
  </servlet>
  <servlet-mapping>
  	<servlet-name>FirstWay</servlet-name>
  	<url-pattern>/FirstWay</url-pattern>
  </servlet-mapping>
</web-app>
           

解釋:

1、servlet-name:見名知意:servlet的名字,注意要與下面你設定映射的名字對應

2、serlvet-class:serlvet的全限定名

3、load-on-startup:是否在tomcat啟動的時候就建立servlet對象,傳入一個大于0的整數‘’(預設是浏覽器第一次請求的時候建立servlet對象)

4、servlet-mapping:見名知意,設定浏覽器的通路映射

5、servlet-name:于上面的對應

6、url-pattern:浏覽器的通路映射(假設預設是本機的話,且tomcat的端口号為8080,那麼浏覽器通路這個servlet的路徑為:localhost:8080/項目名/FirstWay)

有了這些基礎,讓我們通路看看;

  • 第一步:啟動tomcat
    深入了解tomcat中servlet的建立方式深入了解tomcat中servlet的建立方式
    tomcat正常啟動
  • 第二步:通過浏覽器通路(我們這裡手動通路3次)
    深入了解tomcat中servlet的建立方式深入了解tomcat中servlet的建立方式
    浏覽器通路正常
  • 第三步:觀察控制台
    深入了解tomcat中servlet的建立方式深入了解tomcat中servlet的建立方式

通過運作結果分析:

  • 第一次啟動伺服器,對象并沒有被建立
  • 浏覽器請求三遍,但是對象隻建立一次,init()方法也隻調用一次
  • 每通路一次,對象便會調用一次service()方法
  • 其他方法沒被調用

    解釋為嘛沒被調用:getServletConfig():得到ServletConfig對象

    : getServletInfo():得到Servlet的信心,比如作者

    :destroy():servlet銷毀的時候才會調用這個方法,(比如:tomcati正常關閉 這裡我就不去測試,想測試的小夥伴,可以右鍵service,點選stop)然後再觀察控制台便可知了。

2、方法二:注解的方式告訴tomcat(與前者相比,推薦使用)

@WebServlet(value ="映射路徑")
public Fristservlet implement Servelt {
}
           

通過這個注解也可以設定,是否在啟動伺服器的時候就建立對象,這裡就不示範了,

注意:(一旦使用了注解的方式告訴tomcat如果建立某個對象,就不能在web.xml裡面再對這個servlet進行通路設定了)

三、回歸主題,servlet的第二種建立方式

有了前面的解釋,直接上代碼然後再分析

package com.briup.web;

import java.io.IOException;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
@WebServlet(value = "/secondWay")
public class SecondWayCreate extends GenericServlet {

	@Override
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		// TODO Auto-generated method stub
		System.out.println("調用了service方法");
	}

}

           
  • 1、比第一種方法簡潔,實作的是GenericServlet這個類
  • 2、我們看一下GenericServlet源碼,然後進行分析;

可知,這是個抽線類,是servlet接口的實作類,那麼GenericServlet間接 實作了servlet接口,

與第一種方式相比:開發者不是必須将一些接口中不必要的方法實作,可以具有選擇性,減少了代碼量。然而并沒有上面ruan用,就是裝b而已

三、重點第三種方式(與前兩者相比,我更推薦第三種方式)

直接上代碼

package com.briup.web;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(value = "/ThreeWayCreate")
public class ThreeWayCreate extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO Auto-generated method stub
		super.doGet(req, resp);
	}
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO Auto-generated method stub
		super.doPost(req, resp);
	}
}

           

通過以上代碼,可能就有小夥伴要問了

不是說servlet要直接或者間接實作servlet接口嗎,不是說浏覽器每請求一次就要調用一次service方法嗎?方法在哪呢?這不是與前面理論沖突了嗎?

我們繼續看源碼,源碼才是道理

我在下面值列舉源碼裡面比較核心的部分,需要了解更加深入了解的小夥伴,直接去看源碼,tomcat是開源的

public abstract class HttpServlet extends GenericServlet {
	 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(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }
     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(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }

}

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;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // 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);
        }
    }


    /*
     * Sets the Last-Modified entity header field, if it has not
     * already been set and if the value is meaningful.  Called before
     * doGet, to ensure that headers are set before response data is
     * written.  A subclass might have set this header already, so we
     * check.
     */
      public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }
}
}
           

分析:

  • 第一步分析
    深入了解tomcat中servlet的建立方式深入了解tomcat中servlet的建立方式
    可知這個抽象類繼承了GennericeServlet這個抽象類 也就是逐層往下推,實作了Servle接口,那麼這個抽線類必然也繼承了serice方法。
  • 第二步分析
    深入了解tomcat中servlet的建立方式深入了解tomcat中servlet的建立方式
    這個是繼承servlet接口的service方法,當浏覽器每請求一次時,都會調用這個方法,由圖可知,這個方法已經被HttpServlet實作了,由實作類可以得出,請求對象req,和響應對象res,被強轉成了HttpServletRequest,和HttpServletResponse(向下轉型),然後将強轉的對象,傳入HttpServlet重載的Service方法中,調用,第三步,分析重載後的Service(HttpRequest req,HttpRespone res);
  • 第三步分析
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;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // 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);
        }
    }

           

通過傳過來的HttpRequest對象,判斷請求方式,通過請求方式,決定調用哪個方法(如果請求方式是post方式,那麼就會調用doPost(HttpRequest req,HttpRestpone Res)方法)

  • 第四步分析

    綜上分析,總結:tomcat建立對象,當浏覽器請求的時候,調用Servlet的Service(ServeltRequest req,ServletRespone res )方法,然後這個方法再調用,HttpServlet裡面重載的Servlet(HttpServletReqeust req ,HttpServletRespone res)方法,然後這個方法會通過請求方式是什麼,選擇性的調用doPost(),還是doGet()方法(當然還有很多其他的方式這裡就不列舉了),

是以第三種方式,的本質還是當浏覽器發起一次請求的時候調用了Servlet接口裡面的Service(ServeltRequest req,ServletRespone res )方法,然後通過實作類的裡面的邏輯,間接的調用了doPost()等方法。

優點:

  • 1、通過請求方式可以處理相應的請求,使得邏輯更加清晰
  • 2,減少代碼量,是程式更加簡潔
  • 3,使得請求或者響應的操作性更加豐富
  • 4…

四、 總結:

注意點:浏覽器發起請求調用的一定是servlet種的service方法;

有任何錯誤或者見解希望各位讀者指出互相進步,互相學習。