Servlet與JSP核心程式設計 讀書筆記
雖然Servlet和JSP學習也使用了挺長時間了,但是最近讀了下《Servlet和JSP核心程式設計》這本書,雖然是熟悉的知識,但是仍然有些因為過長時間沒使用而忘記了,記下這篇讀書筆記,就當是對Servlet和JSP涉及的核心知識點進行一個整理與回顧吧。書中對Servlet與JSP進行了完整的介紹,總體上還是非常基礎的,如果是初學者,或者有了一定開發經驗的開發人員,都能從書中得到收穫。
下面的條目是我在閱讀過程中記下的筆記,調理也許不是那麼清晰,但是對於自己在需要的時候複習還是很有幫助的。
1、應該儘可能覆蓋doGet()和doPost()方法,不要為了省事直接覆蓋service()方法。
原因由兩個:一是喪失想要覆蓋其他doXXX()方法的可能性,不排除以後需要覆蓋這些方法的可能性;二是無法實現getLastModified方法,該欄位可以得到請求最後修改的時間,如果請求的資源沒有發生修改,那麼服務端將返回304狀態碼,表示請求的資源已經是最新的,上次請求的資源可以繼續使用。
getLastModified擴充套件:如果方法返回的是整數,並且客戶端請求頭沒有包含If-Modified-Since欄位或者已經包含If-Modified-Since欄位,但是返回值比If-Modified-Since指定的時間更新的話,會呼叫doGet方法請求最新的資源,並返回包含Last-Modified頭欄位的響應資訊。如果返回值比If-Modified-Since欄位指定的時間小的話(發生在之前),那麼服務端可以做出判斷上次響應給客戶端的資源仍然可用,於是返回304狀態碼,告訴客戶端你上次請求的這個資源可以繼續使用。
2、不要為了阻止併發訪問Servlet例項而去實現SingleThreadModel介面,原因有二:
1) 由於實現SingleThreadModel介面就相當於對Servlet例項進行了同步,表面上不用擔心因為多執行緒併發訪問的問題造成資料的安全性和一致性,但是如果servlet被頻繁地訪問,那麼同步對效能造成的影響是巨大的。比如在servlet等待IO任務的時候,servlet不能處理其他的請求
2) servlet規範允許伺服器使用多個例項處理請求,來替代對單個例項的請求進行排隊的方案。當然我們並不希望使用多個例項,因為多個servlet例項都擁有變數的單獨副本,從而造成資料不能正確共享,在servlet之間傳遞訊息也有困難。
注:SingleThreadModel在servlet規範2.4中明確反對使用這種方式。為了實現同步,使用synchronized往往是更好的選擇。
3、如果需要讀取請求引數,為了防止跨站指令碼攻擊(XSS),必須過濾出特殊的HTML字元。這種過濾方式的問題可能導致輸出部分缺失。比如將’<’替換成’<’,’>’替換成’>’,’”’替換成’"’,’&’替換成’&’。
4、使用Servlet構建Excel電子表格
設定響應頭:
response.setContentType("application/vnd.ms-excel"); PrintWriter out = response.getWriter();
- 1
- 2
例子:
public class App1 extends HttpServlet{ public void doGet(HttpServletRequest req,HttpServletResponse res){ response.setContentType("application/vnd.ms-excel"); PrintWriter out = response.getWriter(); out.println(\tQ1\tQ2\tQ3\tQ4\tTotal); out.println("Apples\t78\t87\t92\t29\t=SUM(B2:E2)"); out.println("Oranges\t78\t87\t92\t29\t=SUM(B3:E3)"); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
5、使用Servlet生成JPEG影象
第一步:建立一個BufferedImage
int width = 100px; int height = 100px; BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
- 1
- 2
- 3
第二步:在BufferedImage繪製圖像
Graphics2D g = (Graphics2D)image.getGraphics(); g.setXXX(); g.fill(); g.draw();
- 1
- 2
- 3
- 4
第三步:設定Content-Type響應頭
response.setContentType("image/jpeg");
- 1
第四步:獲取輸出流
OutputStream out = response.getOutputStream();
- 1
第五步:以JPEG格式將影象傳送到輸出流
ImageIO.write(image,"jpg",out);
- 1
6、使用Cookie記錄偏好
form.jsp
<form action="/register" method="post"> firstName:<input name="firstName"/><br> lastName:<input name="lastName"/><br> <input type="submit"/> </form>
- 1
- 2
- 3
- 4
- 5
RegisterServlet.java
@WebServlet("/register"); public class RegisterServlet extends HttpServlet{ public void doGet(...){ response.setContentType("text/html"); String firstName = request.getParameter("firstName"); String lastName = request.getParameter("lastName"); Cookie c1 = new Cookie("firstName",firstName); response.addCookie(c1); Cookie c2 = new Cookie("lastName".lastName); response.addCookie(c2); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
7、對發往客戶端的URL進行編碼
如果使用URL重寫進行會話跟蹤,大部分頁面或者全部頁面都必須動態生成,站點的任何靜態HTML文件都不能指向站點的動態頁面的連結
情況一:在servlet生成的Web頁面中含有嵌入的URL,應該呼叫response的encodeURL,確定當前是否在使用URL重寫,僅在必須時附加回話資訊;否則不做任何修改直接返回傳入的URL。
情況二:在sendRedirect呼叫中,應該呼叫encodeRedirectURL
如果最終可能使用URL重寫替代Cookie,那麼使用URL編碼是最好的選擇
8、JSP的好處
1) 易於編寫HTML並維護
2) 可以使用標準的開發工具開發
3) 開發人員可以集中精力在表示上
4) JSP擅長生成結構化的HTML頁面,Servlet擅長業務的處理,可以生成二進位制的資料
9、JSP指令碼、表示式編譯成的Servlet
Foo.jsp
<h2>foo</h2> <!-- JSP表示式,呼叫後會顯示bar方法執行返回的結果,結尾不需要分號 --> <%= bar() %> <!-- JSP scriplet,直接呼叫bar(),結尾需要分號 --> <% bar(); %>
- 1
- 2
- 3
- 4
- 5
Foo_jsp.java,之後被編譯成Foo_jsp.class
public void _jspInit(){ ..} – 當JSP網頁一開始執行時,最先執行此方法,執行初始化工作
public void _jspDestory(){...} – JSP網頁最後執行的方法 public void _jspService(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, ServletException { HttpSession session = request.getSession(); JspWriter out = response.getWriter(); out.println(<h2>Foo</h2>); out.println(bar()); bar(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
10、對於JSP的初始化和清理工作,可以使用JSP宣告覆蓋jspInit()和jspDestroy()方法
JSP宣告格式 <%! 變數或者方法 %> 現考慮如下JSP宣告: <%! private int accessCount = 0;%> <%= ++accessCount%>
- 1
- 2
- 3
- 4
- 5
不同的客戶端得到的技術不是唯一的,也就是由於多個請求共享accessCount變數,該程式碼不是執行緒安全的。但是在伺服器重新啟動之前,所有使用者看到的都是同樣的結果
11、session屬性
使用形式:<%@ page session = “true” %><%– default –%>
session屬性屬於jsp的page指令。可以控制是否參與會話,
true表示如果存在會話,則繫結到現有的會話中,如果沒有則建立一個新的session會話(session的型別是HttpSession);
值為false,表示不會自動建立會話,在將jsp轉為servlet時,對變數session的訪問會導致出錯。但是false也不是無之地,對於高流量的網站,使用false可以節省大量的伺服器記憶體。但是使用false並不表示會禁用會話跟蹤,而且會話只是針對使用者,並不針對頁面,所以關閉頁面的會話跟蹤沒有任何益處
12、isELIgnored屬性
預設為false,表示對JSP表示式進行正常的求值,為true則忽略表示式語言。也就是在JSP頁面${expression}得到的就是表示式。
在JSP1.2之前(包括JSP1.2)該屬性的值都是true,只有在Servlet2.4之後(JSP2.0)該值的預設屬性為false。所以如果在訪問JSP的時候出現直接輸出JSP表示式的情況,需要檢查一下你的Servlet版本是否在2.4之前,如果不想更換版本,只需要在JSP頁面只能中直接新增該屬性為false即可
13、jsp:include動作與include指令
前者會對被包含的JSP的頁面在請求時進行處理,後者在頁面轉換期間就被處理。而且前者會生產兩個Servlet檔案,後者只會產生一個。
在維護和能力兩方面造成jsp:include動作優於include指令。
由於jsp:include動作會會產生兩個Servlet而後者則有兩個,那麼如果通過include指令被包含的頁面發生了修改,那麼所有包含該頁面的jsp檔案都需要重新轉換成新的Servlet,不然下次請求訪問的仍然是修改之前的Servlet。
對於檔案包含應該儘可能用jsp:include動作,僅在所包含的檔案中定義了主頁面需要用到的欄位或者方法,或者在所包含的檔案中設定了主頁面的響應頭的時候,才應該使用include指令。
然而,不能因為在所包含的頁面定義了主頁面的需要的欄位或者方法就認為必須應該使用include指令。對此合理的解釋是,如果使用jsp:include動作,那麼針對類似訪問計數功能的頁面來說,所有使用該頁面的主jsp頁面都將顯示相同的計數。核心就是產生了兩個Servlet,而這兩個Servlet之間是無法共享計數值的。
14、基於請求的共享
public class NumberBean{ private int num; ... } public class NumberServlet extends HttpServlet{ ... doGet(...){ NumberBean number = new NumberBean(new Random().nextInt(Integer.MAX_VALUE)); request.setAttribute("number",number); String addr = "/WEB-INF/view/number.jsp"; request.getRequestDispatcher(addr).forward(request,response); } } number.jsp ... <jsp:useBean id = "number" type="servlet.NumberServlet" scope="request"/> Your number is: <jsp:getProperty name="number" property="num">
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
以上這種組合方式只能訪問簡單型別的屬性,對於包裝的POJO的屬性則需要複雜的語法才能實現,在現在主流的MVC框架中(包括Struts2和Spring Web MVC)都對這點進行了封裝,讓使用者可以像訪問簡單POJO的屬性一樣訪問包裝型別的POJO的屬性。
15、EL
${bash}
輸出
${bash}
\${1+1} is ${1+1}
輸出
${1+1} is 2
${name}
作用域訪問次序:PageContext–>HttpServletRequest–>HttpSession–>ServletContext
等同於呼叫JSP表示式:<%= pageContext.findAttribute(“name”)%>
等同於:
<jsp:useBean id="name" type="somePackage.someClass" scope="..."> <%= name%>
- 1
- 2
返回某個作用域變數的某個屬性
customer.fistName等同於 customer.fistName等同於{customer[“fistName”]}(這種方式很少使用,不推薦使用)
等同於
<%@ page import="bean.Customer"%> <% Customer customer = (Customer)pageContext.findAttribute("customer"); %> <%= customer.getFistName() %>
- 1
- 2
- 3
等同於
<jsp:useBean id="customer" type="bean.Customer" scope="request,session, or application" /> <jsp:getProperty name="customer" property="fistName" />
- 1
- 2
但是對於類似${customer.address.zipCode},使用和則不能完成相同的獲取屬性的工作
訪問集合
${attributeName[entryName]}
對於JSP2.0,Servlet2.4以上(包含)的版本,還規定了以下的隱式物件:
pageContext:一次擁有request、response、session、out和servletContext屬性。獲取會話ID可以用${pageContext.session.id}
param和paramValues:允許訪問基本的引數值和請求引數值陣列
header和headerValues:訪問HTTP請求報頭的主要值和全部值
cookie:
initParam:
pageScope,requestScope,sessionScope和applicationScope:
算數運算子:+,-,*,/(div)
關係運算符:==和eq(比較兩個引數是否相等)、!=和ne(比較兩個引數是否不等)、<和lt、>和gt、<=和le、>=和ge
邏輯運算子:&&、and、or、||、!、not
空運算子:empty
16、JDBC
使用JDBC連線資料庫:
1) 載入JDBC連線資料庫的驅動檔案
Class.forName(“com.mysql.jdbc.Driver”);
2) 定義要開啟的連線
String url = “jdbc:mysql://localhost:3306/test”;
3) 建立連線
String username = “root”;
String password = 1234;
Connection connection = DriverManager.getConnection(url,username,password);
4) 建立Statement物件
Statement statement = connection.createStatement();
5) 執行查詢
String sql = “select * from user where user_id = 3”;
ResultSet rs = statement.executeQuery(sql);
6) 結果處理
ResultSet的第一行的索引是1
while(rs.next()){
rs.getString(COLUMN_NAME);
}
7) 關閉連線
connection.close();
呼叫資料庫的儲存過程:
1) 定義對資料庫儲存過程的呼叫
? = call procedure_name(?,?..?);
2) 準備CallableStatement
String procedure = {? = call procedure_name(?..?)}
CallableStatement statement = connection.prepareCall(procedure);
3) 註冊輸出引數的型別
statement.registerOutParameters(n,type);
n是輸出引數的索引(第一個的索引是1),type是java.sql.Types定義的常量
4) 提供輸入值
statement.setInt(2,12);
statement.setString(3,”name”);
將第一個輸入引數設為12,將第二個輸入引數設為name,輸入引數的索引從第一個輸出引數開始計起
5) 執行儲存過程
statement.execute();
6) 訪問返回的輸出引數
int res = statement.getInt(1);
這裡返回的是第一個輸出引數,如果是第二個引數則將引數1變為2,其他以此類推
事務管理模板:
Connection connection = DriverManager.getConnection(url.username,password); boolean autoCommit = connection.getAutoCommit(); try{ connection.setAutoCommit(fasle); Statement st = connection.createStatement(); //執行事務單元 ... st.close(); }catch(SQLException e){ connection.rollBack(); throw new Throwable(e.getCause()); }finally{ connection.commit(); connection.setAutoCommit(autoCommit); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
mysql使用者授權
grant all privileges on database_name.* to [email protected]"%" identified by 'PASSWORD'
- 1
再分享一下我老師大神的人工智慧教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智慧的隊伍中來!https://blog.csdn.net/jiangjunshow