JSP
尚硅谷JavaWeb筆記-05

JSP

Java Server Page

  • 接收請求、處理請求已經了解,現在來到響應的部分
  • 當有人來請求,我們用servlet接收並動態回應,比如想輸出一個html頁面作為結果,servlet可以做到但實現的辦法很蠢,就是靠out.print拼接硬刻出標籤文本格式,改進辦法就是JSP
  • JSP將java > html 的過程包裝起來,可以更好的處理View的部分
    • 底層代碼其實也是拼接,不過Java就是這樣,一層套一層
  • 轉換過程
    • a.jsp >(被web容器翻譯成servlet) > a_jsp.java > a_jsp.class
    • 然後類似一個servlet的生命週期,不過jsp轉來的有自己的方法名
    • 構造 > _jspInit() > _jspService() > _jspDestroy()

JSP與Servlet的異同

引用 https://ithelp.ithome.com.tw/articles/10133506

  • 兩者都是HttpServlet(Servlet API)的子類

URL對應

  • Servlet的URL對應是需要設定的
  • 但是JSP的對應就是它的實體路徑

如何被載入

  • Servlet一開始就是被編譯成class檔案,然後被http request的時候在被Web Container掛載進來
  • JSP是在被第一次呼叫的時候才會被Web Container先翻譯成為Servlet的java寫法,才編譯成為class檔案,放在Web Container一個 暫時的資料夾。雖然第一次執行會比Servlet慢,不過掛載以後就一樣了(因為JSP和Servlet一樣,只會掛載一次)

更新方式

  • Servlet如果有修改,需要重新編譯,因此需要重啟Web Container的服務
  • JSP頁面因為Web Container有在監控,因此,只要有修改,他會重新翻譯、編譯然後掛載。因此不需要重啟Web Container就能看到最新修改

JSP過氣?

到了2021年現在技術已經傾向前後端分離,JSP這種從後端響應結果到前端的技術確實已經少用了,但學javaweb作為基礎知識還是很有必要的,畢竟框架例如springMVC也是基於這些技術搭建,夯實地基才能建高樓

檔頭聲明

<!-- 這是 jsp 檔的頭聲明。表示這是 jsp 頁面 -->
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
  • language:值只能是 java。 表示翻譯的得到的是 java 語言的
  • contentType:設置回應頭 contentType 的內容
  • pageEncoding:設置當前 jsp 頁面的編碼
  • import:給當前jsp 頁面導入需要使用的類包
  • autoFlush:設置是否自動刷新out 的緩衝區,預設為true
  • buffer:設置out 的緩衝區大小。預設為8KB
  • errorPage:設置當前jsp 發生錯誤後,需要跳轉到哪個頁面去顯示錯誤資訊
  • isErrorPage:設置當前jsp 頁面是否是錯誤頁面。是的話,就可以使用exception 異常物件
  • session:設置當前jsp 頁面是否獲取session 物件,預設為true
  • extends:給伺服器廠商預留的jsp 預設翻譯的servlet 繼承於什麼類

代碼格式

  • 要啟動一個jsp頁面,需要編寫.jsp檔案,在IDEA中可以用右鍵新增會自動導入預設格式
  • 然後要記住運行jsp是需要web容器的協助,啟動tomcat後,在url從工程路徑找到它
  • 如果出現錯誤或方法不給用,可能是沒導包,專案沒啟動前當然不認得你的jsp方法,到apache-tomcat-8.0.50\lib\jsp-api.jar自己手動導入,就可以開始用了

聲明腳本(極少用)

近乎廢棄,看看就好

格式 <%! 聲明 java 代碼 %>

<%!
private Integer id;
private String name;
private static Map<String,Object> map;
%>

運算式腳本(常用)

  • 所有的運算式腳本都會被翻譯到_jspService()方法中
  • 運算式腳本都會被翻譯成out.printf()輸出到頁面上
  • 由于運算式腳本翻譯的內容都在_jspService()方法中,所以_jspService()方法中的物件都可以直接使用
  • 不能以分號結束
格式 <%=運算式%>

<%=12 %> <br>
<%=12.12 %> <br>
<%="我是字串" %> <br>
<%=map%> <br>
<%=request.getParameter("username")%>

代碼腳本

  • 特性同運算式腳本
  • 優勢在於可以把java跟html混著用(但這樣很鬧,不利於閱讀)
格式

<%
java 語句
%>

<table border="1" cellspacing="0">
<%
for (int j = 0; j < 10; j++) {
%>
<tr>
<td>第 <%=j + 1%>行</td>
</tr>
<%
}
%>
</table>

註釋

  • JSP的註釋有三種,但有些會保留到翻譯或轉譯後,需要看情況使用
<!-- 這是 html 注釋 -->
html注釋會被翻譯到java原始程式碼中
在_jspService方法裡以out.writer輸出到用戶端,在.html檔中能見

<%
// 單行 java 注釋
/* 多行 java 注釋 */
%>
java注釋會被翻譯到java原始程式碼中,在.java檔中能見

<%-- 這是 jsp 注釋 --%>
jsp注釋才是真正只在jsp中

四大域物件

jsp既然跟servlet一樣,自然內建有request或response等物件提供http協議通訊。也有可以用來儲存上下文或一些資料的Context物件

  • 他們的使用範圍從小到大是:
    • pageContext (PageContextImpl 類):當前 jsp 頁面範圍內有效
    • request (HttpServletRequest 類):一次請求內有效
    • session (HttpSession 類):一個會話範圍內有效,打開流覽器訪問伺服器,直到關閉流覽器
    • application (ServletContext 類):整個 web 工程範圍內都有效,只要 web 工程不停止,資料都在
  • 使用時當然優先從小的開始用,不行再增加範圍,以節省資源
<body>
    <h1>scope.jsp 頁面</h1>
    <%
    // 往四個域中都分別保存了資料
    pageContext.setAttribute("key", "pageContext");
    request.setAttribute("key", "request");
    session.setAttribute("key", "session");
    application.setAttribute("key", "application");
    %>
    pageContext 域是否有值:<%=pageContext.getAttribute("key")%> <br>
    request 域是否有值:<%=request.getAttribute("key")%> <br>
    session 域是否有值:<%=session.getAttribute("key")%> <br>
    application 域是否有值:<%=application.getAttribute("key")%> <br>
    <%
    request.getRequestDispatcher("/scope2.jsp").forward(request,response);
    %>
</body>

-----------------------

<body>
    <h1>scope2.jsp 頁面</h1>
    pageContext 域是否有值:<%=pageContext.getAttribute("key")%> <br>
    request 域是否有值:<%=request.getAttribute("key")%> <br>
    session 域是否有值:<%=session.getAttribute("key")%> <br>
    application 域是否有值:<%=application.getAttribute("key")%> <br>
</body>

輸出

  • 想從jsp輸出至html,有3種方法:
    • out.write():會輸出字節流,可能有亂碼問題
    • out.print():在底層代碼還是轉成out.write,不過用它可以避免亂碼
    • response.getWriter().write():會放在response緩衝區,它的優先順位比較高
  • 避免混亂最好統一使用out.print()
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
response.getWriter().write("寫到html的第1行 <br/>");
out.write("寫到html的第2行 <br/>");
out.print("寫到html的第3行 <br/>");
%>
</body>
</html>

包含標籤

  • 包含就是Servlet容器將其他Web組件(JSP、Servlet、Html),將生成的結果包含到自己的結果中
  • 比如用來把網站模組化拆開實現解偶與復用,比如網頁腳部訊息(看網站時拉到最下面聯絡資訊那些),讓他引用同一個jsp,我只要維護這一個jsp就可以套用到整個網站的所有頁面

Servlet之間是不允許相互調用的,所以只能用jsp實現

靜態包含

  • 格式:
<%@ include file="/xxx/footer.jsp"%>
注意第一個/表示http://ip:port/工程/
  • 不翻譯,直接把jsp代碼原封不動拷貝一份過去
  • 屬於編譯階段代碼的拼接,最終只會產生一個.class文件
  • 用於加載後就再也不會變的東西

動態包含

已經較少使用了,原因後述

  • 格式:
<jsp:include page="/include/footer.jsp">
    <jsp:param name="username" value="bbj"/>
    <jsp:param name="password" value="root"/>
</jsp:include>
  • 可以傳遞參數
  • 當web容器執行到include語句才開始翻譯要引用的內容,產生出對應的Servlet文件並調用執行(最終會產生多個.class文件),所以達到動態的效果
  • 屬於編譯後,servlet運行結果的拼接
  • 用於加載比如查詢資料庫結果、時間戳等等

轉發標籤

  • 格式:
<jsp:forward page="/scope2.jsp"></jsp:forward>
  • 就是用來轉發,跟寫這個有一樣的效果
<%
	request.getRequestDispatcher("/scope2.jsp").forward(request,response);
%>

小結

前端收到一個請求 > Servlet接收 > 處理(調用資料庫等等) > 將參數(可能是查詢的結果)存在域物件中(因為是同一個request) > 轉發給JSP > 從域物件解析參數 > 生成html傳給前端

JSP的痛點

引用 https://www.zhihu.com/question/328713931/answer/711014242

  1. 動態資源和靜態資源全部耦合在一起,無法做到真正的動靜分離。伺服器壓力大,因為伺服器會收到各種http請求,例如css的http請求,js的圖片的,動態代碼的等等。一旦伺服器出現狀況,前後臺一起玩完,用戶體驗極差。
  2. 前端工程師做好html後,需要由java工程師來將html修改成jsp頁面,出錯率較高(因為頁面中經常會出現大量的js代碼),修改問題時需要雙方協同開發,效率低下。
  3. jsp必須要在支援java的web伺服器裡運行(例如tomcat等),無法使用nginx等(nginx據說單實例http併發高達5w,這個優勢要用上),性能提不上來。
  4. 第一次請求jsp,必須要在web伺服器中編譯成servlet,第一次運行會較慢。
  5. 每次請求jsp都是訪問servlet再用輸出流輸出的html頁面,效率沒有直接使用html高。
  6. jsp內有較多標籤和運算式,前端工程師在修改頁面時會捉襟見肘,遇到很多痛點。
  7. 如果jsp中的內容很多,頁面回應會很慢,因為是同步載入。

上次修改於 2022-01-05