AJAX入門這一篇就夠了

Java3y發表於2018-02-13

什麼是Ajax

Ajax(Asynchronous JavaScript and XML) 非同步JavaScript和XML

Ajax實際上是下面這幾種技術的融合:

  • (1)XHTML和CSS的基於標準的表示技術
  • (2)DOM進行動態顯示和互動
  • (3)XML和XSLT進行資料交換和處理
  • (4)XMLHttpRequest進行非同步資料檢索
  • (5)Javascript將以上技術融合在一起

客戶端與伺服器,可以在【不必重新整理整個瀏覽器】的情況下,與伺服器進行非同步通訊的技術

為什麼我們需要Ajax?

在我們之前的開發,每當使用者向伺服器傳送請求,哪怕只是需要更新一點點的區域性內容,伺服器都會將整個頁面進行重新整理。

  • 效能會有所降低(一點內容,重新整理整個頁面!)
  • 使用者的操作頁面會中斷(整個頁面被重新整理了)

Ajax就是能夠做到區域性重新整理

這裡寫圖片描述


XMLHttpRequest

XMLHttpRequest物件是Ajax中最重要的一個物件使用Ajax更多的是編寫客戶端程式碼,而不是服務端的程式碼。

##XMLHttpRequest 工作原理##

傳統的web前端與後端的互動中,瀏覽器直接訪問Tomcat的Servlet來獲取資料。Servlet通過轉發把資料傳送給瀏覽器。

當我們使用AJAX之後,瀏覽器是先把請求傳送到XMLHttpRequest非同步物件之中,非同步物件對請求進行封裝,然後再與傳送給伺服器。伺服器並不是以轉發的方式響應,而是以流的方式把資料返回給瀏覽器

XMLHttpRequest非同步物件會不停監聽伺服器狀態的變化,得到伺服器返回的資料,就寫到瀏覽器上【因為不是轉發的方式,所以是無重新整理就能夠獲取伺服器端的資料】

這裡寫圖片描述


建立XMLHttpRequest物件

要建立XMLHttpRequest物件是要分兩種情況考慮的:

  • 在IE6以下的版本
  • 在IE6以上的版本以及其他核心的瀏覽器(Mozilla)等

    <script type="text/javascript">

        var httpRequest;

        if(window.XMLHttpRequest) {

            //在IE6以上的版本以及其他核心的瀏覽器(Mozilla)等
            httpRequest = new XMLHttpRequest();
        }else if(window.ActiveXObject) {

            //在IE6以下的版本
            httpRequest = new ActiveXObject();
        }


    </script>
複製程式碼

瞭解XMLHttpRequest物件的屬性和方法

方法

  • open()(String method,String url,boolean asynch,String username,String password)
  • send(content)
  • setRequestHeader(String header,String value)
  • getAllResponseHeaders()
  • getResponseHeader(String header)
  • abort()

常用的方法就是黑色粗體的前三個

  • open():該方法建立http請求
    • 第一個引數是指定提交方式(post、get)
    • 第二個引數是指定要提交的地址是哪
    • 第三個引數是指定是非同步還是同步(true表示非同步,false表示同步)
    • 第四和第五引數在http認證的時候會用到。是可選的
  • setRequestHeader(String header,String value):設定訊息頭(使用post方式才會使用到,get方法並不需要呼叫該方法)
    • xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  • send(content):傳送請求給伺服器
    • 如果是get方式,並不需要填寫引數,或填寫null
    • 如果是post方式,把要提交的引數寫上去

屬性

  • onreadystatechange:請求狀態改變的事件觸發器(readyState變化時會呼叫此方法),一般用於指定回撥函式
  • readyState:請求狀態readyState一改變,回撥函式被呼叫,它有5個狀態
    • 0:未初始化
    • 1:open方法成功呼叫以後
    • 2:伺服器已經應答客戶端的請求
    • 3:互動中。Http頭資訊已經接收,響應資料尚未接收。
    • 4:完成。資料接收完成

這裡寫圖片描述

  • responseText:伺服器返回的文字內容
  • responseXML:伺服器返回的相容DOM的XML內容
  • status:伺服器返回的狀態碼
  • statusText:伺服器返回狀態碼的文字資訊

上面有兩個地方都提及了回撥函式,回撥函式是什麼??

回撥函式就是接收伺服器返回的內容!

這裡寫圖片描述


編寫第一個Ajax程式

檢測使用者輸入的使用者名稱是否為"zhongfucheng",只要不是zhongfucheng,就可以使用!

html程式碼

  • 建立的div只要用於顯示伺服器返回的資料
  • 當使用者點選按鈕的時候,就觸發事件。

	<input type="text" id="username">
	<input type="button" onclick="checkUsername()" value="檢測使用者名稱是否合法">
	<div id="result">
	
	</div>

複製程式碼

JavaScript程式碼

  • 建立XMLHttpRequest物件
  • 建立http請求
  • 把文字框的資料傳送給http請求的目標
  • 指定回撥函式
  • 編寫回撥函式
  • 傳送http請求
  • 回撥函式得到http返回的內容,把內容寫在div上


    <script type="text/javascript">

        var httpRequest;
        function checkUsername() {

            if(window.XMLHttpRequest) {

                //在IE6以上的版本以及其他核心的瀏覽器(Mozilla)等
                httpRequest = new XMLHttpRequest();
            }else if(window.ActiveXObject) {

                //在IE6以下的版本
                httpRequest = new ActiveXObject();
            }


            //建立http請求
            httpRequest.open("POST", "Servlet1", true);

            //因為我使用的是post方式,所以需要設定訊息頭
            httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

            //指定回撥函式
            httpRequest.onreadystatechange = response22;

            //得到文字框的資料
            var name = document.getElementById("username").value;

            //傳送http請求,把要檢測的使用者名稱傳遞進去
            httpRequest.send("username=" + name);

        }

        function response22() {

            //判斷請求狀態碼是否是4【資料接收完成】
            if(httpRequest.readyState==4) {

                //再判斷狀態碼是否為200【200是成功的】
                if(httpRequest.status==200) {

                    //得到服務端返回的文字資料
                    var text = httpRequest.responseText;

                    //把服務端返回的資料寫在div上
                    var div = document.getElementById("result");
                    div.innerText = text;
                }

            }
        }
    </script>

複製程式碼

效果

實現了區域性更新,不需要重新整理整一個頁面

這裡寫圖片描述


XMLHttpRequest解決中文亂碼

在傳統的Web中我們已經解決過中文亂碼問題了。

  • 伺服器傳送給瀏覽器資料發生亂碼:response設定編碼的時候和瀏覽器頁面的編碼一致便可以解決
  • 瀏覽器傳送給伺服器資料發生亂碼:如果是post方式,request設定編碼便可以解決。如果是get方式,Tomcat下,使用ISO8859-1編碼得到原本的二進位制陣列,再使用UTF-8編碼便可以解決

接下來,要介紹的是:我們可以遮蔽任何瀏覽器和任何伺服器的編碼格式,瀏覽器傳送給伺服器的資料不造成亂碼問題!

具體我們是這樣做的:

  • 傳送資料給伺服器的時候,JavaScript使用兩次EncodeURI()
  • 伺服器得到資料,使用URLEncode.decode(資料,"utf-8")進行解碼

為啥我能說這種方式遮蔽任何瀏覽器和伺服器的編碼格式,都不會亂碼呢??

這裡寫圖片描述


XMLHttpRequest解決快取問題

在傳統的Web中我們也解決過快取的問題,通過設定response的頭資訊,返回給瀏覽器就可以實現不快取頁面了。

但是呢,現在我們使用XMLHttpRequest,拿到的不是全新的頁面,僅僅是伺服器端傳送過來的資料!!

那我們要怎麼解決快取的問題呢??產生快取的原因就是:我們請求了同一個地址,做了相同的操作。服務端認為我的操作並沒有什麼變化,就直接把快取的資訊給我了。這樣的話,我就不能更換驗證碼圖片了(等等應用)。

我們可以這樣做:

  • 在每次請求url中加入一個時間戳引數【每次url就不一樣了】
  • 加入時間戳引數到url時,也分兩種情況
    • url本身就帶有引數了,也就是說有"?"號了,那麼新增時間戳的時候就需要用"&"號
    • [x] url沒有引數,直接使用"?"號來新增時間戳

	if(url.indexOf("?") >= 0){
	url = url + "&t=" + (new Date()).valueOf();
	} else{
	url = url + "?t=" + (new Date()).valueOf();
	}

複製程式碼

XMLHttpRequest跨域訪問

使用XMLHttpRequest去跨域訪問是會出現錯誤的

這裡寫圖片描述

我們要怎麼解決呢??這時候就要用代理思想了

  • XMLHttpRequest先把請求提交給同域的Servlet處理
  • 同域Servlet再將XMLHttpRequest的請求提交給跨域的伺服器
  • 同域Servlet得到跨域伺服器的返回值,再返回給XMLHttpRequest

這裡寫圖片描述

這個時候,XMLHttpRequest跨域訪問就分兩種(GET和POST)情況了,因為這兩種提交資料的方式是不一樣的!

瀏覽器程式碼

  • 我們需要在呼叫open方法之前判斷一下要連線的地址是不是以http開頭的,如果是則認為要訪問的是跨域的資源
  • 首先將當前url中的”?”變成”&”,這是因為將要連線的地址改為”Proxy?url=” + url以後,如果原來url地址中有引數的話,新的url地址中就會有兩個“?”這會導致伺服器端解析引數錯誤,”url=”之後的內容表示本來要訪問的跨域資源的地址。

GET方式

GET方式是直接把引數的資訊都放在url地址上,所以處理起來會相對簡單。

步驟:

  • 使用StringBuilder裝載著getParameter("url")【獲取得到地址,呆會要做拼接,所以用StringBuilder】
  • **得到其他引數的時候,做URLEncode.encode(),因為我們進入Servlet的時候已經被decode了一次【我們要儘可能保留原始請求】(參照解決中文亂碼) **
  • 遍歷所有的請求引數,只要名字不是"url",就新增到StringBuilder中【第一個引數為"?",其他的引數為"&"】(http://localhost:8080/url?aa=bb&cc=dd)
  • 建立URL物件,把拼接成的StringBuilder傳遞進去
  • 使用BufferReader讀取遠端伺服器返回的資料,要指定輸入流編碼格式,否則會亂碼

	BufferedReader reader = new BufferedReader(new InputStreamReader(URL物件.openSteam(),"UTF-8"));

複製程式碼
  • 最後,把遠端伺服器讀取到的資料再返回給XMLHttpRequest

POST方式

POST方式把引數的資訊都封裝到HTTP請求中,在URL進行連線的時候,需要把資料寫給遠端伺服器

步驟:

  • 得到url引數,建立StringBuilder
  • **得到其他引數的時候,做URLEncode.encode(),因為我們進入Servlet的時候已經被decode了一次【我們要儘可能保留原始請求】(參照解決中文亂碼) **
  • 遍歷所有的請求引數,只要名字不是"url",就新增到StringBuilder中【第一個引數直接給出,其他的引數為"&"】(aa=bb&cc=dd&ee=ff)
  • 建立URL物件,建立URL聯結器,允許寫資料到遠端伺服器上
	URL url = new URL(url);
	URLConnection connection = url.openConnection;
	connection.setDoOutPut(true);


複製程式碼
  • 得到寫資料流

	OutputSteamWriter writer = new OutputSteamWriter(conncetion.getOutputSteam)

複製程式碼
  • 把StringBuilder的資料寫到遠端伺服器上,並flush
  • 使用BufferReader讀取遠端伺服器返回的資料

	BufferedReader reader =  new BufferedReader(new InputSteamReader(conncetion.inputSteamReader,"UTF-8"));

複製程式碼

AJAX二級下拉聯動案例【XML版】

我們在購物的時候,常常需要我們來選擇自己的收貨地址,先選擇省份,再選擇城市...

有沒有發現:當我們選擇完省份的時候,出現的城市全部都是根據省份來給我們選擇的。這是怎麼做到的呢???其實就是通過AJAX來完成的。使用AJAX技術讓我們看起來網頁非常“智慧”,會根據省份來給出對應的城市資訊。

這裡寫圖片描述

我們這裡就不讀取資料庫了,直接在Servlet寫死資料來進行模擬測試


分析

我們知道AJAX與伺服器之間的互動常用的傳輸載體格式有三種:

  • HTML
  • XML
  • JSON

由於省份與城市是有層級關係的,因此我們只能用XML或者JSON

我們這裡首先就用XML來進行,後面會使用JSON,來看看他倆有什麼不同的地方。。

前臺分析

當使用者選擇了某個省份之後,就使用AJAX與伺服器進行互動,那麼在選擇城市的時候就出現對應的城市資訊。

  • 監聽下拉框值變化事件
  • 只要下拉框值變化了,就與伺服器進行互動
  • 得到伺服器返回的值,解析XML
  • 使用DOM把資料寫到城市下拉框列表中

後臺分析

  • 得到前臺帶過來的資料
  • 判斷該資料是什麼,返回對應的的XML檔案

寫JSP頁面


<%--
  Created by IntelliJ IDEA.
  User: ozc
  Date: 2017/5/17
  Time: 19:38
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>多級聯動</title>
    <script type="text/javascript" src="js/ajax.js"></script>
</head>
<body>

<%--############前臺頁面###################--%>
<select name="province" id="provinceId">
    <option value="-1">請選擇省份</option>
    <option>廣東</option>
    <option>湖南</option>
</select>
<select name="city" id="cityId">
    <option>請選擇城市</option>
</select>

<%--############AJAX###################--%>

<script type="text/javascript">

    document.getElementById("provinceId").onchange = function () {


        /**********定位到下拉框,獲取下拉框的值***************/
        // 獲取選中的下拉框索引值
        var index = this.selectedIndex;
        // 得到下拉框的值
        var province = this.options[index].innerHTML;

        //下拉框的值要是“請選擇”,那麼我們是不會訪問伺服器的
        if ("請選擇省份" != province) {

            /********由於每次都會自動新增,因此每次在呼叫的時候清除***********/
            var citySelect = document.getElementById("cityId");

            //每次都將option變成長度只有1的
            citySelect.options.length = 1;

            /*************ajax程式碼*********************/
            //建立AJAX物件
            var ajax = createAJAX();
            //準備傳送請求
            var method = "post";
            var url = "${pageContext.request.contextPath}/ProvinceServlet?time=" + new Date().getTime();
            ajax.open(method, url);
            //由於是POST方式,因此要設定頭
            ajax.setRequestHeader("content-type", "application/x-www-form-urlencoded");

            ajax.send("province=" + province);

            /************ajax回撥函式*********************/
            ajax.onreadystatechange = function () {

                if (ajax.readyState == 4) {
                    if (ajax.status == 200) {

                        //得到伺服器端帶過來的XML
                        var XMLDocument = ajax.responseXML;

                        /**********解析XML文件,使用DOM寫到下拉框中****************/
                        var cities = XMLDocument.getElementsByTagName("city");

                        //得到每一個cities節點的值,動態生成下拉框,新增到下拉框中
                        for (var i = 0; i < cities.length; i++) {
                            var value = cities[i].firstChild.nodeValue;
                            //動態生成下拉框
                            var optionElement = document.createElement("option");
                            optionElement.innerHTML = value;

                            //新增到下拉框中
                            citySelect.appendChild(optionElement);

                        }
                    }
                }
            };

        }

    };

</script>


</body>
</html>

複製程式碼

Servlet


import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by ozc on 2017/5/17.
 */
@javax.servlet.annotation.WebServlet(name = "ProvinceServlet",urlPatterns = "/ProvinceServlet")
public class ProvinceServlet extends javax.servlet.http.HttpServlet {
    protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {

        //設定中文編碼
        request.setCharacterEncoding("UTF-8");
        String province = request.getParameter("province");

        //這裡是返回的是XML,因此指定XML資料!
        response.setContentType("text/xml;charset=UTF-8");

        PrintWriter printWriter = response.getWriter();

        /****************返回XML檔案給前臺**************/
        printWriter.write("<?xml version='1.0' encoding='UTF-8'?>");
        printWriter.write("<root>");
        if("廣東".equals(province)){
            printWriter.write("<city>廣州</city>");
            printWriter.write("<city>深圳</city>");
            printWriter.write("<city>中山</city>");
        }else if("湖南".equals(province)){
            printWriter.write("<city>長沙</city>");
            printWriter.write("<city>株洲</city>");
            printWriter.write("<city>湘潭</city>");
            printWriter.write("<city>岳陽</city>");
        }
        printWriter.write("</root>");

        System.out.println("1111");


        /*******事後操作*******/
        printWriter.flush();
        printWriter.close();


    }

    protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {

        this.doPost(request, response);
    }
}


複製程式碼

效果:

這裡寫圖片描述

XML方式總結

  • 監聽下拉框的變化,如果變化了,那麼就使用非同步操作去訪問伺服器,得到對應的資料返回給非同步物件
  • 非同步物件解析伺服器帶過來的資料,使用DOM程式設計把資料動態新增到頁面上
    • 在Servlet上記得要指定返回的是XML的資料!
    • 在前臺解析XML文件的時候,不能直接使用innerHtml來得到節點的值,只能通過firstChild.nodeValue的方式獲取。
    • 由於每次append到下拉框都會連續append,因此在響應事件的時候,把下拉框清零
    • 把下拉框options的長度賦值為1,那麼就是清零的操作了

AJAX二級下拉聯動案例【JSON版】

前面我們已經使用過了XML作為資料載體在AJAX中與伺服器進行互動。當時候我們的案例是二級聯動,使用Servlet進行控制

這次我們使用JSON作為資料載體在AJAX與伺服器互動,使用三級聯動,使用Action進行控制....

  • 省份-城市-區域三級聯動【Struts2 + JSON版】

分析

與上次是一樣的,只不過這次換了用JSON,使用Action控制罷了...

監聽下拉框的變動,使用非同步物件與伺服器進行互動。

前臺分析

  • 監聽下拉框的變動
  • 得到伺服器返回的JSON資料
  • 使用eval()進行解析,得到具體的物件
  • 使用DOM程式設計把資料填充到對應的下拉框上

後臺分析

  • 得到前臺傳送過來的資料
  • 判斷具體的資料是什麼,給出對應的資料
  • 使用Struts2提供的元件把資料封裝成JSON
  • 返回給瀏覽器

監聽省份JSP頁面


<%--
  Created by IntelliJ IDEA.
  User: ozc
  Date: 2017/5/18
  Time: 13:36
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>使用JSON資料載體與伺服器進行互動</title>

      <script type="text/javascript" src="js/ajax.js"></script>
  </head>
  <body>


  <%--############前臺頁面##############################--%>
  <select name="province" id="provinceId">
    <option>請選擇省份</option>
    <option>廣東</option>
    <option>北京</option>
  </select>

  <select name="city" id="cityId">
    <option>請選擇城市</option>
  </select>


  <select name="area" id="areaId">
    <option>請選擇區域</option>
  </select>

  <%--############監聽省份##############################--%>
  <script type="text/javascript">

      document.getElementById("provinceId").onchange= function () {

          // 得到選中的下拉框的值
          var provinceValue = this.options[this.selectedIndex].innerHTML;


          /***************ajax程式碼*************************/
          if("請選擇省份" != provinceValue) {

              //每次訪問的時候,都要清空select的值
              var citySelect = document.getElementById("cityId");
              citySelect.options.length = 1;

              var ajax = createAJAX();
              var method = "post";
              var url = "${pageContext.request.contextPath}/province_findCityByProvince?time=" + new Date().getTime();
              ajax.open(method, url);
              ajax.setRequestHeader("content-type", "application/x-www-form-urlencoded");

              //顧及到傳送的key、value值有很多,於是我們使用物件吧。
              ajax.send("bean.name=" + provinceValue);

              /***************等待伺服器的響應,得到伺服器返回的資料************************/
              ajax.onreadystatechange = function () {

                  if(ajax.readyState==4) {
                      if(ajax.status==200) {
                        var jsonJava = ajax.responseText;

                        //解析成是JS型別的JSON
                        var json = eval("(" + jsonJava + ")");

                        //得到每個城市的值
                        for(var i=0;i<json.city.length;i++) {
                          var city = json.city[i];

                          //動態建立option控制元件
                          var option = document.createElement("option");
                          option.innerHTML = city;

                          citySelect.appendChild(option);
                        }
                      }
                  }
              };

          }

      };
    
  </script>

  </body>
</html>

複製程式碼

監聽省份Action

要想Struts2能夠把Action的資料封裝成JSON,就需要匯入Struts2的開發包

  • struts2-json-plugin-2.3.4.1.jar

這裡寫圖片描述

在Action中對應的成員屬性需要給getter方法


import com.opensymphony.xwork2.ActionSupport;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by ozc on 2017/5/18.
 */
public class ProvinceAction  extends ActionSupport{

    //自動封裝資料
    private Bean bean;
    public Bean getBean() {
        return bean;
    }
    public void setBean(Bean bean) {
        this.bean = bean;
    }

    //封裝城市的集合
    private List<String> city = new ArrayList<>();
    public List<String> getCity() {
        return city;
    }

    public String findCityByProvince() throws Exception {

        if ("廣東".equals(bean.getName())) {
            city.add("廣州");
            city.add("珠海");
            city.add("從化");
        } else if ("北京".equals(bean.getName())) {
            city.add("一環");
            city.add("二環");
            city.add("三環");
            city.add("四環");

        } else {
            System.out.println("沒有你選擇的地區");

        }
        return "ok";
    }
}

複製程式碼

返回給前端的時候,資料是這樣子的:

這裡寫圖片描述


效果

這裡寫圖片描述


監聽城市JSP


  <%--############監聽城市##############################--%>

  <script type="text/javascript">
      document.getElementById("cityId").onchange = function () {

          //清空值
          var areaSelect = document.getElementById("areaId");
          areaSelect.options.length = 1;

          //得到選擇選中的下拉框的值
          var cityValue = this.options[this.selectedIndex].innerHTML;
          if(cityValue!="請選擇城市"){

              var ajax = createAJAX();
              var method = "post";
              var url = "${pageContext.request.contextPath}/province_findAreaByCity?time=" + new Date().getTime();
              ajax.open(method, url);
              ajax.setRequestHeader("content-type", "application/x-www-form-urlencoded");

              //顧及到傳送的key、value值有很多,於是我們使用物件吧。
              ajax.send("bean.name=" + cityValue);

              /***************等待伺服器的響應,得到伺服器返回的資料************************/
              ajax.onreadystatechange = function () {

                  if(ajax.readyState==4) {
                      if(ajax.status==200) {

                          var jsonJava = ajax.responseText;

                          var json = eval("(" + jsonJava + ")");

                          //得到每個地區的值
                          for(var i=0;i<json.area.length;i++) {
                              var area = json.area[i];

                              //動態建立option控制元件
                              var option = document.createElement("option");
                              option.innerHTML = area;

                              areaSelect.appendChild(option);
                          }

                      }
                  }
              }

          };
      };

  </script>


複製程式碼

Action頁面


    public String findAreaByCity() throws Exception {

        if ("廣州".equals(bean.getName())) {
            area.add("白雲區");
            area.add("黃浦區");
            area.add("蘿崗區");
        } else if ("珠海".equals(bean.getName())) {
            area.add("香江");
            area.add("拱北");
            area.add("EE");
            area.add("xx");
        } else {
            System.out.println("沒有你選擇的地區");

        }
        return "ok";
    }
複製程式碼

最終效果:

這裡寫圖片描述

總結

這次使用的是JSON作為資料載體與伺服器進行互動,和XML本質上是沒有區別的。

只不過JSON是更加輕量級文字資料,在JavaScript能夠方便地獲取返回的資料

  • 在Struts2中把Action資料封裝成JSON格式,返回給非同步物件
    • 需要匯入jar包
    • 在配置檔案中配置繼承json包
    • 返回的型別是json
  • 如果使用POST時,傳送的key、vaulue太多的話,我們可以使用bean進行封裝
  • 當選中省份時,把城市和區域的下拉框清空,當選擇城市時,把區域的下拉框清空

#總結圖#

這裡寫圖片描述

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章的同學,可以關注微信公眾號:Java3y

相關文章