JavaWeb之 Servlet(2萬6千字詳解)

目錄

  • 前言
  • 1. Servlet 簡介
  • 2. Servlet 前世今生
  • 3. Servlet 執行流程
  • 4. Servlet 快速入門
  • 5. 兩種配置 Servlet程序 URL的方式
    • 5.1 使用 注解來配置 Servlet程序 的 URL
      • 5.1.1 urlPattern 的配置規則
        • 精確匹配
        • 目錄匹配:使用 * 符號代表任意路徑
        • 擴展名匹配
        • 任意匹配
      • 5.1.2 小結
    • 5.2 使用 XML文件來配置 Servlet程序 的 URL
  • 6. Servlet 接口詳解
    • 6.1 ServletConfig 類
    • 6.2 ServletRequest 和 ServletResponse 類
    • 6.3 Servlet 接口中的方法
  • 7. Servlet 體系結構
    • 7.1 GenericServlet 抽象類
    • 7.2 HttpServlet 抽象類
  • 8. Servlet 生命周期
    • 8.1 生命周期與三個重要方法的對應
    • 8.2 生命周期的演示
    • 8.3 Servlet 的初始化時機
  • 9. Servlet 線程不安全問題
  • 10. 小結一下 Tomcat 和 Servlet的執行流程

前言

博主將用 CSDN 記錄 Java 后端開發學習之路上的經驗,并將自己整理的編程經驗和知識分享出來,希望能幫助到有需要的小伙伴。
博主也希望和一直在堅持努力學習的小伙伴們共勉!唯有努力鉆研,多思考勤動手,方能在編程道路上行至所向。
由于博主技術知識有限,博文中難免會有出錯的地方,還望各位大佬包涵并批評指正,博主會及時改正;如果本文對小伙伴你有幫助的話,求求給博主一個贊支持一下,可以一起交流,一起加油!!

本文是博主在學習B站尚硅谷的JavaWeb網課時整理的學習筆記,在這里感謝其優質網課,如果有興趣的小伙伴也可以去看看。
本文編寫過程中,參考了以下幾位 csdn博主的博客,寫的非常好,有興趣的小伙伴也可以去看看。
Servlet是什么
JavaWeb(專欄)
(尚硅谷)JavaWeb新版教程03-Tomcat-Servlet

1. Servlet 簡介

1627234763207
  • Servlet是 JavaWeb最為核心的內容,它是 Java提供的一門動態Web資源開發技術

  • 使用Servlet就可以實現,根據不同的用戶在頁面上動態顯示不同內容。

  • Servlet是JavaEE規范之一,其實本質上就是一個接口,將來我們需要自己定義類來實現Servlet 接口,并由Web服務器運行實現了Servlet接口的類。

1627234972853

那 Servlet到底是干什么的呢?答案很簡單,接口的作用就是用于規范代碼

在這里插入圖片描述

Servlet 接口定義的是一套處理網絡請求的規范,所有實現了 Servlet接口的類,都需要實現它的五個接口方法。

其中最主要的是兩個聲明周期的方法**init()destory(),還有一個處理請求的方法service()。也就是說,所有實現 Servlet接口的類,或者說,所有想要處理網絡請求**的類,都需要回答以下三個問題:

  • 你初始化時要做什么?

  • 你接收到請求時要做什么?

  • 你銷毀時要做什么?

剛剛說 Servlet是一個規范,那實現了 Servlet接口的類,就能處理瀏覽器發送的請求了嗎?

  • 答案是不能。我們不會在 Servlet實現類中寫監聽 8080端口的代碼,Servlet 實現類也不會直接和瀏覽器打交道。

那瀏覽器發送的請求是怎么來到 Servlet實現類的呢?

  • 答案是使用 Servlet 容器,比如最常用的**Tomcat**。Servlet 實現類都是要部署在一個容器中的,不然 Servlet實現類根本不能起作用。

所以換而言之,Tomcat 才是直接與瀏覽器打交道的家伙,它負責監聽端口,當瀏覽器發送請求過來后,Tomcat根據瀏覽器所訪問的URL等信息,確定要將請求交給哪個 Servlet實現類去處理,然后調用那個 Servlet實現類的service()方法,service()方法便會返回一個響應對象,最后再由Tomcat 把這個響應對象返回給瀏覽器。

Servlet 本身在 Tomcat中是“非常被動”的一個角色,處理網絡請求與響應不是它的主要職責,它其實更偏向于業務代碼。所謂的請求和響應對象都是由 Tomcat 傳給 Servlet用來處理請求和響應的工具,但 Servlet本身不處理這些,而是另外調用其他類的方法去處理。

2. Servlet 前世今生

Tomcat其實是 Web服務器Servlet容器的結合體。

  1. Web服務器的所做的工作本質上是:

    • 將某個主機上的資源映射為一個URL供外界(如:瀏覽器)訪問。
  2. 那什么是 Servlet容器呢?

    • Servlet容器,顧名思義里面存放的是 Servlet 實現類對象

我們怎樣才能通過 Web服務器映射的URL訪問到資源呢?肯定是需要創建一個Web項目來處理瀏覽器所發送的請求的,主要有三個過程:

  1. 接收請求(收到請求)

  2. 處理請求(處理請求)

  3. 響應請求(返回處理結果)

任何一個Web項目,必然包括這三個步驟。其中接收請求和響應請求是共性功能,而且沒有差異性。于是,我們就可以把接收請求和響應請求這兩個步驟抽取出來并封裝進Web服務器中。

在這里插入圖片描述

但對于不同請求的處理功能邏輯是不同的。沒關系,我們將處理請求的過程抽取出來封裝成一個類,該類必須要實現 Servlet接口,也就是 Servlet實現類,而這個類就交給程序員自己編寫。我們把 Servlet實現類存放在 Web項目中,可以把它看作是一個資源。

當然,隨著互聯網的發展,出現了三層架構,所以一些處理邏輯又從 Servlet實現類中抽取出來,封裝到 Service層(業務層)和Dao層(持久層)中。

在這里插入圖片描述

但是Servlet 并不擅長往瀏覽器輸出 HTML頁面,所以后來就出現了 Thymeleaf技術(后面會介紹)。

慢慢的,等Spring 家族出現后,Servlet 開始退居幕后,取而代之的是 SpringMVCSpringMVC的核心組件DispacterServlet 其實本質上就是一個 Servlet接口的實現子類,但是它已經自立門戶,在原來 HttpServlet(Servlet子類)的基礎上,又封裝了一層邏輯。本處我們不再具體展開,而是專注于講解 Servlet。

3. Servlet 執行流程

Servlet(Server Applet,服務器小程序,是服務器的一小部分),全稱 Java Servlet,是用 Java編寫的服務器端程序。其主要功能在于能夠交互式地瀏覽和修改數據,生成動態 Web內容。

狹義的 Servlet是指 Java語言實現的一個接口,而廣義的 Servlet是指任何實現了 Servlet接口的類。一般情況下,我們將 Servlet 理解為后者。在后面的描述中,Servlet 就代指任何實現了 Servlet接口的類。

Servlet 可以運行于所有支持 Java 的應用服務器中。從原理上講,Servlet 可以響應任何類型的請求,但絕大多數情況下Servlet 只用于基于 HTTP協議的Web服務器

Servlet的工作流程:Servlet 由 Web服務器調用,Web服務器在收到瀏覽器對某個 Servlet的訪問請求后:

  1. Web服務器首先檢查是否已經裝載并創建了該Servlet的實例對象。如果是,則直接執行第④步,否則,執行第②步。
  2. 裝載并創建該 Servlet的一個實例對象。
  3. 調用 Servlet實例對象的init()方法。
  4. 創建一個用于封裝 HTTP請求消息的**HttpServletRequest對象和一個代表HTTP響應消息的HttpServletResponse**對象,然后調用 Servlet中的service()方法,并將請求和響應對象作為參數傳遞進去。
  5. Web服務器被停止或重新啟動之前,Servlet引擎將卸載所有 Servlet,并在卸載之前調用Servlet的destroy()方法。

最早支持 Servlet標準的是 JavaSoftJava Web Server,此后,一些其它的基于 Java的Web服務器開始支持標準的 Servlet。執行流程如下圖:

img

以 Tomcat服務器為例,Servlet 簡單的執行流程如下:

  1. 瀏覽器向Tomcat服務器請求某個 Servlet 的實例對象;
  2. Tomcat 加載該 Servlet 類到內存中;
  3. Tomcat 調用 init()方法初始化該 Servlet并實例化該Servlet對象;
  4. Tomcat 調用該Servlet 類中的 service() 方法, service() 方法根據不同請求的方式再去調用doGet()方法或者 doPost()方法,此外還有doHead()doPut()doTrace()doDelete()doOptions()等方法;
  5. 最后Tomcat 調用 Servlet中的destroy()方法銷毀該Servlet對象。

詳細的執行流程如下:

  1. 加載和實例化 Servlet。這項操作一般是動態執行的。然而,Tomcat 通常會提供一個管理的選項,用于在 Tomcat 啟動時強制裝載和初始化特定的 Servlet;
  2. Tomcat 創建一個 Servlet的實例;
  3. 第一個瀏覽器的請求到達 Tomcat;
  4. Tomcat 調用 Servlet 的 init() 方法(可配置為 Tomcat 創建 Servlet 實例時調用:在 Web.xml文件中 <servlet> 標簽下配置<load-on-startup> 標簽,配置的值為整型,值越小 Servlet 的啟動優先級越高);
  5. 一個瀏覽器的請求到達 Tomcat;
  6. Tomcat 創建一個請求對象,用于處理瀏覽器請求;
  7. Tomcat 創建一個響應對象,用于響應瀏覽器請求;
  8. Tomcat 調用 Servlet 的 service() 方法,并傳遞請求對象和響應對象作為方法的參數;
  9. service() 方法獲得關于請求對象的信息,處理請求,訪問其他資源,獲得需要的信息;
  10. service() 方法使用響應對象的方法,將響應對象傳回 Tomcat,最終Tomcat 將響應傳回瀏覽器。service()方法還可能調用其它方法以處理請求,如doGet()doPost() 或者程序員自己開發的新的方法;
  11. 對于更多的瀏覽器的相同請求,Tomcat 創建新的請求對象和響應對象,仍然激活此 Servlet 的 service()方法,將這兩個對象作為參數傳遞給它,如此重復以上的循環。但無需再調用init()方法。一般 Servlet 只初始化一次(只有一個對象),當 Tomcat 不再需要該 Servlet 時(一般當 Server 關閉時),Tomcat 調用 Servlet 的 destroy()方法將其銷毀。

小結:

  1. Servlet就是一群人來制定 Java應用程序中使用 Web時的各種規范,統一接口,其他內部實現由廠商自己實現,Tomcat、jetty、jboss等等應運而生。

  2. Web服務器習慣處理靜態資源,所以需要一個幫助程序來幫忙處理動態請求(如當前時間)。Web服務器會將動態請求轉發給幫助程序,幫助程序處理后,返回處理后的靜態結果給Web服務器。這樣就避免了Web服務器處理動態資源。所以,Servlet 的本質是一個幫助程序。 如下圖:

img

3.Servlet執行流程分為三個階段:init(初始化),service(運行),destroy(銷毀)

下面我們試試自己創建一個Servlet 程序,并通過瀏覽器訪問該程序。

4. Servlet 快速入門

需求分析: 編寫一個Servlet類,并使用IDEA中 Tomcat插件進行部署,最終通過瀏覽器訪問所編寫的 Servlet程序。 具體的實現步驟為:

第一步:使用 Maven工具創建一個Web項目Web-demo,并在pom.xml文件中導入 ServletAPI 的依賴坐標。

<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><!--此處為什么需要添加<scope>標簽?provided 指的是該依賴只在編譯和測試過程中有效,最后生成的war包時不會加入該依賴因為Tomcat的lib目錄中已經有servlet-api這個jar包,如果在生成war包的時候再加入,就會和Tomcat中的jar包沖突,導致報錯--><scope>provided</scope>
</dependency>

第二步:在 Web項目中的 main/src文件夾下定義一個ServletDemo1類,實現 Servlet接口,并重寫接口中所有方法,在service()方法中輸出一句話。

import javax.servlet.*;
import java.io.IOException;public class ServletDemo1 implements Servlet {public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("servlet hello world~");}public void init(ServletConfig servletConfig) throws ServletException {}public ServletConfig getServletConfig() {return null;}public String getServletInfo() {return null;}public void destroy() {}
}

第三步:在ServletDemo1類上使用@WebServlet注解,該注解的作用是配置該 Servlet程序的訪問路徑(相當于我們給Tomcat中部署的 Web項目設置訪問路徑URL,也就是在Application context屬性中設置的值)。

@WebServlet("/demo1")
public class ServletDemo1 implements Servlet {...
}

第四步:啟動Tomcat,在瀏覽器中輸入URL地址訪問該Servlet程序。

http://localhost:8080/Web-demo/demo1

第五步:瀏覽器訪問后,在IDEA 控制臺會打印出servlet hello world~,說明ServletDemo1程序已經成功運行并成功處理了瀏覽器請求。

我們以快速入門的這個程序為例,簡單看一下 Servlet 程序的執行流程。如下圖:

1627236923139

1. 瀏覽器發出http://localhost:8080/Web-demo/demo1請求,從請求的UEL中可以解析出三部分內容,分別是localhost:8080Web-demodemo1

  • 根據localhost:8080可以找到要訪問的Tomcat Web服務器。
  • 根據Web-demo可以找到部署在 Tomcat服務器上的Web-demo項目。
  • 根據demo1可以找到要訪問的是項目中的哪個 Servlet類,其根據@WebServlet注解中設置的值進行匹配。

2. 找到ServletDemo1這個類后,Tomcat Web服務器就會為ServletDemo1這個類創建一個對象,然后調用對象中的service()方法。

  • ServletDemo1類實現了 Servlet接口,所以類中必然會重寫service()方法供 Tomcat Web服務器進行調用。
  • service()方法中有 ServletRequestServletResponse兩個形參,ServletRequest封裝的是請求數據,ServletResponse封裝的是響應數據,后期我們可以通過這兩個參數實現前后端的數據交互。(后面我們會詳細講解這幾個參數)

至此,Servlet 的入門案例就已經完成,大家可以按照上面的步驟進行練習了。

5. 兩種配置 Servlet程序 URL的方式

5.1 使用 注解來配置 Servlet程序 的 URL

在快速入門案例中,我們知道,一個 Servlet程序在編寫好后,要想被瀏覽器訪問到,就需要配置其訪問路徑:即urlPattern。一個Servlet程序,可以配置多個urlPattern,也就是說,同一個 Servlet 程序可以被映射到多個URL路徑上,多個URL路徑可以訪問同一個 Servlet程序。 比如:

@WebServlet(urlPatterns = {"/demo1", "/demo2"})

代碼舉例:

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;/**
* urlPattern: 一個Servlet可以配置多個訪問路徑
*/
@WebServlet(urlPatterns = {"/demo1", "/demo2"})
public class ServletDemo1 implements Servlet {public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("servletdemo1 & servletdemo2");}public void init(ServletConfig servletConfig) throws ServletException {}public ServletConfig getServletConfig() {return null;}public String getServletInfo() {return null;}public void destroy() {}
}

在瀏覽器上輸入:
http://localhost:8080/Web-demo/demo1
http://localhost:8080/Web-demo/demo2
這兩個地址都能訪問到ServletDemo1程序。

5.1.1 urlPattern 的配置規則

精確匹配

1627273174144

/*** UrlPattern:* * 精確匹配*/
@WebServlet(urlPatterns = "/user/select")
public class ServletDemo3 implements Servlet {public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("精確訪問 Servlet");}...// 省略其他幾個方法
}
  • ServletDemo3程序訪問路徑為:http://localhost:8080/Web-demo/user/select
目錄匹配:使用 * 符號代表任意路徑

1627273184095

/*** UrlPattern:* * 目錄匹配: /user/**/
@WebServlet(urlPatterns = "/user/*")
public class ServletDemo4 implements Servlet {public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("目錄訪問 Servlet");}...// 省略其他幾個方法
}
  • ServletDemo4程序的訪問路徑為:http://localhost:8080/Web-demo/user/任意

思考:

  1. 訪問路徑http://localhost:8080/Web-demo/user是否能訪問到ServletDemo4程序?
  2. 訪問路徑http://localhost:8080/Web-demo/user/a/b是否能訪問到ServletDemo4程序?
  3. 訪問路徑http://localhost:8080/Web-demo/user/select是訪問到ServletDemo3程序還是ServletDemo4程序?

答案是:是;是;訪問ServletDemo3

因此我們可以得到的結論是/user/*中的/*代表的是零或多個層級訪問目錄,同時精確匹配優先級要高于目錄匹配。

擴展名匹配

1627273194118

/*** UrlPattern:* * 擴展名匹配: *.do*/
@WebServlet(urlPatterns = "*.do")
public class ServletDemo5 implements Servlet {public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("擴展名匹配 Servlet");}...// 省略其他幾個方法
}
  • ServletDemo5程序的訪問路徑為:http://localhost:8080/Web-demo/任意.do

注意:

  1. 如果路徑配置的不是擴展名方式,那么在路徑的前面就必須要加**/**,否則會報錯。如下:

1627274483755

  1. 如果路徑配置的是*.do,那么在*.do的前面不能加上/,即不能是"/*.do",否則會報錯。如下:

1627274368245

任意匹配

1627273201370

/*** UrlPattern:* * 任意匹配: /*/
@WebServlet(urlPatterns = "/")
public class ServletDemo6 implements Servlet {public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("擴展名匹配 Servlet");}...// 省略其他幾個方法
}
  • ServletDemo6程序的訪問路徑為:http://localhost:8080/Web-demo/任意
/*** UrlPattern:* * 任意匹配: /**/
@WebServlet(urlPatterns = "/*")
public class ServletDemo7 implements Servlet {public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("擴展名匹配 Servlet");}...// 省略其他幾個方法
}
  • ServletDemo7程序的訪問路徑為:http://localhost:8080/Web-demo/任意

注意: 路徑//*的區別?

  1. 當我們的項目中的 Servlet 程序的urlpattern配置為 /,就會覆蓋掉 Tomcat中的 DefaultServlet類,當其他所有Servlet程序的url-pattern都匹配不上瀏覽器的某個請求URL時,最后都會匹配這個urlpattern配置為 /的 Servlet程序。

  2. 當我們的項目中配置了 Servlet 程序的urlpattern配置為 /,意味著該 Servlet程序可以匹配任意請求URL,但是精確匹配仍是排在第一位的。

  3. DefaultServlet是用來處理靜態資源,如果配置了/會把默認的DefaultServlet類覆蓋掉,就會引發請求靜態資源的時候沒有走默認的路徑而是走了自定義的 Servlet類,最終導致靜態資源不能被訪問。

5.1.2 小結

  1. urlPattern總共有四種配置方式,分別是精確匹配、目錄匹配、擴展名匹配、任意匹配

  2. 五種配置的優先級為 精確匹配 > 目錄匹配> 擴展名匹配 > /* > /,無需記,以最終運行結果為準。

5.2 使用 XML文件來配置 Servlet程序 的 URL

前面對應 Servlet程序的URL配置,我們都是使用 @WebServlet 這個注解,但 Servlet接口 從 3.0版本后才開始支持注解配置,3.0版本前只支持使用 XML文件的配置方法。

由于瀏覽器通過URL地址訪問 Web服務器中的 Web項目和資源,所以 Servlet 程序若想被瀏覽器訪問,就必須把 Servlet程序映射到一個URL地址上,這個工作需要在Web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。

  • <servlet>元素用于注冊 Servlet,它包含有兩個主要的子元素:<servlet-name><servlet-class>,分別用于設置 Servlet的注冊名稱和 Servlet的完整類名。

  • 一個<servlet-mapping>元素用于映射一個已注冊的 Servlet的一個對外訪問路徑,它包含有兩個子元素:<servlet-name><url-pattern>,分別用于指定 Servlet的 注冊名稱和該 Servlet的對外訪問路徑

對于 Web.xml文件的配置步驟有兩步:

第一步:編寫一個Servlet類(此時類上不需要加上@WebServlet這個注解了)

public class ServletDemo8 implements Servlet {public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {System.out.println("使用XML配置文件映射 Servlet");}...// 省略其他幾個方法
}

第二步:在web.xml文件中配置該 Servlet程序的URL

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/Web-app_4_0.xsd"version="4.0"><!-- 下面的元素用于注冊 Servlet,相當于告訴Web服務器存在一個Servlet,名稱為ServletDemo8,完整類名為...--><servlet><!-- Servlet的名稱,名字任意--><servlet-name>demo8</servlet-name><!-- Servlet的類全名--><servlet-class>com.Web.ServletDemo8</servlet-class></servlet><!-- 下面的元素用于映射一個已注冊的Servlet的對外訪問路徑,相當于告訴Web服務器,若想訪問ServletDemo8,就需要通過 /demo8 這個url路徑訪問--><servlet-mapping><!-- Servlet的名稱,要和上面的名稱一致--><servlet-name>demo8</servlet-name><!-- Servlet的訪問路徑--><url-pattern>/demo8</url-pattern></servlet-mapping>
</Web-app>

同一個 Servlet 可以被映射到多個URL路徑上,即多個<servlet-mapping>元素的<servlet-name>的設置值可以是同一個 Servlet的注冊名, 但是映射的<url-pattern>的值可以不一樣。例如:

<servlet-mapping><servlet-name>HelloServlet</servlet-name><url-pattern>/hello</url-pattern></servlet-mapping><servlet-mapping><servlet-name>HelloServlet</servlet-name><url-pattern>/hello.html</url-pattern>
</servlet-mapping>

通過上面的配置,當瀏覽器想訪問名稱是HelloServle的 Servlet程序時,可以使用如下的幾個地址去訪問:

  • http://localhost:8080/Web-demo/hello

  • http://localhost:8080/Web-demo/hello.html

顯然,HelloServlet程序被映射到了多個URL上。

這種通過 XML文件配置方式和注解比起來,確認麻煩很多,所以建議大家使用注解方式來開發。但是大家也要認識 XML文件的配置方式,因為并不是所有的項目都是基于注解開發的。

6. Servlet 接口詳解

下面我們將介紹Servlet 接口中的方法以及方法中的形參。

首先查看Servlet 接口中的接口方法:

在這里插入圖片描述

Servlet 接口中包含了五個方法,這些方法最難的地方在于,傳入方法中的形參是什么。不過幸好Tomcat已經事先幫我們把形參對象封裝好傳入方法中了,不需要我們再去設置。

除此之外,既不需要我們自己寫 TCP連接,也不需要我們解析 HTTP請求,更不需要我們把結果轉換成 HTTP響應,ServletRequest 對象和ServletResponse對象已經幫我們把這些難事搞定了(注:這兩個對象我們會在后面詳細介紹)。

所以,在 Servlet實現類里面主要寫的代碼都是業務邏輯,我們自己編寫的 Servlet程序和原始的、底層的解析、連接等沒有絲毫關系。最難的這些操作,Tomcat 已經幫我們封裝成形參對象傳入方法中了。

上面我們說過,Servlet 接口的作用是為了保證了方法名的規范性和一致性,若方法名不一致,Tomcat 將無法通過方法名對其實現類的方法進行調用。

因此我們自己寫的 Servlet程序,只需要實現 Servlet接口,并編寫處理瀏覽器所發送的請求的業務邏輯就好了。

在這里插入圖片描述

總的來說,Tomcat 已經幫我們完成了底層的操作,并且傳入了三個形參,分別是:ServletConfigServletRequestServletResponse。他們的解析將在下面分別介紹。

6.1 ServletConfig 類

ServletConfig 類即Servlet配置類,也就是我們在 Web.xml文件中配置的 <servlet> 標簽中的內容。它封裝了 Servlet程序的一些參數信息,如果需要這些信息,我們可以從ServletConfig 對象中獲取。

如下圖為TestServlet類實例化以及ServletConfig類實例化過程:

在這里插入圖片描述

過程解釋:

  • 首先,Tomcat 解析 Web.xml配置文件,在<servlet-mapping>標簽中通過<url-pattern>找到對應的<servlet-name>中設置的 TestServlet。

  • 然后Tomcat再去<servlet>標簽中通過<servlet-name>找到對應的<servlet-class>中設置的TestServlet 的全類名,最后通過 Java的反射機制便可將 TestServlet 類實例化為對象。

  • 在 TestServlet實例化的同時,Tomcat 也會對ServletConfig類進行實例化。

  • 最后,Tomcat 會將 ServletConfig 的實例對象作為參數傳遞給 TestServlet對象中的init()方法。

6.2 ServletRequest 和 ServletResponse 類

ServletRequest類是接收請求和發送請求的類,Tomcat 已經處理并封裝好了,不需要我們編寫的 Servlet程序操心。

當瀏覽器發送的HTTP請求到達 Tomcat之后,Tomcat 通過字符串解析,把各個請求頭(Header)、請求地址(URL)、請求參數等都封裝進ServletRequest對象中,然后將ServletRequest對象傳遞給負責處理請求的 Servlet程序。

在 Servlet程序 中通過調用ServletRequest對象中的方法,就可以得到瀏覽器發送的請求信息。如下:

在這里插入圖片描述

至于ServletResponse類,當瀏覽器發送的HTTP請求到達 Tomcat之后,Tomcat 也會將一個ServletResponse對象傳遞給 Servlet程序,不過此時它還是一個空對象。

在 Servlet程序 處理完請求后會得到處理結果,這時 Servlet程序就會通過ServletResponse對象的write()方法,將處理結果寫入ServletResponse對象內部的緩沖區。

Servlet程序處理請求結束后,會將封裝了處理結果的ServletResponse對象返回給 Tomcat,Tomcat 遍歷ServletResponse對象中緩沖區存儲的信息,最后組裝成 HTTP響應發給瀏覽器。

如下圖是ServletRequest/ServletResponse 對象的傳遞流程:

img

6.3 Servlet 接口中的方法

上面我們介紹完ServletConfigServletRequestServletResponse類后,對于Servlet 接口中的方法形參有了一些了解。接下來我們就介紹一下Servlet 接口中的5個方法。如下圖:

在這里插入圖片描述

Servlet接口中包含了5個方法,其中 init()service()destory() 這3個方法是聲明周期方法init()destory()方法在 Servlet程序存活期間各自只會執行一次,即分別在 Servlet程序被 Tomcat創建和銷毀時。而service()方法則在每次有新請求到來時都會被調用。也就是說,我們主要的業務代碼需要寫在service()方法中。

但是,瀏覽器發送的請求基本只有兩種類型:GET/POST。所以我們必須在service()方法中對不同的請求類型進行判斷。如下:

在這里插入圖片描述

  • 其中第一行的request.getMethod()方法是獲取瀏覽器發送請求的類型:GET/POST/PUT...等等,我們需要判斷瀏覽器發送請求的類型,并根據不同的請求類型對請求進行處理。

那能不能將上面的代碼簡化呢?因為每一個 Servlet程序都要寫這些重復的判斷請求類型代碼的話,工作量會很大且沒有意義,我們不想自己寫這些判斷請求類型的代碼。

我們去看看 JavaAPI給我們提供的 Servlet 接口庫是否有抽象類或者接口已經幫我們寫好這些代碼了,如果有,那我們的 Servlet程序直接繼承或者實現它不就行了嗎?

7. Servlet 體系結構

要想解決上面的問題,我們需要先對 Servlet接口的體系結構進行了解。如下圖:

1627240593506

因為我們將來開發B/S架構的Web項目,都是針對 HTTP協議,所以我們自定義的 Servlet程序,其實都繼承自 HttpServlet抽象類。

我們分別來看看 這幾個抽象類都有些什么吧。

7.1 GenericServlet 抽象類

GenericServlet抽象類:該類實現了 Servlet接口(Generic:通用的)。 GenericServlet類源碼如下:

在這里插入圖片描述

GenericServlet抽象類的作用如下:

  • 定義了一個全局變量ServletConfig對象,提升了init()方法中原本是形參的ServletConfig對象的作用域,使得ServletConfig對象變成了全局變量,方便其他方法使用。
  • init() 方法中還另外調用了一個init()空參方法,如果我們希望 Servlet程序在被創建時做一些特定的初始化操作,可以在繼承GenericServlet類后,重寫該init()空參方法。
  • 為了方便在其他類中也可以獲得ServletConfig對象,于是寫了一個getServletConext()方法。

不過令人沮喪的是,GenericServlet類中的service()方法還是一個空方法,這并沒有完成我們最初簡化代碼開發的設想。那繼續看一下HttpServlet 抽象類

7.2 HttpServlet 抽象類

HttpServlet 抽象類,它繼承自GenericServlet類。HttpServlet類源碼如下:

在這里插入圖片描述

HttpServlet 本身是一個抽象類,它定義了很多全局變量,我們看看它里面的service()方法有沒有實現對瀏覽器發送請求的類型進行判斷。源碼如下:

在這里插入圖片描述

顯而易見,HttpServlet類中的service()方法幫我們完成了對復雜的瀏覽器發送請求的類型判斷,而這正是我們想要的。

service()方法中,如果請求類型為 GET類型,則會調用doGet()方法處理該請求;如果請求類型為 POST類型,則會調用doPost()方法處理該請求。

問題是HttpServlet 類為什么還要聲明成抽象類呢?它的文檔中注釋了:

在這里插入圖片描述

可以發現,繼承了HttpServlet類的 Servlet程序,必須重寫doGet()doPost()doPut()doDelete()等方法中的至少一個。

補充:一個類聲明成抽象類,一般有兩個原因:

  1. 它內部有抽象方法。

  2. 它沒有抽象方法,但是該類不希望被實例化。

其實,HTTPServlet類做成抽象類,是為了不被實例化。

在這里插入圖片描述

它為什么不希望被實例化,而且要求子類重寫doGet()doPost()等方法呢?我們來看一下源碼:

在這里插入圖片描述

HttpServlet中的doGet()方法,使用了protected修飾,意思是希望子類能重寫該方法。

那如果我們沒有重寫doGet()doPost()等方法,又會怎么樣?

在這里插入圖片描述

源碼告訴我們,瀏覽器頁面會顯示405錯誤:(http.method_get_not_supported)

也就是說,HttpServlet類雖然在service()方法中幫我們完成了對瀏覽器發送請求的類型的判斷。但是在實際開發中,針對每一種請求類型,具體的業務邏輯代碼都是不同的(換句話說,HttpServlet只能幫我們把不同類型的請求分類,而并不能幫我們處理請求)。

由于HttpServlet無法知曉具體的請求想干什么,所以它索性抽象出了 7個方法,并且提供了方法的默認實現:即報 405、400錯誤。這些錯誤提示我們HttpServlet類并不支持處理具體的請求業務。因此,我們必須重寫這些方法,用來處理具體的請求業務。

在這里插入圖片描述

以上就是不能讓HttpServlet類被實例化的原因了,不然如果不小心調用了HttpServlet對象提供的doXxx()方法去處理請求,就會出錯。

而這也就是模板方法模式:父類把能寫的邏輯都寫完,把不確定的業務代碼抽象成一個方法,并調用它。當子類重寫了該方法后,整個業務代碼就活了。

我們自己編寫的 Servlet程序繼承了HttpServlet類,當 Tomcat 以后調用HttpServlet類的service()方法時,在該方法內部會調用doXxx()方法。由于我們的 Servlet程序重寫了HttpServlet類的doXxx()方法,因此service()方法最終調用的是我們自己編寫的doXxx()方法。

補充:Java子類調用父類的方法的步驟:

  1. 子類的對象調用方法時,會首先在子類中查找是否存在該方法,如果子類中沒有該方法,再到父類中查找。
  2. 如果該方法中又調用了其他方法,那么還是按照之前的順序,先在子類中查找,再在父類中查找。

如下圖:

在這里插入圖片描述

小結

  • 最后我們明白了,我們自己編寫一個 Servlet程序來處理瀏覽器發送的請求,只需要繼承HttpServlet類,然后在 Servlet程序中根據不同的請求類型和請求業務,重寫HttpServlet類中的doGet()doPost()doPut()doDelete()等方法即可。
  • 其他的事就不需要我們管了,方法形參由 Tomcat幫我們封裝好傳遞進來,請求類型的判斷由HttpServlet類的service()方法幫我們完成。

8. Servlet 生命周期

8.1 生命周期與三個重要方法的對應

生命周期:Servlet 生命周期指它從被創建到被銷毀的整個過程。對應 Servlet 中的三個方法:init(), service(), destroy()

默認情況下:

  • 第一次接收到瀏覽器發送的請求時,負責處理請求的 Servlet程序會進行實例化(調用構造方法)、初始化(調用init()方法)、然后處理請求(調用service()方法)。
  • 從第二次接收請求開始,Servlet 只會調用service()方法處理請求,沒有實例化和初始化的過程了。
  • 當Tomcat 容器關閉時,容器中所有的 Servlet 實例都會被銷毀,此時調用銷毀方法destroy(),實例隨后會被 Java的垃圾收集器所回收。

8.2 生命周期的演示

我么通過重寫 Servlet接口中的這三個方法來看一下 Servlet 的整個生命周期過程,之前我們說過,如果子類中重寫了父類的方法,那么調用的是子類重寫后的方法。

Web-demo項目中創建Demo02Servlet類。如下:

// 演示Servlet的生命周期
@WebServlet(urlPatterns = "/demo2")
public class Demo02Servlet extends HttpServlet {public Demo02Servlet(){System.out.println("正在實例化....");}@Overridepublic void init() throws ServletException {System.out.println("正在初始化.....");}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("正在服務.....");}@Overridepublic void destroy() {System.out.println("正在銷毀......");}
}

在瀏覽器中發送第一次請求,訪問:http://localhost:8080/Web-demo/demo2

控制臺輸出如下:

在這里插入圖片描述

然后刷新網頁,在網頁上每一次刷新,就代表著重新發送一次請求,這個時候控制臺輸出信息只有:正在服務…。如下圖:

在這里插入圖片描述

最后點擊停止運行按鈕,Tomcat 容器就會對所有的 Servlet實例 進行銷毀,控制臺輸出如下:

在這里插入圖片描述

通過上面案例我們發現:

  1. 對于每一個 Servlet 實例,Tomcat 只會創建一個,所有相同的請求都由對應的處理該請求的 Servlet實例去處理。

  2. 默認情況下,第一次發送請求時,Tomcat 才會去實例化和初始化 Servlet程序,然后由 Servlet程序處理請求。

    • 這樣做的好處是: 提高系統的啟動速度。就是說只在有請求時,才創建 Servlet 對象。
    • 這樣做的缺點是: 處理第一次發送的請求時,系統耗時較長。

結論:

  • 如果需要提高系統的啟動速度,當前默認情況就是這樣。
  • 如果需要提高系統的響應速度,我們就應該設置 Servlet 的初始化時機。

8.3 Servlet 的初始化時機

默認是第一次接收請求時,進行實例化,初始化 Servlet。

我們可以通過修改Web.xml文件中 Servlet程序 的 <load-on-startup>標簽來設置 Servlet程序啟動的先后順序,數字越小,啟動順序越靠前,最小值為0(這個優先級表示隨著 Tomcat 的啟動,直接對 Servlet程序進行實例化和初始化操作,無需等到有請求到來)。

修改Web.xml配置文件如下:

<servlet><servlet-name>Demo02Servlet</servlet-name><servlet-class>com.servlets.Demo02Servlet</servlet-class><!--設置初始化優先級--><load-on-startup>1</load-on-startup>
</servlet>

啟動 Tomcat時控制臺展示如下,可以看出,在部署好 Tomcat 之前 Demo02Servlet就進行了實例化和初始化:

在這里插入圖片描述

補充:我們也可以使用注解@WebServlet(urlPatterns = "/demo2", loadOnStartup = 1)來設置 Servlet程序初始化的優先級。loadOnstartup的取值有兩類情況:

  • 負整數:第一次有請求訪問時再初始化 Servlet對象。
  • 0 或正整數:服務器啟動時就創建 Servlet對象,數字越小優先級越高。

9. Servlet 線程不安全問題

Servlet 在Tomcat容器中是:單例的、多線程情況下是線程不安全的。

單例:所有相同的請求都由對應處理該請求的同一個 Servlet實例去響應處理。

線程不安全:假如有兩個瀏覽器線程同時請求同一個 Servlet實例,第一個線程需要根據 Servlet程序中的某個成員變量值去做邏輯判斷。但是在第一個線程進行邏輯判斷的過程中,第二個線程改變了這個成員變量的值,從而導致第一個線程的判斷結果發生了變化。這就是線程不安全問題。如下圖:

eecd046a42bd4eeb9ffa819433084d0c

解決多線程不安全問題的注意事項:

  1. 盡量的不要在 Servlet程序中定義成員變量。
  2. 如果不得不定義成員變量,那么一定不要去:①修改成員變量的值;②根據成員變量的值做一些邏輯判斷。

10. 小結一下 Tomcat 和 Servlet的執行流程

一個 Web 項目,需要部署到 Tomcat 容器中去,并不是把 Web 項目的源代碼部署進去,而是把 Web 項目的部署包artifact部署到 Tomcat中去。所以我們需要先生成 Web項目的 部署包,在IDEA 2022中,創建 Web 項目時,就會自動生成其對應的部署包了。不過在舊版的 IDEA 中,則需要在 Project Structure中手動生成 Web項目的部署包。

若是已經生成了 Web項目的部署包之后,又需要在 Web 項目中另外引入新的 jar 包,這個后面引入的 jar 包在已部署的包中不會自動更新。此時在Project Structure中會出現 Problems提示,我們根據其提示操作就可以解決該問題。另外,我們也可以直接把第三方 jar 包直接放到已部署的 Web 項目中的 WEB-INF目錄下,這樣該 jar 包也會自動添加進部署包中,不過這樣做的缺點是該后添 jar 包只能給該Web項目獨享,其他的項目不能再使用此 jar包。

最后就是要在 Tomcat 中部署 Web 項目的部署包了。首先進入 Tomcat 模板中的 Deployment模塊,添加需要部署的 Web項目的 Artifact部署包,然后會出現一個Web項目的根目錄Application context,其內設置的目錄路徑就是 Web 項目在 Tomcat 容器中的根目錄路徑context root,我們可以對其進行更改。 當Tomcat 啟動后,會自動打開指定的瀏覽器,然后去訪問Web項目的URL

1.假設在 Tomcat 中設置的 URL 為http://localhost:8080/demo,啟動 Tomcat 后,瀏覽器便會訪問該 URL,此時會向 Tomcat 服務器中部署的 Web 項目中請求資源 demo,于是 Tomcat 便會到 Web 項目中尋找該資源。

  • 若通過注解@WebServlet沒有直接找到,Tomcat 便解析 Web.xml配置文件,在<servlet-mapping> 標簽中找到了 <url-patter>標簽中值為/demo所對應的<servlet-name>標簽中的類名。
  • 然后去<servlet>標簽中找到相匹配的 Servlet 類,然后通過反射生成該類的實例對象,并自動調用其 sevice()方法,最后在使用結束后銷毀該實例對象。

2.假設在 Tomcat 中設置的 URL 為http://localhost:8080/test.html,啟動 Tomcat 后,在瀏覽器訪問該 URL,則是向 Tomcat 服務器中部署的 Web 項目中請求資源 test.html,于是 Tomcat 便會到 Web 項目中尋找該資源,找到該資源后,便給瀏覽器返回響應,于是在瀏覽器中便會顯示對應的test.html靜態界面,

瀏覽器啟動后,可能會報錯 404。404:意味著找不到指定的資源。 假設我們的網址是:http://localhost:8080/pro01/(/pro01是根目錄,沒有指定要訪問的資源路徑), 那么表明客戶端請求的資源是 Web 項目中的 index.html,它也是客戶端默認訪問的資源路徑。

我們也可以通過Web.xml中的<welcome-file-list>標簽進行設置客戶端默認訪問的資源路徑(在 Tomcat的Web.xml中設置,或者在自己項目的Web.xml中設置)。

**瀏覽器啟動后,可能會報錯 405。405:表示當前請求的方法不支持。**比如,我們的 form表單中的method=post , 那么 Servlet 必須重寫doPost()方法。否則會報 405 錯誤。

還有可能會出現空指針異常或者是NumberFormatException

最后注意:web.xml文件中的<url-pattern>標簽中的值中以斜杠開頭。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/714859.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/714859.shtml
英文地址,請注明出處:http://en.pswp.cn/news/714859.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【MATLAB】語音信號識別與處理:SG濾波算法去噪及譜相減算法呈現頻譜

1 基本定義 SG 濾波算法&#xff08;Savitzky - Golay 濾波算法&#xff09;是一種數字信號處理算法&#xff0c;用于對信號進行平滑處理。該算法利用最小二乘法擬合局部數據段&#xff0c;然后用擬合的函數來估計每個數據點的值&#xff0c;從而實現平滑處理。 SG 濾波算法的…

redis05 sprngboot整合redis

redis的Java客戶端 整合步驟 添加redis的pom依賴 <!-- 引入redis依賴 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency><!-- 引入redis連…

51單片機學習day02

基于普中的stc89c52&#xff0c; 串口&#xff1a; 通訊接口&#xff0c;51單片機自帶UART&#xff08;通用異步收發器&#xff09;&#xff0c;可實現窗口通訊。 硬件電路&#xff1a; 簡單雙向串口通信有兩根通信線&#xff08;發送端TXD和接收端RXD&#xff09;&#xff0…

HelixToolKit的模型旋轉操作

前面加載了模型以后&#xff0c;鼠標拖動和縮放比較好操作&#xff1b;但是旋轉似乎沒有&#xff0c; 操作了一陣&#xff0c;也不是沒有&#xff0c;應該是還不熟悉&#xff1b; 旋轉的指示器在右下角&#xff0c;現在U面看到正面&#xff0c; 想看一下模型的背面&#xff0…

【Java項目介紹和界面搭建】拼圖小游戲——添加圖片

&#x1f36c; 博主介紹&#x1f468;?&#x1f393; 博主介紹&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高興認識大家~ ?主攻領域&#xff1a;【滲透領域】【應急響應】 【Java】 【VulnHub靶場復現】【面試分析】 &#x1f389;點贊?評論?收藏 …

扼殺網絡中的環路:STP、RSTP、MSTP

目錄 前言&#xff1a; 一、STP&#xff08;Spanning Tree Protocol&#xff09; 1.1 STP功能 1.2 STP應用 二、RSTP&#xff08;Rapid Spanning Tree Protocol&#xff09; 2.1 RSTP功能 2.2 RSTP應用 三、MSTP&#xff08;Multiple Spanning Tree Protocol&#xff0…

Angular 由一個bug說起之四:jsonEditor使用不當造成的bug

一&#xff1a;問題 項目中使用了一個JSON第三方庫&#xff1a; GitHub - josdejong/jsoneditor: A web-based tool to view, edit, format, and validate JSON 當用戶編輯JSON格式的數據&#xff0c;查找替換時&#xff1a; 用戶的期望結果是&#xff1a;$$ 被替換為$$_text&a…

[物聯網] OneNet 多協議TCP透傳

[物聯網] OneNet 多協議TCP透傳 STM32物聯網–ONENET云平臺的多協議接入產品創建 : https://blog.csdn.net/qq_44942724/article/details/134492924 Onenet tcp 透傳 : https://blog.csdn.net/flyme2010/article/details/107086001 tcp服務端測試工具 : http://tcp.xnkiot.com/…

zephyr學習

zephyr內核對象學習 定時器 類似linux的定時器&#xff0c; 可以分別設置第一次到期時間和后續的周期觸發時間&#xff0c; 可以注冊到期回調和停止回調 還有一個計數狀態&#xff0c;用于標記timer到期了多少次 duration&#xff1a;設定timer第一次到期的時間。 period: …

SpringBoot3.2.0整合MyBatis-plus的相關問題及處理方法

SpringBoot3.2.0整合MyBatis-plus的相關問題 文章目錄 SpringBoot3.2.0整合MyBatis-plus的相關問題1. build.gradle2. mybatis-plus整合問題1. 錯誤描述2. 問題分析及解決1. 原因分析2. 解決方式 Springboot3.2.0 GA版發布于 2023-11-24 環境&#xff1a;SpringBoot3.2.0Gradle…

【蛀牙】日常生活如何正確護理牙齒?刷牙、洗牙、補牙

程序員生活指南之 【蛀牙】日常生活如何正確護理牙齒&#xff1f;刷牙、洗牙、補牙 文章目錄 一、日常如何清洗牙齒&#xff1f;——刷牙與洗牙1、牙齒污垢1.1 牙菌斑1.2 軟垢1.3 牙結石1.4 牙齦出血 2、如何刷牙2.1 關于時間2.2 各種工具2.3 巴氏刷牙法 二、定期進行洗牙3、如…

題目 1076: 內部收益率

題目描述: 在金融中&#xff0c;我們有時會用內部收益率IRR來評價項目的投資財務效益&#xff0c;它等于使得投資凈現值NPV等于0的貼現率。換句話說&#xff0c;給定項目的期數T、初始現金流CF0和項目各期的現金流CF1, CF2, ...&#xff0c;CFT&#xff0c;IRR是下面方程的解&…

RISC-V特權架構 - 特權模式與指令

RV32/64 特權架構 - 特權模式與指令 1 特權模式2 特權指令2.1 mret&#xff08;從機器模式返回到先前的模式&#xff09;2.2 sret&#xff08;從監管模式返回到先前的模式&#xff09;2.3 wfi&#xff08;等待中斷&#xff09;2.4 sfence.vma&#xff08;內存屏障&#xff09; …

SpringBoot+Vue+MySQL:裝修管理新架構探索

??計算機畢業編程指導師 ??個人介紹&#xff1a;自己非常喜歡研究技術問題&#xff01;專業做Java、Python、微信小程序、安卓、大數據、爬蟲、Golang、大屏等實戰項目。 ??實戰項目&#xff1a;有源碼或者技術上的問題歡迎在評論區一起討論交流&#xff01; ?? Java、…

FPGA開源項目分享——2D N-Body重力模擬器

?導語 今天繼續康奈爾大學FPGA 課程ECE 5760的典型案例分享——2D N-Body重力模擬器。 &#xff08;更多其他案例請參考網站&#xff1a; Final Projects ECE 5760&#xff09; 1. 項目概述 項目網址 Grav Sim 項目說明 該項目的目標是創建一個用DE1-SOC進行硬件加速的2…

Java面試技巧

一、面試前準備 復習基礎知識&#xff1a;深入理解Java核心概念&#xff0c;如JVM、JDK、JRE等。熟悉Java基本語法、面向對象編程、異常處理、集合類、IO流等。同時&#xff0c;對Java的新特性&#xff0c;如Lambda表達式、Stream API等也要有所了解。強化算法和數據結構&…

簡易內存池2 - 華為OD統一考試(C卷)

OD統一考試&#xff08;C卷&#xff09; 分值&#xff1a; 200分 題解&#xff1a; Java / Python / C 題目描述 請實現一個簡易內存池,根據請求命令完成內存分配和釋放。 內存池支持兩種操作命令&#xff0c;REQUEST和RELEASE&#xff0c;其格式為: REQUEST請求的內存大小 …

Redis 【1】—— 安裝 與 配置

Redis 【1】—— 安裝 與 配置 一、安裝 與 配置&#xff08;一&#xff09;使用 yum 安裝&#xff08;二&#xff09;創建符號鏈接1. 軟鏈接2. 相關指令 &#xff08;三&#xff09;修改配置文件&#xff08;四&#xff09;Redis 的啟停 一、安裝 與 配置 &#xff08;一&…

Java的強引用、軟引用、弱引用和虛引用詳解。

Java的強引用、軟引用、弱引用和虛引用 1. 強引用2. 軟引用3. 弱引用4. 虛引用 總結 Java的強引用、軟引用、弱引用和虛引用可以用來標識GC時判斷對象是否達到回收的條件&#xff0c;下面結合Java代碼看看這四類引用吧。 1. 強引用 是最普通的引用方式&#xff0c;通過new關鍵…

外貿業務員沒客戶的7大原因+解決辦法!

業務員沒有客戶&#xff0c;就是無源之水&#xff0c;無本之木&#xff0c;這自然也就沒有業績。那些吃空餉的業務員&#xff0c;遲早會拖垮公司。所以不管是什么原因導致的業務員沒客戶&#xff0c;都要一一查驗清楚。七個業務員沒有客戶的原因&#xff0c;七種對策&#xff0…