理解Servlet工作原理

什么是Servlet

Servlet的作用是為Java程序提供一個(gè)統(tǒng)一的web應(yīng)用的規(guī)范,方便程序員統(tǒng)一的使用這種規(guī)范來編寫程序,應(yīng)用容器可以使用提供的規(guī)范來實(shí)現(xiàn)自己的特性。比如tomcat的代碼和jetty的代碼就不一樣,但作為程序員你只需要了解servlet規(guī)范就可以從request中取值,你可以操作session等等。不用在意應(yīng)用服務(wù)器底層的實(shí)現(xiàn)的差別而影響你的開發(fā)。

HTTP 協(xié)議只是一個(gè)規(guī)范,定義服務(wù)請(qǐng)求和響應(yīng)的大致式樣。Java servlet 類將HTTP中那些低層的結(jié)構(gòu)包裝在 Java 類中,這些類所包含的便利方法使其在 Java 語言環(huán)境中更易于處理。

正如您正使用的特定 servlet 容器的配置文件中所定義的,當(dāng)用戶通過 URL 發(fā)出一個(gè)請(qǐng)求時(shí),這些 Java servlet 類就將之轉(zhuǎn)換成一個(gè) HttpServletRequest,并發(fā)送給 URL 所指向的目標(biāo)。當(dāng)服務(wù)器端完成其工作時(shí),Java 運(yùn)行時(shí)環(huán)境(Java Runtime Environment)就將結(jié)果包裝在一個(gè) HttpServletResponse 中,然后將原 HTTP 響應(yīng)送回給發(fā)出該請(qǐng)求的客戶機(jī)。在與 Web 應(yīng)用程序進(jìn)行交互時(shí),通常會(huì)發(fā)出多個(gè)請(qǐng)求并獲得多個(gè)響應(yīng)。所有這些都是在一個(gè)會(huì)話語境中,Java 語言將之包裝在一個(gè) HttpSession 對(duì)象中。在處理響應(yīng)時(shí),您可以訪問該對(duì)象,并在創(chuàng)建響應(yīng)時(shí)向其添加事件。它提供了一些跨請(qǐng)求的語境。

容器(如 Tomcat)將為 servlet 管理運(yùn)行時(shí)環(huán)境。您可以配置該容器,定制 J2EE 服務(wù)器的工作方式,以便將 servlet 暴露給外部世界。正如我們將看到的,通過該容器中的各種配置文件,您在 URL(由用戶在瀏覽器中輸入)與服務(wù)器端組件之間搭建了一座橋梁,這些組件將處理您需要該 URL 轉(zhuǎn)換的請(qǐng)求。在運(yùn)行應(yīng)用程序時(shí),該容器將加載并初始化 servlet,管理其生命周期。

Servlet體系結(jié)構(gòu)

Servlet頂級(jí)類關(guān)聯(lián)圖

Servlet

Servlet的框架是由兩個(gè)Java包組成的:javax.servlet與javax.servlet.http。在javax.servlet包中定義了所有的Servlet類都必須實(shí)現(xiàn)或者擴(kuò)展的通用接口和類。在javax.servlet.http包中定義了采用Http協(xié)議通信的HttpServlet類。Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必須實(shí)現(xiàn)這個(gè)接口。

Servlet接口

在Servlet接口中定義了5個(gè)方法:

1. init(ServletConfig)方法:負(fù)責(zé)初始化Servlet對(duì)象,在Servlet的生命周期中,該方法執(zhí)行一次;該方法執(zhí)行在單線程的環(huán)境下,因此開發(fā)者不用考慮線程安全的問題;
2. service(ServletRequest req,ServletResponse res)方法:負(fù)責(zé)響應(yīng)客戶的請(qǐng)求;為了提高效率,Servlet規(guī)范要求一個(gè)Servlet實(shí)例必須能夠同時(shí)服務(wù)于多個(gè)客戶端請(qǐng)求,即service()方法運(yùn)行在多線程的環(huán)境下,Servlet開發(fā)者必須保證該方法的線程安全性;
3. destroy()方法:當(dāng)Servlet對(duì)象退出生命周期時(shí),負(fù)責(zé)釋放占用的資源;
4. getServletInfo:就是字面意思,返回Servlet的描述;
5. getServletConfig:這個(gè)方法返回由Servlet容器傳給init方法的ServletConfig。

ServletRequest & ServletResponse

對(duì)于每一個(gè)HTTP請(qǐng)求,servlet容器會(huì)創(chuàng)建一個(gè)封裝了HTTP請(qǐng)求的ServletRequest實(shí)例傳遞給servlet的service方法,ServletResponse則表示一個(gè)Servlet響應(yīng),其隱藏了將響應(yīng)發(fā)給瀏覽器的復(fù)雜性。通過ServletRequest的方法你可以獲取一些請(qǐng)求相關(guān)的參數(shù),而ServletResponse則可以將設(shè)置一些返回參數(shù)信息,并且設(shè)置返回內(nèi)容。

ServletConfig

ServletConfig封裝可以通過@WebServlet或者web.xml傳給一個(gè)Servlet的配置信息,以這種方式傳遞的每一條信息都稱做初始化信息,初始化信息就是一個(gè)個(gè)K-V鍵值對(duì)。為了從一個(gè)Servlet內(nèi)部獲取某個(gè)初始參數(shù)的值,init方法中調(diào)用ServletConfig的getinitParameter方法或getinitParameterNames方法獲取,除此之外,還可以通過getServletContext獲取ServletContext對(duì)象。

ServletContext

ServletContext是代表了Servlet應(yīng)用程序。每個(gè)Web應(yīng)用程序只有一個(gè)context。在分布式環(huán)境中,一個(gè)應(yīng)用程序同時(shí)部署到多個(gè)容器中,并且每臺(tái)Java虛擬機(jī)都有一個(gè)ServletContext對(duì)象。有了ServletContext對(duì)象后,就可以共享能通過應(yīng)用程序的所有資源訪問的信息,促進(jìn)Web對(duì)象的動(dòng)態(tài)注冊(cè),共享的信息通過一個(gè)內(nèi)部Map中的對(duì)象保存在ServiceContext中來實(shí)現(xiàn)。保存在ServletContext中的對(duì)象稱作屬性。操作屬性的方法:

GenericServlet

前面編寫的Servlet應(yīng)用中通過實(shí)現(xiàn)Servlet接口來編寫Servlet,但是我們每次都必須為Servlet中的所有方法都提供實(shí)現(xiàn),還需要將ServletConfig對(duì)象保存到一個(gè)類級(jí)別的變量中,GenericServlet抽象類就是為了為我們省略一些模板代碼,實(shí)現(xiàn)了Servlet和ServletConfig,完成了一下幾個(gè)工作:

將init方法中的ServletConfig賦給一個(gè)類級(jí)變量,使的可以通過getServletConfig來獲取。

public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
}

同時(shí)為避免覆蓋init方法后在子類中必須調(diào)用super.init(servletConfig),GenericServlet還提供了一個(gè)不帶參數(shù)的init方法,當(dāng)ServletConfig賦值完成就會(huì)被第帶參數(shù)的init方法調(diào)用。這樣就可以通過覆蓋不帶參數(shù)的init方法編寫初始化代碼,而ServletConfig實(shí)例依然得以保存

為Servlet接口中的所有方法提供默認(rèn)實(shí)現(xiàn)。

提供方法來包裝ServletConfig中的方法。

HTTPServlet

在編寫Servlet應(yīng)用程序時(shí),大多數(shù)都要用到HTTP,也就是說可以利用HTTP提供的特性,javax.servlet.http包含了編寫Servlet應(yīng)用程序的類和接口,其中很多覆蓋了javax.servlet中的類型,我們自己在編寫應(yīng)用時(shí)大多時(shí)候也是繼承的HttpServlet。

Servlet工作原理

當(dāng)Web服務(wù)器接收到一個(gè)HTTP請(qǐng)求時(shí),它會(huì)先判斷請(qǐng)求內(nèi)容——如果是靜態(tài)網(wǎng)頁數(shù)據(jù),Web服務(wù)器將會(huì)自行處理,然后產(chǎn)生響應(yīng)信息;如果牽涉到動(dòng)態(tài)數(shù)據(jù),Web服務(wù)器會(huì)將請(qǐng)求轉(zhuǎn)交給Servlet容器。此時(shí)Servlet容器會(huì)找到對(duì)應(yīng)的處理該請(qǐng)求的Servlet實(shí)例來處理,結(jié)果會(huì)送回Web服務(wù)器,再由Web服務(wù)器傳回用戶端。

針對(duì)同一個(gè)Servlet,Servlet容器會(huì)在第一次收到http請(qǐng)求時(shí)建立一個(gè)Servlet實(shí)例,然后啟動(dòng)一個(gè)線程。第二次收到http請(qǐng)求時(shí),Servlet容器無須建立相同的Servlet實(shí)例,而是啟動(dòng)第二個(gè)線程來服務(wù)客戶端請(qǐng)求。所以多線程方式不但可以提高Web應(yīng)用程序的執(zhí)行效率,也可以降低Web服務(wù)器的系統(tǒng)負(fù)擔(dān)。

Web服務(wù)器工作流程

接著我們描述一下Tomcat與Servlet是如何工作的,首先看下面的時(shí)序圖:

Servlet工作原理時(shí)序圖
  1. Web Client 向Servlet容器(Tomcat)發(fā)出Http請(qǐng)求;

  2. Servlet容器接收Web Client的請(qǐng)求;

  3. Servlet容器創(chuàng)建一個(gè)HttpRequest對(duì)象,將Web Client請(qǐng)求的信息封裝到這個(gè)對(duì)象中;

  4. Servlet容器創(chuàng)建一個(gè)HttpResponse對(duì)象;

  5. Servlet容器調(diào)用HttpServlet對(duì)象的service方法,把HttpRequest對(duì)象與HttpResponse對(duì)象作為參數(shù)傳給 HttpServlet對(duì)象;

  6. HttpServlet調(diào)用HttpRequest對(duì)象的有關(guān)方法,獲取Http請(qǐng)求信息;

  7. HttpServlet調(diào)用HttpResponse對(duì)象的有關(guān)方法,生成響應(yīng)數(shù)據(jù);

  8. Servlet容器把HttpServlet的響應(yīng)結(jié)果傳給Web Client;

Servlet生命周期

在Servlet接口中定義了5個(gè)方法,其中3個(gè)方法代表了Servlet的生命周期:

1. init(ServletConfig)方法:負(fù)責(zé)初始化Servlet對(duì)象,在Servlet的生命周期中,該方法執(zhí)行一次;該方法執(zhí)行在單線程的環(huán)境下,因此開發(fā)者不用考慮線程安全的問題;
2. service(ServletRequest req,ServletResponse res)方法:負(fù)責(zé)響應(yīng)客戶的請(qǐng)求;為了提高效率,Servlet規(guī)范要求一個(gè)Servlet實(shí)例必須能夠同時(shí)服務(wù)于多個(gè)客戶端請(qǐng)求,即service()方法運(yùn)行在多線程的環(huán)境下,Servlet開發(fā)者必須保證該方法的線程安全性;
3. destroy()方法:當(dāng)Servlet對(duì)象退出生命周期時(shí),負(fù)責(zé)釋放占用的資源;

編程注意事項(xiàng)說明:

  1. 當(dāng)Server Thread線程執(zhí)行Servlet實(shí)例的init()方法時(shí),所有的Client Service Thread線程都不能執(zhí)行該實(shí)例的service()方法,更沒有線程能夠執(zhí)行該實(shí)例的destroy()方法,因此Servlet的init()方法是工作在單線程的環(huán)境下,開發(fā)者不必考慮任何線程安全的問題。
  2. 當(dāng)服務(wù)器接收到來自客戶端的多個(gè)請(qǐng)求時(shí),服務(wù)器會(huì)在單獨(dú)的Client Service Thread線程中執(zhí)行Servlet實(shí)例的service()方法服務(wù)于每個(gè)客戶端。此時(shí)會(huì)有多個(gè)線程同時(shí)執(zhí)行同一個(gè)Servlet實(shí)例的service()方法,因此必須考慮線程安全的問題。
  3. 雖然service()方法運(yùn)行在多線程的環(huán)境下,并不一定要同步該方法。而是要看這個(gè)方法在執(zhí)行過程中訪問的資源類型及對(duì)資源的訪問方式。分析如下:
1. 如果service()方法沒有訪問Servlet的成員變量也沒有訪問全局的資源比如靜態(tài)變量、文件、數(shù)據(jù)庫連接等,而是只使用了當(dāng)前線程自己的資源,比如非指向全局資源的臨時(shí)變量、request和response對(duì)象等。該方法本身就是線程安全的,不必進(jìn)行任何的同步控制。

2. 如果service()方法訪問了Servlet的成員變量,但是對(duì)該變量的操作是只讀操作,該方法本身就是線程安全的,不必進(jìn)行任何的同步控制。

3. 如果service()方法訪問了Servlet的成員變量,并且對(duì)該變量的操作既有讀又有寫,通常需要加上同步控制語句。

4. 如果service()方法訪問了全局的靜態(tài)變量,如果同一時(shí)刻系統(tǒng)中也可能有其它線程訪問該靜態(tài)變量,如果既有讀也有寫的操作,通常需要加上同步控制語句。

5. 如果service()方法訪問了全局的資源,比如文件、數(shù)據(jù)庫連接等,通常需要加上同步控制語句。

在創(chuàng)建一個(gè) Java servlet 時(shí),一般需要子類 HttpServlet。該類中的方法允許您訪問請(qǐng)求和響應(yīng)包裝器(wrapper),您可以用這個(gè)包裝器來處理請(qǐng)求和創(chuàng)建響應(yīng)。Servlet的生命周期,簡(jiǎn)單的概括這就分為四步:

Servlet類加載--->實(shí)例化--->服務(wù)--->銷毀;
Servlet生命周期

創(chuàng)建Servlet對(duì)象的時(shí)機(jī):

  1. 默認(rèn)情況下,在Servlet容器啟動(dòng)后:客戶首次向Servlet發(fā)出請(qǐng)求,Servlet容器會(huì)判斷內(nèi)存中是否存在指定的Servlet對(duì)象,如果沒有則創(chuàng)建它,然后根據(jù)客戶的請(qǐng)求創(chuàng)建HttpRequest、HttpResponse對(duì)象,從而調(diào)用Servlet對(duì)象的service方法;
  2. Servlet容器啟動(dòng)時(shí):當(dāng)web.xml文件中如果<servlet>元素中指定了<load-on-startup>子元素時(shí),Servlet容器在啟動(dòng)web服務(wù)器時(shí),將按照順序創(chuàng)建并初始化Servlet對(duì)象;
  3. Servlet的類文件被更新后,重新創(chuàng)建Servlet。Servlet容器在啟動(dòng)時(shí)自動(dòng)創(chuàng)建Servlet,這是由在web.xml文件中為Servlet設(shè)置的<load-on-startup>屬性決定的。從中我們也能看到同一個(gè)類型的Servlet對(duì)象在Servlet容器中以單例的形式存在;

注意:在web.xml文件中,某些Servlet只有<serlvet>元素,沒有<servlet-mapping>元素,這樣我們無法通過url的方式訪問這些Servlet,這種Servlet通常會(huì)在<servlet>元素中配置一個(gè)<load-on-startup>子元素,讓容器在啟動(dòng)的時(shí)候自動(dòng)加載這些Servlet并調(diào)用init(ServletConfig config)方法來初始化該Servlet。其中方法參數(shù)config中包含了Servlet的配置信息,比如初始化參數(shù),該對(duì)象由服務(wù)器創(chuàng)建。

銷毀Servlet對(duì)象的時(shí)機(jī):

Servlet容器停止或者重新啟動(dòng):Servlet容器調(diào)用Servlet對(duì)象的destroy方法來釋放資源。以上所講的就是Servlet對(duì)象的生命周期。那么Servlet容器如何知道創(chuàng)建哪一個(gè)Servlet對(duì)象?Servlet對(duì)象如何配置?實(shí)際上這些信息是通過讀取web.xml配置文件來實(shí)現(xiàn)的。

<servlet>
    <!-- Servlet對(duì)象的名稱 -->
    <servlet-name>action<servlet-name>
    <!-- 創(chuàng)建Servlet對(duì)象所要調(diào)用的類 -->
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
        <!-- 參數(shù)名稱 -->
        <param-name>config</param-name>
        <!-- 參數(shù)值 -->
        <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
        <param-name>detail</param-name>
        <param-value>2</param-value>
    </init-param>
    <init-param>
        <param-name>debug</param-name>
        <param-value>2</param-value>
    </init-param>
    <!-- Servlet容器啟動(dòng)時(shí)加載Servlet對(duì)象的順序 -->
    <load-on-startup>2</load-on-startup>
</servlet>
<!-- 要與servlet中的servlet-name配置節(jié)內(nèi)容對(duì)應(yīng) -->
<servlet-mapping>
    <servlet-name>action</servlet-name>
    <!-- 客戶訪問的Servlet的相對(duì)URL路徑 -->
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

當(dāng)Servlet容器啟動(dòng)的時(shí)候讀取<servlet>配置節(jié)信息,根據(jù)<servlet-class>配置節(jié)信息創(chuàng)建Servlet對(duì)象,同時(shí)根據(jù)<init-param>配置節(jié)信息創(chuàng)建HttpServletConfig對(duì)象,然后執(zhí)行Servlet對(duì)象的init方法,并且根據(jù)<load-on-startup>配置節(jié)信息來決定創(chuàng)建Servlet對(duì)象的順序,如果此配置節(jié)信息為負(fù)數(shù)或者沒有配置,那么在Servlet容器啟動(dòng)時(shí),將不加載此Servlet對(duì)象。當(dāng)客戶訪問Servlet容器時(shí),Servlet容器根據(jù)客戶訪問的URL地址,通過<servlet-mapping>配置節(jié)中的<url-pattern>配置節(jié)信息找到指定的Servlet對(duì)象,并調(diào)用此Servlet對(duì)象的service方法。

在整個(gè)Servlet的生命周期過程中,創(chuàng)建Servlet實(shí)例、調(diào)用實(shí)例的init()和destroy()方法都只進(jìn)行一次,當(dāng)初始化完成后,Servlet容器會(huì)將該實(shí)例保存在內(nèi)存中,通過調(diào)用它的service()方法,為接收到的請(qǐng)求服務(wù)。下面給出Servlet整個(gè)生命周期過程的UML序列圖,如圖所示:

Servlet生命周期

如果需要讓Servlet容器在啟動(dòng)時(shí)即加載Servlet,可以在web.xml文件中配置<load-on-startup>元素。

Servlet中的Listener

Listener 使用的非常廣泛,它是基于觀察者模式設(shè)計(jì)的,Listener 的設(shè)計(jì)對(duì)開發(fā) Servlet 應(yīng)用程序提供了一種快捷的手段,能夠方便的從另一個(gè)縱向維度控制程序和數(shù)據(jù)。目前 Servlet 中提供了 5 種兩類事件的觀察者接口,它們分別是:4 個(gè) EventListeners 類型的,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 個(gè) LifecycleListeners 類型的,ServletContextListener、HttpSessionListener。如下圖所示:

Servlet中的Listener

它們基本上涵蓋了整個(gè) Servlet 生命周期中,你感興趣的每種事件。這些 Listener 的實(shí)現(xiàn)類可以配置在 web.xml 中的 <listener> 標(biāo)簽中。當(dāng)然也可以在應(yīng)用程序中動(dòng)態(tài)添加 Listener,需要注意的是 ServletContextListener 在容器啟動(dòng)之后就不能再添加新的,因?yàn)樗O(jiān)聽的事件已經(jīng)不會(huì)再出現(xiàn)。掌握這些 Listener 的使用,能夠讓我們的程序設(shè)計(jì)的更加靈活。

Cookie與Session

Servlet 能夠給我們提供兩部分?jǐn)?shù)據(jù),一個(gè)是在 Servlet 初始化時(shí)調(diào)用 init 方法時(shí)設(shè)置的 ServletConfig,這個(gè)類基本上含有了 Servlet 本身和 Servlet 所運(yùn)行的 Servlet 容器中的基本信息。還有一部分?jǐn)?shù)據(jù)是由 ServletRequest 類提供,從提供的方法中發(fā)現(xiàn)主要是描述這次請(qǐng)求的 HTTP 協(xié)議的信息。關(guān)于這一塊還有一個(gè)讓很多人迷惑的 Session 與 Cookie。

Session 與 Cookie 的作用都是為了保持訪問用戶與后端服務(wù)器的交互狀態(tài)。它們有各自的優(yōu)點(diǎn)也有各自的缺陷。然而具有諷刺意味的是它們優(yōu)點(diǎn)和它們的使用場(chǎng)景又是矛盾的,例如使用 Cookie 來傳遞信息時(shí),隨著 Cookie 個(gè)數(shù)的增多和訪問量的增加,它占用的網(wǎng)絡(luò)帶寬也也會(huì)越來越大。所以大訪問量的時(shí)候希望用 Session,但是 Session 的致命弱點(diǎn)是不容易在多臺(tái)服務(wù)器之間共享,所以這也限制了 Session 的使用。

不管 Session 和 Cookie 有什么不足,我們還是要用它們。下面詳細(xì)講一下,Session 如何基于 Cookie 來工作。實(shí)際上有三種方式能可以讓 Session 正常工作:

  • 基于 URL Path Parameter,默認(rèn)就支持
  • 基于 Cookie,如果你沒有修改 Context 容器個(gè) cookies 標(biāo)識(shí)的話,默認(rèn)也是支持的
  • 基于 SSL,默認(rèn)不支持,只有 connector.getAttribute("SSLEnabled") 為 TRUE 時(shí)才支持

第一種情況下,當(dāng)瀏覽器不支持 Cookie 功能時(shí),瀏覽器會(huì)將用戶的 SessionCookieName 重寫到用戶請(qǐng)求的 URL 參數(shù)中,它的傳遞格式如:

 /path/Servlet?name=value&name2=value2&JSESSIONID=value3

接著 Request 根據(jù)這個(gè) JSESSIONID 參數(shù)拿到 Session ID 并設(shè)置到 request.setRequestedSessionId 中。

請(qǐng)注意如果客戶端也支持 Cookie 的話,Tomcat 仍然會(huì)解析 Cookie 中的 Session ID,并會(huì)覆蓋 URL 中的 Session ID。

如果是第三種情況的話將會(huì)根據(jù) javax.servlet.request.ssl_session 屬性值設(shè)置 Session ID。

有了 Session ID 服務(wù)器端就可以創(chuàng)建 HttpSession 對(duì)象了,第一次觸發(fā)是通過 request. getSession() 方法,如果當(dāng)前的 Session ID 還沒有對(duì)應(yīng)的 HttpSession 對(duì)象那么就創(chuàng)建一個(gè)新的,并將這個(gè)對(duì)象加到 org.apache.catalina. Manager 的 sessions 容器中保存,Manager 類將管理所有 Session 的生命周期,Session 過期將被回收,服務(wù)器關(guān)閉,Session 將被序列化到磁盤等。只要這個(gè) HttpSession 對(duì)象存在,用戶就可以根據(jù) Session ID 來獲取到這個(gè)對(duì)象,也就達(dá)到了狀態(tài)的保持。

Session相關(guān)類圖

上從圖中可以看出從 request.getSession 中獲取的 HttpSession 對(duì)象實(shí)際上是 StandardSession 對(duì)象的門面對(duì)象,這與前面的 Request 和 Servlet 是一樣的原理。下圖是 Session 工作的時(shí)序圖:

Session工作的時(shí)序圖

還有一點(diǎn)與 Session 關(guān)聯(lián)的 Cookie 與其它 Cookie 沒有什么不同,這個(gè)配置的配置可以通過 web.xml 中的 session-config 配置項(xiàng)來指定。

參考

WEB請(qǐng)求處理三:Servlet容器請(qǐng)求處理

Java Servlet 技術(shù)簡(jiǎn)介

Servlet 工作原理解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容