Java web筆記(更新ing) 一、HTTP協議 HTTP(超文本傳輸協議),它是一種主流B/S架構中應用的通信協議。具有以下特點: 1、無狀態 服務端不會記錄客戶端每次提交的請求,伺服器一旦相應客戶端之後,就會結束本次的通信過程。客戶端下一次的請求是一個新的 連接,和上一次通信沒有任何關係。 ...
1、無狀態
服務端不會記錄客戶端每次提交的請求,伺服器一旦相應客戶端之後,就會結束本次的通信過程。客戶端下一次的請求是一個新的 連接,和上一次通信沒有任何關係。
2、簡單靈活
HTTP是基於請求(request)和響應(response)的模型
3、支持客戶端與服務端
支持主流的B/S架構的通信以及C/S架構的通信。
註意:C/S架構可選的協議有多種,例如:TCP/IP,UDP,HTTP
而B/S架構通常只支持HTTP協議
二、伺服器
1、概念
伺服器通常由硬體和軟體部分構成,統一對用戶提供多種不同的服務。
1、硬體:包括響應的CPU、記憶體、磁碟等等
2、軟體:包括操作系統、運行環境、伺服器軟體、資料庫等等
2、web伺服器
web伺服器是提供服務端程式運行的一個環境,它本身也是一個軟體。
例如:將我們編寫HTML文件放入到web伺服器中,那麼外界就可以通過瀏覽器訪問我們的html頁面
常見的web伺服器有Apache,Tomcat、Jetty、Nginx等等。
而Tomcat、Jetty這些web伺服器更準確的說是一個Servlet容器。
三、JavaWeb項目結構
項目根目錄,例如:myweb、ch01 | 通常存放靜態資源文件(如:html等等) | ||
---|---|---|---|
WEB-INF | 這個目錄是當前項目私有的一個文件夾,只能提供給項目內部訪問,對於客戶端來說是訪問不到了,通常這個目錄下存放的是Java源代碼、編譯後的位元組碼文件以及Servlet的核心配置文件web.xml | ||
src | 存放java源代碼的目錄 | ||
classes | 存放編譯後的位元組碼文件 | ||
lib | lib目錄存放當前項目所需要的jar文件 | ||
JSP | 用於存放JSP動態頁面 | ||
web.xml | 項目的配置文件,用於配置Servlet的請求映射、過濾器、監聽器等等信息。每一個web項目都對應一個web.xml配置文件 | ||
META-INF | 配置應用程式、擴展程式、類載入服務等等 |
四、Servlet基礎
1、什麼是Servlet
Servlet是JavaEE中標準組件,專門用於處理客戶端提交的HTTP請求。並且它必須依賴於Servlet容器才可以運行(Tomcat就是一個標準的Servlet容器),Servlet容器給Servlet提供一個運行環境,所以Servlet組件必須要這個環境中可以運行,而不能脫離這個環境而單獨執行。因為Servlet的實例是由容器創建和銷毀的,並不是通過我們平常使用的new關鍵創建出來。
2、開發一個Servlet的步驟
1.編寫一個類,然後繼承HttpServlet這個父類
2.重寫父類的service方法,這個就是專門處理客戶端請求的方法,這個方法有兩個參數(HttpServletRequest,HttpServletResponse),同時這個方法會拋出兩個異常(ServletException,IOException)
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; //要讓當前的類是一個Servlet,必須繼承HttpServlet public class HelloServlet extends HttpServlet{ //重寫父類的service方法,處理客戶端請求, //這個方法私有servlet容器去調用, //並且request和response參數都是由servlet容器傳遞進來的 public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("Hello Servlet"); //響應一些信息回饋給客戶端瀏覽器 //1.設置要響應的類型,這裡就響應簡單的html文本類型 //通過response參數來進行設置,如:text/html,text/plain response.setContentType("text/html;charset=utf-8"); //2.獲取輸出流並寫回html數據 response.getWriter().println("<h1>Hello Servlet</h1>"); } }
3.編譯Servlet,需要依賴servlet-api.jar文件
4.編寫web.xml,為servlet配置請求映射的URL
<?xml version="1.0" encoding="utf-8"?> <!-- 配置根節點 --> <web-app> <!-- 配置servlet類 --> <servlet> <!-- 指定servlet的別名 --> <servlet-name>hello</servlet-name> <!-- 指定Servlet的完整類名--> <servlet-class>HelloServlet</servlet-class> </servlet> <!-- 配置請求映射--> <servlet-mapping> <!-- 這裡的servlet-name和上面的servlet-name要一一對應 --> <servlet-name>hello</servlet-name> <!-- 配置請求映射的url,必須以“/”開頭--> <url-pattern>/test</url-pattern> </servlet-mapping> </web-app>
5.將項目部署到Tomcat的webapps目錄中
3、servlet處理請求的流程
1.瀏覽器發起http的請求,這個請求首先會被servlet容器(Tomcat)截獲,然後容器會根據web.xml文件中配置servlet的<url-pattern>來找到相應的<servlet-name>這個別名,然後再根據這個別名找到具體Servlet的類,然後容器會創建這個Servlet類的實例並調用Servlet方法來處理這個請求。
請求網頁地址: http://127.0.0.1:8080/ch02/test
4、Servlet的生命周期
所謂的生命周期,就是從Servlet的創建一直到它銷毀的整個過程。並且它的 整個生命周期都是由Servlet容器(Tomcat)負責管理和維護的。(補充:在Tomcat中,Servlet是以單實例多線程的方式處理客戶端請求)
4.1 Servlet對象創建的過程
當第一次請求某個Servlet的時候,容器會先查找之前有沒有創建過這個Servlet的實例,如果沒有則創建一個實例並緩存起來。後續 所有請求這個Servlet的時候,都會使用這個緩存的對象來處理客戶端請求。(註意“這裡說的是第一次請求時創建。另外一種情況則是在容器啟動的時候就創建Servlet的實例,在web.xml中為Servlet指定<load-on-startup>配置,這個配置的值是一個整形,數值越小,則初始化 的優先順序別越高)
4.2 生命周期方法
方法名 | 描述 |
---|---|
init | 在Servlet對象創建之後立即執行的初始化方法,且只執行一次 |
service | 核心的請求處理方法,這個方法可以執行多次 |
destroy | 容器準備銷毀Servlet實例之前執行的方法,也是執行一次 |
5、HTTP請求報文
5.1 請求報文
請求行:請求報文的第一行就是請求行。包括請求方法、請求URL地址、HTTP協議版本
請求頭:請求行之後的信息就是請求頭,它是以“名稱:內容”的格式體現。主要包括伺服器主機地址及埠號、連接狀態、接收的數據類型、編碼、語言等等
請求體:請求頭結束之後會有一個空行,空行之後就是請求體的內容。通常使用POST提交的數據信息會存放在請求體中,然後傳遞給伺服器。
5.2 響應報文
狀態行:主要包括HTTP協議、響應狀態碼(例如:200表示OK,成功響應)
響應頭:主要包括伺服器信息、響應的類型及編碼、內容的長度、響應的時間等
響應體:伺服器將信息攜帶到響應體中,帶回客戶端。
6、HTTP請求方法
在HTTP/1.1協議中,請求方法主要包括8個,下麵列舉常用的請求方法進行說明。
請求方法 | 說明 |
---|---|
GET | 向伺服器請求指定的資源,並返迴響應主體。一般來說GET方法應該只用於數據的讀取(類似於查詢) |
POST | 向指定的伺服器提交數據(例如:表單數據的提交、文件上傳等),並且提交的數據會放入請求體中(類似於新增) |
PUT | 向伺服器提交數據,但是和POST有所區別。如果伺服器不存在此資源的時候,則執行新增,如果存在則執行修改。(類似於修改) |
DELETE | 根據uri的表示刪除伺服器上的某個資源(類似於刪除) |
... | ... |
備註:GET與POST區別:
1.GET主要用於獲取數據,POST用於提交數據。
2.GET請求所帶的參數是放在請求行的url地址後面,而POST這是放在請求體中。
3.通常瀏覽器會對GET請求的url長度有所限制 ,而POST通常在請求體中,可以提交更多的數據信息。
4.瀏覽器會對GET請求進行緩存。
7、Servlet的請求處理方法
方法 | 說明 |
---|---|
service | 可以處理任何的請求類型 |
doGet | 處理對應的GET請求 |
doPOST | 處理對應的POST請求 |
doPut | 處理對應的PUT請求 |
doDelete | 處理對應的DELETE請求 |
說明:通過HttpServlet的源代碼得知,預設的所有請求都會先經過service方法,然後service方法根據請求的方法類型判斷來決定交給doGet或者是doPOST方法來處理請求。如果子類重寫了service方法同時還重寫了其他的doXxx的方法,那麼只有service方法會處理請求,其他方法將失效。
8.1 HttpServletRequest常用API
方法 | 說明 |
---|---|
getParameter(String name) | 獲取請求參數的值,根據請求參數的name指定 |
getParameterValues(String name) | 獲取相同name的請求參數,返回的是字元串數組 |
getParameterMap() | 獲取所有請求參數,包括參數名稱和值 |
getMethod() | 獲取請求方法的類型 |
getHeader(Stirng name) | 根據請求頭的名稱獲取響應的信息 |
getRemoteAddr() | 獲取遠程客戶端的IP地址 |
getServletPath() | 獲取Servlet的請求地址,也就是url-pattern |
getRequestURL() | 獲取請求完整的URL地址 |
getRealPath(String path) | 獲取項目的絕對路徑。(這個方法在request對象中已廢棄,建議通過ServletContext對象獲取) |
其他 | ... |
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ System.out.println("---------------獲取參數值----------------"); //獲取請求參數,根據請求的參數名 String name = request.getParameter("uname"); String age = request.getParameter("age"); System.out.println(name); System.out.println(age); //獲取相同參數名的參數 String[] addrs = request.getParameterValues("address"); for (String addr : addrs) { System.out.println(addr); } System.out.println("--------------獲取參數名---------------"); //獲取所有的參數名 Enumeration<String> es = request.getParameterNames(); //枚舉使用迭代器來迴圈遍歷 while(es.hasMoreElements()) { String paramName = es.nextElement(); System.out.println(paramName); } System.out.println("---------------獲取所有的參數名和參數值-------------------"); //map泛型的第一個參數表示請求的參數名稱,第二個參數是請求的參數值,值可以有多個,所以是數組 Map<String,String[]> map = request.getParameterMap(); //map的key對應的是參數名,value就是參數的值 for (String key : map.keySet()) { System.out.println("參數名:" + key); System.out.println("參數值: "); String[] values = (String[])map.get(key); for (String value : values) { System.out.println(value); } System.out.println("~~~~~~~~~~~"); } System.out.println("-------------獲取客戶端的請求方法--------------"); String method = request.getMethod(); System.out.println("請求方法:" + method); System.out.println("--------------獲取請求頭部的信息---------------"); String header = request.getHeader("Host"); System.out.println("請求頭信息:" + header); System.out.println("--------------獲取遠程客戶端的IP地址--------------"); String addr = request.getRemoteAddr(); System.out.println("客戶端的IP地址" + addr); System.out.println("------------獲取Servlet的url-pattern---------------"); String servletPath = request.getServletPath(); System.out.println("url-pattern: " + servletPath); System.out.println("------------獲取請求的完整URL---------------------------"); String url = request.getRequestURL().toString(); System.out.println(url); System.out.println("-------------獲取項目的絕對路徑------------------"); String path = request.getServletContext().getRealPath("/"); System.out.println(path); }
8.2 HttpServletResponse常用API
方法 | 說明 |
---|---|
setContentType(String str) | 設置響應內容的類型及編碼 |
getWriter() | 獲取響應字元輸出流 |
getOutputStream() | 獲取位元組輸出流 |
setHeader(String name,String value) | 設置響應頭信息,如果存在響應頭信息,則執行更新 |
addHeader(String name,String value) | 設置響應頭,不管存不存在都會新加入一個 |
setStatus(int code) | 設置響應狀態碼 |
其他 | ... |
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //設置響應的內容類型及編碼,包括(text/html,text/plain,application/json) response.setContentType("text/html;charset=utf-8"); //設置響應頭信息 response.addHeader("myheader", "hello header"); response.addHeader("myheader", "my servlet"); //設置響應的狀態碼 response.setStatus(200); //獲取位元組輸出流 OutputStream os = response.getOutputStream(); //獲取響應的字元輸出流 PrintWriter pw = response.getWriter(); pw.println("<html>"); pw.println("<head><title>index</title></head>"); pw.println("<body>"); pw.println("<h3>Hello Servlet</h3>"); pw.println("</body>"); pw.println("</html>"); }
8.3 常見的響應狀態碼
狀態碼 | 說明 |
---|---|
200 | 請求成功 |
401 | 禁止訪問,未授權 |
404 | 找不到請求的資源 |
405 | 請求的行的方法不被支持 |
500 | 伺服器內部錯誤 |
其他 | ... |
9、Servlet之間的通信
所謂轉發,就是在多個Servlet之間共用請求和響應對象,所有參與轉發過程的Servlet都可以獲取同一個請求對象的信息。在Servlet的API中,轉發的操作是有HttpServletRequest對象完成的。
示例代碼:
public class ServletA extends HttpServlet{ public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ //獲取頁面提交的參數 String name = request.getParameter("userName"); //轉發由HttpServletRequest完成 //第一步先獲取一個請求轉發器RequestDispatcher //獲取請求轉發器的同時要告訴轉發器轉發到哪裡,轉發給誰 //如果要轉發給ServletB,那麼就是對應ServletB的url-pattern RequestDispatcher rd = request.getRequestDispatcher("servletB"); //調用轉發器的forward方法執行轉發,同時將request和response對象一併轉發ServletB rd.forward(request, response); } }
public class ServletB extends HttpServlet{ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //這裡在同一個請求中再次獲取頁面的參數 String name = request.getParameter("userName"); System.out.println("ServletB獲取請求參數:"+name); } }
轉發的特點:
1、URL地址欄不會發生改變
2、轉發的過程是在服務端自動完成
請求作用域:
每一個請求對象都有一個獨立的空間,這個空間我們稱之為請求作用域(RequestScope),可以為當前這個請求攜帶額外的一些數據信息,這些信息同樣可以在多個Servlet之間進行共用。
示例代碼:
public class ServletA extends HttpServlet{ public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ //在ServletA中為request對象設置請求作用域 request.setAttribute("age", 35) } }
public class ServletB extends HttpServlet{ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //在ServletB中獲取統一個請求對象作用域的值域的值 Integer age = (Integer)request.getAttribute("age"); } }
9.2 重定向
重定向的機制和轉發不同,一次重定向的過程中會有兩次請求和兩次響應,服務端在接收第一次請求後會先做一次302的響應(302表示重定向狀態碼),告訴客戶端瀏覽器必鬚髮起一個新的地址,服務端再次接收這個請求處理,最後再次響應客戶端。
重定向特點:
1、URL地址欄會發生改變
2、重定向的操作是在客戶端瀏覽器完成的
示例代碼:
方式一:
public class ServletC extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //執行重定向 //方式一:設置302響應狀態碼,併在響應頭中添加location屬性指定重定向的地址 response.setStatus(302); response.addHeader("location", "http://localhost:8080/ch06/servletD"); } }
方式二:
public class ServletC extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("請求到達ServletC"); System.out.println("ServletC獲取請求參數:"+request.getParameter("userName")); //執行重定向 //方式二:使用response的sendRedirect方法 response.sendRedirect("servletD"); } }
10、會話跟蹤
10.1 cookie
cookie是客戶端瀏覽器的內部的一個文本文件,專門用於記錄伺服器發送過來的一些文本信息,那麼在每次請求的時候,客戶端都把這個cookie信息由提交回給相應的伺服器,那麼伺服器就可以獲取cookie的信息,達到會話跟蹤的目的。使用cookie的機制是基於客戶端瀏覽器來維護與伺服器的狀態跟蹤。
示例代碼:
設置cookie
public class SetCookieServlet extends HttpServlet{ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //創建一個Cookie的實例 Cookie cookie = new Cookie("userId","10001"); //將cookie對象設置到響應對象中 response.addCookie(cookie); System.out.println("成功設置cookie"); } }
獲取cookie
public class GetCookieServlet extends HttpServlet{ @Override protected void service(HttpServletRequest request, HttpServletResponse repsonse) throws ServletException, IOException { //cookie是通過request對象來得到的 //從請求中可以獲取多個cookie對象 Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { //判斷cookie,只獲取name為userId的cookie對象 if("userId".equals(cookie.getName())) { System.out.println(cookie.getValue()); } } } }
cookie保存中文:
在保存cookie的時候如果需要保存中文,那麼中文信息需要經過編碼後才可以寫入cookIe
示例代碼:
編碼使用URLEncoder
String str=URLEncoder.encode("張三","utf-8"); Cookie cookie = new Cookie("userName",str);
解碼使用URLDecoder
String str=URLDecoder.decode(cookie.getValue(),"utf-8");
System.out.println(str);
cookie的生命周期:
預設cookie只會保存在瀏覽器進程的記憶體中,並不會寫入cookie文件,如果關閉了瀏覽器,那麼瀏覽器的進程也就消失了,那麼對應的記憶體就會釋放空間,因此cookie的也就銷毀。如果想將cookie寫入文件,那麼就必須設置cookie的生命時長,一旦設置了生命時長,那麼就表示這個cookie會在文件中保留多長時間,到了這個時間之後,瀏覽器就會自動銷毀這個cookie。
設置cookie存活時間:
//設置為0表示立即刪除cookie cookie.setMaxAge(0); //設置為正數表示cookie在cookie文件的存活時間,單位:秒 cookie.setMaxAge(5); //設置為-1表示cookie只保留在瀏覽器的進程中,關閉瀏覽器之後會銷毀cookie cookie.setMaxAge(-1);
Session是基於服務端來保存用戶的信息,這個是和cookie的最大區別。不同的客戶端在請求伺服器的時候,伺服器會為每一個客戶端創建一個Session對象並保存在伺服器端,這個Session對象是每個客戶端所獨有的,相互之間不能訪問。伺服器為了區別不同的Session屬於哪一個客戶端,因此Session對象也有一個唯一標識,叫做SessionID。而這個SessionID是以cookie的機制保存在客戶端瀏覽器。每次請求的時候,瀏覽器都會把這個SessionID帶回服務端,服務端根據這個SessionID就可以找到對應的Session對象。
示例代碼:
public class SessionServlet extends HttpServlet{ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //HttpSession對象是在第一次調用request的getSession()方法時才會創建 //註意:getSession()的方法會先判斷之前是否為客戶端創建了session實例, //如果創建了,則使用之前創建好的Session對象,沒有則創建一個新的Session HttpSession session = request.getSession(); //創建HttpSession的同時,會創建一個唯一的標識SessionID //這個sessionId會保存在瀏覽器的cookie中,每次請求會帶回這個id找到相應的session對象 String sessionId = session.getId(); System.out.println(sessionId); } }
session的生命周期:
1. SessionId是保存在瀏覽器的cookie中,但是不會寫入cookie文件,這也就表示當關閉瀏覽器之後SessionId就會銷毀。SessionId銷毀以後,服務端的Session就沒有任何作用了。但是伺服器並不會立刻銷毀這個Session對象,至於什麼時候銷毀是由伺服器自己決定的。除非我們手動調用了session.invalidate()方法,伺服器就會立即銷毀這個session實例。
示例代碼:
HttpSession session=request.getSession(); //立即銷毀session session.invalidate();
2.Session預設也有存活時間,伺服器在創建Session的時候為Session設置預設的存活時間為30分鐘,如果在30分鐘之內,客戶端沒有發起任何到伺服器,那麼伺服器就會銷毀這個Session對象。我們也可以設置Session的存活時間。可以為當前的Session設置,也可以為全局(伺服器端的所有Session)的Session設置。
設置當前Session的存活時:
HttpSession session=request.getSession(); //設置當前Session的存活時間,單位:秒 session.setMaxInactiveInterval(3600);
設置 全局的Session的存活時間 :
在web.xml中進行設置
<!-- 設置全局Session的存活時間,單位:分鐘 --> <session-config> <session-timeout>60</session-timeout> </session-config>
Session的作用域:
當我們需要將一些數據信息存入Session的時候,就需要操作Session作用域(SessionScope),它和請求作用域類似,也有相應setAttitude和getAttribute方法,只不過Session作用域的範圍要比請求作用域更寬。請求作用域在一次請求響應只有就會消失(因為響應之後請求就會銷毀)。而Session對象只要瀏覽器不關閉或者未超時,那麼Session對象會一直駐留在伺服器端,因此不管重新請求 多少次還是轉發和重定向,都可以從Session中獲取之前保存的數據信息。
示例代碼:
User user = new User(); user.setUid("1001"); user.setUserName("wangl"); HttpSession session = request.getSession(); //將數據保存在會話作用域中 session.setAttribute("user", user);
從會話作用域取值
HttpSession session = request.getSession(); //取值 User user = (User)session.getAttribute("user");
URL重寫:
瀏覽器是可以禁用cookie的,一旦禁用了cookie,那麼SessionID將無法寫入cookie的緩存中,這樣就導致無法實現會話跟蹤了,因此解決辦法就是使用URL重寫。URL重寫的目的就是把SessionID放在請求URL地址的後面提交回伺服器(類似GET請求後面帶上參數,而這個參數就是一個SessionID),伺服器會解析和這個URL的地址並得到SessionID。
示例代碼:
//重寫請求的URL地址,這個地址後面會自動帶上SessionId String url = response.encodeRedirectURL("getSession"); //重定向URL response.sendRedirect(url);
瀏覽器地址演示
http://localhost:8080/ch07/getSession;jsessionid=6F1 BA8C92D7E5D7CC479ED8DD30D3ED0
註意:“;”後面跟著就是SessionID
常用API:
方法 | 說明 |
---|---|
getContextPath() | 獲取項目的相對路徑 |
getRealPath(String path) | 獲取項目的絕對路徑 |
getInitParameter(String name) | 獲取上下文的初始化參數(web.xml中配置的) |
setAttribute(String name, String value) | 將數據放入上下文作用域 |
getAttribute(String name) | 從上下文作用域中去獲取數據 |
上下文作用域:
上下文作用域是為當前項目所有Servlet提供的一個共用記憶體區域,可以將需要的數據信息保存在作用域中。這個作用域的的範圍是最大的,只要容器沒有停止,它就會一直存在。
三種作用域:
結合前面所學的作用域,那麼一共有三個,分別是:請求作用域,會話作用域,上下文作用域。
範圍從小到大來劃分:
請求作用域<會話作用域<上下文作用域
12.1 編寫過濾器
要實現一個過濾器,必須實現一個Filter介面,只有實現了這個介面的類才稱之為過濾器。
示例代碼
public class DemoFilter implements Filter{ ... }
web.xml配置過濾器:
<filter> <filter-name>demoFilter</filter-name> <filter-class>edu.nf.ch09.filter.DemoFilter</filter-class> <!-- 初始化參數 --> <init-param> <param-name>param</param-name> <param-value>hello</param-value> </init-param> </filter> <filter-mapping> <filter-name>demoFilter</filter-name> <!-- 什麼請求可以經過此過濾器,/*表示所有請求 --> <url-pattern>/*</url-pattern> </filter-mapping>
12.2 過濾器的生命周期
與Servlet類似,Filter同樣也是有容器負責創建和銷毀,與Servlet的區別在於,容器會在啟動的時候最先創建所有的過濾器,並執行init方法進行初始化。
生命周期方法: