Tomcat深入淺出——Servlet(三)

Meteor發表於2022-07-07

零、HttpServletRequest

  • 上一篇已經介紹了這個介面,現在補充些內容
    首先介紹一下作用域:
  • jakarta.servlet.jsp.PageContext pageContext 頁面作用域
  • jakarta.servlet.http.HttpServletRequest request 請求域
  • jakarta.servlet.http.HttpSession session 會話域
  • jakarta.servlet.ServletContext application 應用域
  • 作用域從小到大 pageContext-> request -> session -> application
  • 宣告一下這裡的jakarta是Tomcat10的說法,已經被收購了。

如何獲取前端傳來的資料:

String getParameter(String name) // 獲取value中第一個元素(最常用)
Map<String,String[]> getParameterMap() // 獲取鍵值對的整個集合
Enumeration<String> getParameterNames() // 獲取所有鍵key
String[] getParameterValues(java.lang.String name) // 通過key獲取值

轉發和重定向:

  • 轉發

    • //轉發都是一次請求
      //因為他們用的都是當前的Servlet
      //轉發以後它的地址仍然不變
      //我們每次轉發也可以把頁面的資料帶過去放到作用域裡
      request.setAttribute("user",user);
      //這樣我們在另外一個頁面中,也可以獲得這個user物件的資訊
      request.getAttribute("user");
      
      request.getRequestDispatcher("/list").forward(request,response);
      
      //這裡再介紹一個方法
      //這個方法也是轉發,只不過是我們轉發到list這個頁面以後,拿到這個頁面的資料,然後再返回到當前頁面
       req.getRequestDispatcher("/list").include(req,resp);
      
  • 重定向

    • 重定向以後位址列會改變
    • 重定向一次,兩次請求
    • 是瀏覽器完成的
    • //重定向要寫絕對路徑
      resp.sendRedirect("req.getContextPath() + /list");
      
  • 選擇哪個

    • 如果在上一個Servlet當中向request域當中繫結了資料,希望從下一個Servlet當中把request域裡的資料取出來,用轉發
    • 其餘的都用重定向

一、HttpServletResponse


通過觀察HttpServletResponse這個介面,我們可以發現它裡面定義了很多狀態碼。
一般 200 就是 ok了

以 4xx這種 一般都是路徑寫錯了,沒有找到資源

以 5xx 這種都是後端程式碼有問題,或者寫錯了。

狀態 類別 原因
1xx Informational(資訊性狀態碼) 接受的請求正在處理
2xx Success(成功狀態碼) 請求正常處理完畢
3xx Redirection(重定向) 需要進行附加操作以完成請求
4xx Client error(客戶端錯誤) 客戶端請求出錯,伺服器無法處理請求
5xx Server Error(伺服器錯誤) 伺服器處理請求出錯

這裡演示一個利用ajax從伺服器拿到json資料進行解析的小案例:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        //3.響應一段JSON資料
        out.println("[{\"name\":\"lx\"},{\"age\":123}]");

<script type="text/javascript">
        function onJson() {
            //1.傳送ajax請求獲取JSON資料
            var xhr = new XMLHttpRequest();
            xhr.open("GET","/outServlet");//絕對路徑
            xhr.send();
            xhr.onload = function () {
                //JSON.stringify()是將一個物件格式化成JSON資料
                //JSON.parse()是用來解析JSON資料
                var arr = JSON.parse(xhr.responseText);
                arr.forEach((k,v) => {
                    console.log(k,v);
                });
                //這樣我們就能拿到伺服器返回的json資料,可以利用ajax進行頁面的渲染。
            }
        }
    </script>

二、檔案的上傳下載

2.1 檔案的下載

multipart/form-data 可用於HTML 表單從瀏覽器傳送資訊給伺服器。作為多部分文件格式,它由邊界線(一個由'--'開始的字串)劃分出的不同部分組成。每一部分有自己的實體,以及自己的 HTTP 請求頭,Content-Disposition和 Content-Type 用於檔案上傳領域,最常用的 (Content-Length 因為邊界線作為分隔符而被忽略)。

1.首先了解response.setContentType("application/force-download");這個方法是強制下載。當我們的瀏覽器一旦接收到這個請求就會問我們是否進行檔案的下載。
2.我們可以通過response.setHeader("Content-Length","檔案大小");的方法來設定我們的檔案下載大小。
3.根據規範,我們進行設定response.setHeader("Content-Disposition","attachment;filename=test.txt");

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //響應檔案
        String str = "我靠怎麼回事I'm lx,Hello!~~哈哈哈";
        //設定強制提示下載
        response.setContentType("application/force-download");
        //設定檔案的大小
        response.setHeader("Content-Length","" + str.length());
        //根據multipart/form-data規範
        response.setHeader("Content-Disposition","attachment;filename=test.txt");
        ServletOutputStream out = response.getOutputStream();
        //getBytes()預設使用utf-8
        //我們這裡的getBytes方法已經預設使用了utf-8編碼格式,大家可以去看一下原始碼
        out.write(str.getBytes());
    }

2.2 檔案的上傳

<form action="http://localhost:8000/" method="post" enctype="multipart/form-data">
  <!-- 這個enctype="multipart/form-data" 一定要設定!-->
  <!--與此同時我們也一定要在web.xml中的<Servlet>中配置<multipart-config>來告訴它
      因為Servlet是一種懶載入機制,非常的懶,如果我們不去配置,它是不會自己擁有這個功能的,也是為了節省資源吧
  -->
  <input type="text" name="myTextField">
  <input type="checkbox" name="myCheckBox">Check</input>
  <input type="file" name="myFile">
  <button>Send the file</button>
</form>
POST / HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------8721656041911415653955004498
Content-Length: 465
//這個請求頭也宣告瞭使用boundary邊界:-- + 字串的形式,同時也宣告瞭檔案的大小,下面是請求體的內容

-----------------------------8721656041911415653955004498
Content-Disposition: form-data; name="myTextField"
//你會發現這是四個邊界,分成了三個模組
//三個模組當中介紹了三部分資訊,分別對應著我們的form表單
//myTextField資訊

Test
-----------------------------8721656041911415653955004498
Content-Disposition: form-data; name="myCheckBox"
//myCheckBox的資訊

on
-----------------------------8721656041911415653955004498
Content-Disposition: form-data; name="myFile"; filename="test.txt"
Content-Type: text/plain
//myFile 我們的檔案

Simple file.
-----------------------------8721656041911415653955004498--
  • 如果說我們想要實現檔案的上傳這個功能,那麼我們從前端獲取到的資料大家也看到了,就是上面這些,很顯然我們需要自己去解析:那麼解析也有兩種辦法,一種就是我們自己去造輪子?,另一種就是用現成的輪子?
  • 為了顯示我們的工匠精神,我們必須自己造一個輪子?,用原生的Servlet來解析一下
public interface Part {
    //我們首先來了解一下Part這個介面
    InputStream getInputStream() throws IOException;

    //獲取請求型別
    String getContentType();

    //獲取請求的名字
    String getName();

    //獲取我們提交的檔名字
    String getSubmittedFileName();

    long getSize();

    //將我們的檔案資訊,寫入到我們傳進來的路徑中的檔案裡。
    void write(String var1) throws IOException;

    void delete() throws IOException;

    //獲得請求頭資訊
    String getHeader(String var1);

    //遍歷拿到所有請求頭的資訊
    Collection<String> getHeaders(String var1);

    Collection<String> getHeaderNames();
}

第一種實現方式:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        for(Part part:request.getParts()) {
            String name = part.getName();
            System.out.println("name = " + name);
            String value = request.getParameter(name);
            System.out.println(value);
            //檔案處理
            if(part.getContentType() != null) {
                String path = "D:/uploads/" + part.getSubmittedFileName();
                File file = new File(path);
                //如果我們直接使用 file.createNewFile() 那麼如果此時父目錄不存在,則也會報錯。所以我們要通過父目錄是否存在進行判斷
                File parentFile = file.getParentFile();
                if(!parentFile.exists()) {
                    //將所有的父級目錄都建立出來
                    parentFile.mkdirs();
                }
                //將我們拿到的檔案資訊,寫入到這個路徑中的檔案裡
                part.write("path");
            }
        }
    }

第二種實現方式:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        String myTextField = request.getParameter("myTextField");
        String myCheckBox = request.getParameter("myCheckBox");

        //拿到檔案資訊
        Part myFile = request.getPart("myFile");
        //拿到我們的檔名稱
        String fileName = myFile.getSubmittedFileName();
        //這裡就是為了容錯處理。我們將檔案以 . 分割成兩部分
        String[] fs = fileName.split("[.]");
        //我們隨機生成一串字元
        String uuid = UUID.randomUUID().toString();
        //我們的檔案路徑以 父目錄/檔名.字首/當前時間戳/uuid隨機字串/ .字尾構成
        String filePath = "D:/uploads/"+ fs[0] + "/" + System.currentTimeMillis() + "/" + uuid + fs[1];

        File file = new File(filePath);
        //如果我們直接使用 file.createNewFile() 那麼如果此時父目錄不存在,則也會報錯。所以我們要通過父目錄是否存在進行判斷
        File parentFile = file.getParentFile();
        if(!parentFile.exists()) {
            //將所有的父級目錄都建立出來
            parentFile.mkdirs();
        }
        //將我們拿到的檔案資訊,寫入到這個路徑中的檔案裡
        myFile.write("path");
    }

三、Servlet註解開發

  • 談到註解,如果做過專案必然不會陌生,因為我們基本上都是註解式開發了,為了使程式碼更加簡潔高效,減少配置檔案的內容。
//這是在我們的Servlet中用的註解,就不過多的解釋了。
jakarta.servlet.annotation.WebServlet
@WebServlet({"/list" , "/add" , "/detail" , "/del"})

@WebFilter({"/a.do","/b.do"})
public class MyFilter implements Filter {

這裡提到一個解決Servlet類爆炸的問題:

  • 如果我們用JavaWeb這塊技術做專案,必然會用到很多個Servlet,但是如果我們建立很多個Servlet會使業務看起來很複雜,難以維護。
  • 我們可以用下面的設計模式,這樣我們就可以將一個業務放到一個Servlet類中
//模板類
//目錄連結
@WebServlet({"/list" , "/add" , "/detail" , "/del"})
public class ServletFinally extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String servletPath = req.getServletPath();
        if(servletPath.equals("/list")) {
            //根據請求進入相應的方法當中
            doList(req,resp);
        } else if(servletPath.equals("/add")) {
            doAdd(req,resp);
        }
    }
    private void doList(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //然後將我們的業務程式碼放裡面
    }
    private void doAdd(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //然後將我們的業務程式碼放裡面
    }
}

四、結尾

  • 對於Tomcat的Servlet內容就總結這麼多,若想深入學習等待後續更新。
  • 我將會繼續更新關於Java方向的學習知識,感興趣的小夥伴可以關注一下。
  • 文章寫得比較走心,用了很長時間,絕對不是copy過來的!
  • 尊重每一位學習知識的人,同時也尊重每一位分享知識的人。
  • ?你的點贊與關注,是我努力前行的無限動力。?

相關文章