Servlet簡(jiǎn)介與源碼分析

前言

Servlet(即Server applet服務(wù)端小程序)是一個(gè)會(huì)在服務(wù)端被調(diào)用的Java程序,來(lái)處理請(qǐng)求。前面在分析Tomcat的時(shí)候我們知道,Tomcat本身包含了一個(gè)Servlet容器,用來(lái)存放Servlet實(shí)例,當(dāng)有請(qǐng)求到來(lái)時(shí)就會(huì)調(diào)用對(duì)應(yīng)的Servlet實(shí)例來(lái)處理。Servlet其實(shí)就是一個(gè)Java類,但如果它僅僅只是一個(gè)普通的Java類,Tomcat又如何知道該調(diào)用它的哪些方法呢?所以,我們?cè)O(shè)計(jì)了一個(gè)協(xié)議,或者說(shuō)約定。當(dāng)一個(gè)普通的Java類實(shí)現(xiàn)了某些約定的方法,就可以被看作是一個(gè)Servlet實(shí)例。因此Servlet更像是一座聯(lián)系服務(wù)器和服務(wù)端程序的橋梁。

Servlet的作用

Servlet 接口

Servlet接口里就給我們定義了一個(gè)合格Servlet類要實(shí)現(xiàn)哪些接口。

  • Servlet Interface
package javax.servlet;

import java.io.IOException;

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

以下給出了幾個(gè)主要方法的介紹

方法名 介紹
init() 當(dāng)一個(gè)Servlet類實(shí)例化的時(shí)候會(huì)被調(diào)用的邏輯
service() 對(duì)一個(gè)到來(lái)請(qǐng)求的具體處理邏輯
destory() 一個(gè)Servlet類實(shí)例被銷毀時(shí)(通常是在tomcat關(guān)閉時(shí))調(diào)用的邏輯

我們可以看到service()方法,Tomcat會(huì)把一個(gè)請(qǐng)求(注意這里請(qǐng)求并不一定是http請(qǐng)求,Servlet可以處理多種請(qǐng)求)包裝成ServletRequest類的實(shí)例傳入,service()處理完,我們?cè)侔烟幚斫Y(jié)果包裝成ServletResponse類的實(shí)例返回出去。理論上只要實(shí)現(xiàn)了這些接口,我們就可以構(gòu)造出一個(gè)Servlet了,但我們卻很少這么做,因?yàn)檫@個(gè)過(guò)程過(guò)于繁瑣,JavaEE已經(jīng)幫我們包裝好了一些實(shí)現(xiàn)了Servlet接口的類,我們使用的時(shí)候只要簡(jiǎn)單的重寫這些類中的某個(gè)方法就可以了。繼承這些類然后重寫一些方法就好了,比如對(duì)于處理http請(qǐng)求的Servlet,就提供了HttpServlet類供我們繼承,我們來(lái)看看這些類的繼承關(guān)系

類繼承關(guān)系

上圖我們可以看到,HttpServlet并沒(méi)有直接實(shí)現(xiàn)Servlet接口,而是繼承了GenericServlet類并在它的基礎(chǔ)上針對(duì)http進(jìn)行了定制化。我們看看GenericServlet做了什么。

  • GenericeServlet
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        return this.getServletConfig().getInitParameter(name);
    }

    public Enumeration<String> getInitParameterNames() {
        return this.getServletConfig().getInitParameterNames();
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }

    public String getServletInfo() {
        return "";
    }

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

    public void init() throws ServletException {
    }

    public void log(String message) {
        this.getServletContext().log(this.getServletName() + ": " + message);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        return this.config.getServletName();
    }
}

我們看到GenericServlet類只是簡(jiǎn)單的實(shí)現(xiàn)了3個(gè)借口中的方法,并沒(méi)有什么實(shí)質(zhì)性的代碼,那么GenericServlet這個(gè)抽象類的意義是什么?其實(shí)它更多的是提供一個(gè)模版,供子類去修改,而不是讓每個(gè)子類都去實(shí)現(xiàn)這些接口中的所有方法,避免重復(fù)勞動(dòng)。下面讓我們具體分析繼承了GenericServletHttpServlet做了什么。

  • HttpServlet類實(shí)現(xiàn)
    HttpServlet

HttpServlet依然是一個(gè)抽象類,提供了一個(gè)模版。我們可以看到其內(nèi)部重載定義了2個(gè)Service方法,我們先看最基礎(chǔ)的以ServletRequest為參數(shù)的service()方法。

  • public void service(ServletRequest req, ServletResponse res)
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }

        this.service(request, response);
    }

邏輯很簡(jiǎn)單,就是把請(qǐng)求和響應(yīng)都強(qiáng)制類型轉(zhuǎn)換成HttpServletRequestHttpServletResponse類型,再調(diào)用protected void service(HttpServletRequest req, HttpServletResponse resp)方法。若強(qiáng)制類型轉(zhuǎn)換失敗則說(shuō)明該請(qǐng)求不是一個(gè)http請(qǐng)求,直接拋出異常即可。

  • protected void service(HttpServletRequest req, HttpServletResponse resp)
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }
    }

這個(gè)service()方法很長(zhǎng),但是邏輯很簡(jiǎn)單,就是根據(jù)http請(qǐng)求中的方法去調(diào)用對(duì)應(yīng)該方法的處理邏輯,比如http請(qǐng)求是GET方法就調(diào)用doGet(),若是POST就調(diào)用doPost()。再讓我們看看假設(shè)http請(qǐng)求是GET方法,在doGet()中會(huì)發(fā)生什么。
-protected void doGet(HttpServletRequest req, HttpServletResponse resp)

    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(405, msg);
        } else {
            resp.sendError(400, msg);
        }
    }

它直接告訴我們這個(gè)方法還未實(shí)現(xiàn),然后返回錯(cuò)誤參數(shù)。所以doGet()、doPost()...等方法才是我們真正要實(shí)現(xiàn)的方法,來(lái)完成對(duì)應(yīng)請(qǐng)求的處理邏輯。(init()destory()也需要們實(shí)現(xiàn))

配置和使用Servlet

下面利用利用實(shí)現(xiàn)一個(gè)簡(jiǎn)單的web項(xiàng)目,來(lái)實(shí)踐Servlet,首先用IDEA創(chuàng)建一個(gè)默認(rèn)的web項(xiàng)目

  • 項(xiàng)目結(jié)構(gòu)


    項(xiàng)目結(jié)構(gòu)
  • src下編寫一個(gè)Servlet實(shí)例HelloServlet

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HelloServlet中doGet()方法被調(diào)用了!");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}
  • index.jsp里設(shè)置一個(gè)超鏈接來(lái)指向這個(gè)Servlet
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
      <a href="HelloServlet">點(diǎn)擊調(diào)用HelloServlet</a><br/>
  </body>
</html>

讓我們來(lái)運(yùn)行這個(gè)項(xiàng)目, 然后點(diǎn)擊這個(gè)超鏈接,但卻給我們報(bào)了一個(gè)404錯(cuò)誤。我們之前說(shuō)了tomcat內(nèi)部有一個(gè)Servlet容器。就像配置Spring IOC容器一樣,我們需要通過(guò)配置的方式告訴tomcat我們這個(gè)web應(yīng)用里有哪些Servlet,還要告訴tomcat當(dāng)訪問(wèn)哪些路徑時(shí),調(diào)用這個(gè)Servlet實(shí)例。配置以上信息的方式有2種,web.xml和注解的方式,我們先嘗試使用xml的方式。

  • 修改web.xml配置servlet
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/HelloServlet</url-pattern>
    </servlet-mapping>

這里有2部分組成, <servlet></servlet>表示注冊(cè)一個(gè)Servlet實(shí)例到tomcatservlet容器中,類的名字叫HelloServlet,并給他取了一個(gè)別名叫hello。第二部分 <servlet-mapping></servlet-mapping>則說(shuō)明了一個(gè)url和對(duì)應(yīng)servlet實(shí)例的映射關(guān)系。表示當(dāng)訪問(wèn)localhost:8080/webstudy/HelloServlet路徑時(shí),會(huì)去調(diào)用一個(gè)別名為helloServlet實(shí)例對(duì)象的相應(yīng)方法。配置好后讓我們實(shí)驗(yàn)一下。

Connected to server
[2020-04-16 09:47:09,326] Artifact webstudy:war exploded: Artifact is being deployed, please wait...
[2020-04-16 09:47:09,601] Artifact webstudy:war exploded: Artifact is deployed successfully
[2020-04-16 09:47:09,601] Artifact webstudy:war exploded: Deploy took 275 milliseconds
HelloServlet中doGet()方法被調(diào)用了!

因?yàn)槌溄邮且粋€(gè)GET方法,所以這里HelloServletdeGet()方法被調(diào)用了,配置生效。

  • 利用注解的方式配置Servlet
    Servlet3.0之后開始支持注解的的方式配置Servlet,編寫一個(gè)WelcomeServlet類,并用注解的方式把它配置到tomcat中去。
@WebServlet("/WelcomeServlet")
public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("WelcomeServlet的doGet()方法被調(diào)用了");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

@WebServlet("/WelcomeServlet")表示當(dāng)訪問(wèn)localhost:8080/webstudy/WelcomeServlet時(shí),會(huì)調(diào)用這個(gè)對(duì)應(yīng)的Servlet中的對(duì)應(yīng)方法。讓我們添加一個(gè)鏈接到index.jsp中去來(lái)測(cè)試該配置。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
    <a href="HelloServlet">點(diǎn)擊調(diào)用HelloServlet</a><br/>
    <a href="WelcomeServlet">點(diǎn)擊調(diào)用WelcomeServlet</a><br/>
</body>
</html>

運(yùn)行,并點(diǎn)擊點(diǎn)擊調(diào)用WelcomeServlet這個(gè)鏈接,后臺(tái)打印信息如下。

WelcomeServlet的doGet()方法被調(diào)用了

說(shuō)明該配置方法也是生效的。

Servlet生命周期

我們之前提到過(guò)Servlet提供了3個(gè)主要方法,HttpServlet重點(diǎn)重寫了service()方法,而init()destroy()方法并沒(méi)有重寫,GenericHttp中也只是對(duì)這2個(gè)方法給出了空實(shí)現(xiàn)。我們知道,service()方法會(huì)在每個(gè)請(qǐng)求到來(lái)的時(shí)候被調(diào)用,來(lái)處理請(qǐng)求。那么這init()destroy()個(gè)方法會(huì)在什么時(shí)候調(diào)用呢,讓我們來(lái)實(shí)驗(yàn)一下。

  • 修改HelloServlet
public class HelloServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("HelloServlet的init()方法被調(diào)用了");
    }

    @Override
    public void destroy() {
        System.out.println("HelloServlet的destory()方法被調(diào)用了");
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HelloServlet中doGet()方法被調(diào)用了!");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

部署運(yùn)行該項(xiàng)目, 觀察控制臺(tái)的輸出

Connected to server
[2020-04-16 11:22:40,748] Artifact webstudy:war exploded: Artifact is being deployed, please wait...
[2020-04-16 11:22:40,973] Artifact webstudy:war exploded: Artifact is deployed successfully
[2020-04-16 11:22:40,973] Artifact webstudy:war exploded: Deploy took 225 milliseconds
16-Apr-2020 23:22:50.448 信息 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployDirectory 把web 應(yīng)用程序部署到目錄 [/Users/LENN/tomcat/apache-tomcat-9.0.34/webapps/manager]
16-Apr-2020 23:22:50.478 信息 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/Users/LENN/tomcat/apache-tomcat-9.0.34/webapps/manager] has finished in [30] ms
HelloServlet的init()方法被調(diào)用了
HelloServlet中doGet()方法被調(diào)用了!

我們發(fā)現(xiàn)當(dāng)tomcat啟動(dòng)的時(shí)候,init()方法并沒(méi)有被調(diào)用,也就說(shuō)HelloServlet并沒(méi)有在tomcatservlet容器中生成一個(gè)實(shí)例對(duì)象,而是直到第一次調(diào)用HelloServlet中的doGet()方法時(shí),這個(gè)Servlet才被實(shí)例化并加入tomcat容器中去,此時(shí)init()方法才被調(diào)用。是一種懶加載的思想。那么有沒(méi)有什么辦法,讓tomcat能夠在啟動(dòng)的時(shí)候就把這些Servlet實(shí)例化然后加載到容器中去呢?我們可以在web.xml添加如下標(biāo)簽。

  • 修改web.xmlHelloServlettomcat啟動(dòng)時(shí)就加載到容器中去
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>HelloServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/HelloServlet</url-pattern>
    </servlet-mapping>

<load-on-startup>1</load-on-startup>表示在tomcat啟動(dòng)時(shí)就實(shí)例化并加載到tomcatservlet容器里,中間的數(shù)字可以指明多個(gè)Servlet的加載順序,現(xiàn)在讓我們重新啟動(dòng)tomcat。

  • 啟動(dòng)時(shí)init()就被調(diào)用
Connected to server
[2020-04-16 11:38:31,671] Artifact webstudy:war exploded: Artifact is being deployed, please wait...
HelloServlet的init()方法被調(diào)用了
[2020-04-16 11:38:31,913] Artifact webstudy:war exploded: Artifact is deployed successfully
[2020-04-16 11:38:31,914] Artifact webstudy:war exploded: Deploy took 243 milliseconds

ServletConfig類

我們一直重寫的init()方法都是無(wú)參數(shù)的,和Servlet接口中定義的同名方法并不一樣,我們來(lái)看看Servlet接口中是如何定義的。

  • Servlet接口中定義的init()方法
    void init(ServletConfig var1) throws ServletException;

我們可以看到這里有一個(gè)ServletConfig類型的形參。我們一直重寫的無(wú)參init()方法實(shí)際上由GenericServlet提供。

  • GenericServlet中的init()
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    public void init() throws ServletException {
    }

GenericServlet自動(dòng)的幫我們把傳入的ServletConfig保存進(jìn)一個(gè)類變量里,然后再調(diào)用我們重寫的無(wú)參init()的方法。顯然這個(gè)ServletConfig類型的變量是由tomcat生成并傳入進(jìn)來(lái)的,如同它的名字一樣,代表了一個(gè)Servlet的配置。那么如何使用這個(gè)類呢。再看看GenericServlet中的其他方法。

  • GenericServlet
    public ServletConfig getServletConfig() {
        return this.config;
    }
    public String getInitParameter(String name) {
        return this.getServletConfig().getInitParameter(name);
    }

發(fā)現(xiàn)我們可以利用getInitParameter(String name)來(lái)獲得一個(gè)Servlet的初始參數(shù)配置,而這些配置則是以鍵值對(duì)的形式存在的,我們可以在web.xml定義這些和Servlet配置有關(guān)的鍵值對(duì)。

  • web.xml定義InitParameter
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>HelloServlet</servlet-class>
        <init-param>
            <param-name>servletParam</param-name>
            <param-value>servletValue</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

在些init-param的作用范圍是一個(gè)Servlet內(nèi)部,調(diào)用這個(gè)Servlet的所有請(qǐng)求都可以拿到這些init-param,讓我們做個(gè)實(shí)驗(yàn)。

  • 修改HelloServlet獲取init-param
public class HelloServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("HelloServlet的init()方法被調(diào)用了");
        System.out.println(super.getInitParameter("servletParam"));
    }
}

運(yùn)行tomcat可以在控制臺(tái)看到如下信息

Connected to server
[2020-04-16 11:46:10,610] Artifact webstudy:war exploded: Artifact is being deployed, please wait...
HelloServlet的init()方法被調(diào)用了
servletValue

我們成功獲得了鍵servletParam對(duì)應(yīng)的值servletValue。
除了在web.xml配置一個(gè)Servletinit-param,我們還可以用注解的方式定義。

  • 利用注解定義init-param
@WebServlet(value = "/WelcomeServlet", initParams = {@WebInitParam(name = "servletParam", value = "servletValue")})
public class WelcomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("WelcomeServlet的doGet()方法被調(diào)用了");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

ServletContext

ServletConfig代表了一個(gè)Servlet的配置信息,其中主要包含了很多初始化參數(shù)。ServletContextServletConfig相似,只不過(guò)它的作用范圍是整個(gè)tomcatServlet容器,也就是說(shuō)所有的Servlet實(shí)例都可以訪問(wèn)到ServletConfig中的信息。GenericServlet提供了一個(gè)方法可以讓我們拿到這個(gè)類的實(shí)例

   public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }

可以看到ServletContexttomcat包裝進(jìn)每一個(gè)ServletServletConfig中,再在初始化的時(shí)候傳入,從而可以共享這些信息。我們可以在web.xml中定義這些信息。

  • 定義context-param
    <context-param>
        <param-name>globalParam</param-name>
        <param-value>globalValue</param-value>
    </context-param>

  • 獲取這些context-param
public class HelloServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("HelloServlet的init()方法被調(diào)用了");
        System.out.println(super.getInitParameter("servletParam"));
        ServletContext servletContext = super.getServletContext();
        System.out.println(servletContext.getInitParameter("globalParam"));
    }
}

啟動(dòng)tomcat,觀察控制臺(tái)

Connected to server
[2020-04-17 02:21:40,825] Artifact webstudy:war exploded: Artifact is being deployed, please wait...
HelloServlet的init()方法被調(diào)用了
servletValue
globalValue

成功獲得context-param

最后編輯于
?著作權(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)容

  • Java Servlet是運(yùn)行在服務(wù)器或應(yīng)用服務(wù)器上的程序,它是作為來(lái)自Web瀏覽器的請(qǐng)求或其他HTTP客戶端的請(qǐng)...
    殘夢(mèng)Tenderness閱讀 900評(píng)論 0 1
  • servlet接口是Java servlet API的核心抽象。所有的servlet都直接實(shí)現(xiàn)此接口或者更通常是繼...
    0x70e8閱讀 654評(píng)論 0 3
  • 一、Servlet 概述 Servlet 類是 JavaWeb 的三大組件之一,它屬于動(dòng)態(tài)資源。其作用是處理請(qǐng)求,...
    周二鴨閱讀 1,822評(píng)論 1 4
  • 一 servlet概述 狹義的Servlet指javax.servlet包中的一個(gè)接口,而廣義的Servlet則是...
    靜慎獨(dú)閱讀 585評(píng)論 0 0
  • 知道你的足跡將越走越遠(yuǎn) 我的心 多少有些孤單 給祥子的標(biāo)題 給了你 希望,你的行程改簽 在我這里 思念變幻著不同的...
    江城妖怪閱讀 220評(píng)論 0 4

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