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方法進行初始化。
生命周期方法:
方法 | 說明 |
---|---|
init | 初始化方法,容器啟動時執行一次 |
doFilter | 請求過濾方法,決定請求是否放行 |
destroy | 容器銷毀過濾器之前執行的方法 |
示例代碼:
public class DemoFilter implements Filter{ @Override public void destroy() { System.out.println("准備銷毀DemoFilter"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //FilterChain表示一個過濾鏈對象,因為過濾器可能會存在多個 //同時這個對象將決定是否放行當前請求, //放行的話則請求會繼續到達下一個過濾器或者servlet中 System.out.println("請求經過DemoFileer..放行"); chain.doFilter(request, response); System.out.println("響應前經過DemoFilter..."); } @Override public void init(FilterConfig config) throws ServletException { String name = config.getInitParameter("param"); System.out.println("初始化DemoFilter,獲取初始化參數:"+name); } }
12.3 過濾鏈
在一個web項目中可能存在多個過濾器,當有多個過濾器存在的時候就會形成一個過濾鏈。請求會按照過濾器鏈的順序一直傳遞下去,最終到達某個Servlet來處理請求。(注意:過濾鏈的順序是按照web.xml中的先后配置順序決定的)
配置示例:
<!-- 按先后順序配置 --> <!-- 配置第一個過濾器 --> <filter> <filter-name>firstFilter</filter-name> <filter-class>edu.nf.ch09.filter.FirstFilter</filter-class> </filter> <filter-mapping> <filter-name>firstFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 配置第二個過濾器 --> <filter> <filter-name>secondFilter</filter-name> <filter-class>edu.nf.ch09.filter.SecondFilter</filter-class> </filter> <filter-mapping> <filter-name>secondFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
13、監聽器
13.1 監聽作用域對象的創建於銷毀
監聽器 | 說明 |
---|---|
ServletRequestListener | 監聽請求對象的創建和銷毀 |
HttpSessionListener | 監聽會話對象的創建和銷毀 |
ServletContextListener | 監聽Servlet上下文對象的創建和銷毀 |
代碼實例:
1.請求對象監聽器
public class DemoRequestListener implements ServletRequestListener{ /** * 當請求對象銷毀后容器執行此方法 * 銷毀方法中同樣也有一個ServletRequestEvent事件對象 */ @Override public void requestDestroyed(ServletRequestEvent event) { //通過這個事件對象就可以獲取當前的請求對象 HttpServletRequest request = (HttpServletRequest)event.getServletRequest(); System.out.println("銷毀請求對象..."+request); } /** * 當請求對象創建之后容器調用此方法 * ServletRequestEvent這個參數就是一個事件對象 */ @Override public void requestInitialized(ServletRequestEvent event) { //通過這個事件對象就可以獲取當前的請求對象 HttpServletRequest request = (HttpServletRequest)event.getServletRequest(); System.out.println("初始化了請求對象..."+request); } }
web.xml 配置
<!-- 配置監聽器 --> <listener> <!-- 指定監聽器的完整類名 --> <listener-class>edu.nf.ch10.listener.DemoRequestListener</listener-class> </listener>
2.會話對象監聽器
public class DemoSessionListener implements HttpSessionListener{ /** * 監聽HttpSession對象的創建 * HttpSessionEvent參數是一個事件對象 * 通過它可以獲得當前的HttpSession */ @Override public void sessionCreated(HttpSessionEvent event) { HttpSession session = event.getSession(); System.out.println("創建了Session對象"+session); } /** * 監聽HttpSession對象的銷毀 */ @Override public void sessionDestroyed(HttpSessionEvent event) { HttpSession session = event.getSession(); System.out.println("銷毀了Session對象"+session); } }
注意:當第一次調用了request.getSession()方法創建Session時,監聽器才會起作用。
web.xml配置
<listener> <!-- 指定監聽器的完整類名 --> <listener-class>edu.nf.ch10.listener.DemoSessionListener</listener-class> </listener>
3.Servlet上下文監聽器
public class DemoContextListener implements ServletContextListener{ /** * 監聽ServletContext的銷毀 */ @Override public void contextDestroyed(ServletContextEvent event) { //通過事件對象獲取ServletContext ServletContext sc = event.getServletContext(); System.out.println("銷毀了ServletContext對象..."+sc); } /** * 監聽SerlvetContext的創建 */ @Override public void contextInitialized(ServletContextEvent event) { //通過事件對象獲取ServletContext ServletContext sc = event.getServletContext(); System.out.println("創建了ServletContext對象..."+sc); } }
web.xml
<listener> <!-- 指定監聽器的完整類名 --> <listener-class>edu.nf.ch10.listener.DemoContextListener</listener-class> </listener>
13.2 監聽作用域的操作
監聽器 | 說明 |
---|---|
ServletRequestAttributeListener | 監聽請求作用域的操作 |
HttpSessionAttributeListener | 監聽會話作用域的操作 |
ServletContextAttributeListener | 監聽Servlet上下文作用域的操作 |
示例代碼:
這里以HttpSessionAttributeListener說明,其他作用域監聽器用法相似。
public class DemoSessionAttributeListener implements HttpSessionAttributeListener{ /** * 當有數據添加到會話作用域時,執行此方法 */ @Override public void attributeAdded(HttpSessionBindingEvent event) { //獲取存入作用域的鍵和值 System.out.println("存入會話作用域..."+event.getName() + " : " + event.getValue()); } /** * 當從會話作用域移除數據時,執行此方法 */ @Override public void attributeRemoved(HttpSessionBindingEvent event) { System.out.println("移除會話作用域..."+event.getName() + " : " + event.getValue()); } /** * 當替換了會話作用域中的某個數據時,執行此方法 */ @Override public void attributeReplaced(HttpSessionBindingEvent event) { HttpSession session = event.getSession(); System.out.println("替換的值: "+session.getAttribute("userName").toString()); //注意:這里event.getValue()獲取到的是被替換的值 System.out.println("替換會話作用域..."+event.getName() + " : " + event.getValue()); } }
web.xml
<listener> <!-- 指定監聽器的完整類名 --> <listener-class>edu.nf.ch10.listener.DemoSessionAttributeListener</listener-class> </listener>
Servlet3.0開始提供了一系列的注解來配置Servlet、Filter、Listener等等。這種方式可以極大的簡化在開發中大量的xml的配置。從這個版本開始,web.xml可以不再需要,使用相關的注解同樣可以完成相應的配置。
注解 | 說明 |
---|---|
@WebServlet | 這個注解標識在類上,用於配置Servlet。例如:@WebServlet(name="hello",urlPatterns="/hello")也可以簡化配置@WebServlet("/hello") |
@WebFilter | 這個注解標識在類上,用於配置Filter。例如:@WebFilter(filterName="encode",urlPatterns="/*")也可以簡化配置@WebFilter("/*") |
@WebListener | 這個注解標識在類上,用於配置監聽器 |
從Servlet3.0開始提供了文件上傳的功能,操作起來更加的簡單和方便。
15.1
想要使用這個功能,首先必須在web.xml或者使用注解開啟Servlet的上傳功能,否則無效
xml配置:
<servlet> <servlet-name>upload</servlet-name> <servlet-class>edu.demo.UploadServlet</servlet-class> <!-- 開啟上傳功能 --> <multipart-config/> </servlet> <servlet-mapping> <servlet-name>upload</servlet-name> <url-pattern>/upload</url-pattern> </servlet-mapping>
注解配置:
@WebServlet("/login") @MultipartConfig //開啟上傳功能 public class LoginServlet extends HttpServlet{ ... }
參數說明:
參數 | 說明 |
---|---|
location | 指定文件上傳的目錄 |
maxFileSize | 限制單個文件上傳的大小 |
maxRequestSize | 限制一次請求上傳總文件的大小 |
fileSizeThreshold | 設置緩存的大小,當達到緩存大小的時候,會將內存的數據寫入location指定的目錄中(也就是寫入磁盤) |
15.2
文件上傳的核心接口是Part,通過HTTPServletRequest對象可獲得該接口的實體類對象
//獲取單個文件的Part Part part = request.getPart("name"); //獲取多個上傳文件的Part Collection<Part> cool=request.getParts();
常用API:
方法 | 說明 |
---|---|
getContentType() | 獲取上傳的文件類型 |
getSize() | 獲取上傳文件的大小 |
getSubmittedFileName() | 獲取上傳文件的文件名 |
write() | 將文件上傳(寫入)到指定位置 |
5.13
當客戶端使用form表單來上傳文件時,必須將表單的enctype屬性設置為"multipart/form-data"
<form method="post" action="upload" enctype="multipart/form-data"> Username:<input type="text" name="userName"/><br/> <!-- input使用file類型 --> File:<input type="file" name="file"/><br/> <input type="submit" value="submit"/> </form>
案例:
@WebServlet("/upload") @MultipartConfig public class UploadServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //指定上傳的路徑 String uploadPath = "/Users/wangl/uploads"; //創建文件夾,如果不存在的情況下 File dir = new File(uploadPath); if(!dir.exists()){ dir.mkdir(); } //上傳單個文件,參數對用input的name屬性的值 //Part part = req.getPart("file"); //uploadPath = uploadPath + "/" + part.getSubmittedFileName(); //執行上傳 //part.write(uploadPath); //獲取文件的類型 //System.out.println(part.getContentType()); //獲取文件的大小 //System.out.println(part.getSize()); //獲取上傳的文件名 //System.out.println(part.getSubmittedFileName()); //上傳多個文件 Collection<Part> parts = req.getParts(); for (Part p : parts) { p.write(uploadPath + "/" + p.getSubmittedFileName()); } } }
upload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>文件上傳</h2> <form method="post" action="upload" enctype="multipart/form-data"> Username:<input type="text" name="userName"/><br/> File1:<input type="file" name="file"/><br/> File2:<input type="file" name="file"/><br/> <input type="submit" value="submit"/> </form> </body> </html>
1、簡介
JSP全名為Java Server Pages,中文名叫java服務器頁面,是一種動態頁面技術,而HTML是屬於靜態頁面JSP可以在HTML中嵌入java腳本代碼,因為JSP本質上還是一個Servlet,因此JSP也必須依賴於web容器才能運行。JSP的出現並不是為了取代Servlet,而是簡化了Servlet的工作,將Servlet中繁瑣的視圖呈現代碼脫離出來,交給JSP來完成,讓Servlet專注於請求的處理,所以在開發中通常將JSP和Servlet結合一起使用。
2、JSP引擎
由於JSP本質上就是一個Servlet,那么JSP引擎主要負責將JSP文件轉義成一個Servlet的java源文件,然后再通過javac將這個源文件,編譯成class字節碼文件並裝載到JVM中執行。JSP引擎的核心類是JSPServlet,位於Jasper.jar文件中,並且在Tomcat的web.xml中也默認就配置好了這個類(JspServlet也是一個Servlet)。因此,凡是以".jsp"結尾的請求都會先經過JspServlet,那么這個引擎就可以開始工作。通常引擎轉義和編譯后的文件放在容器的work工作目錄中。
注意:如果第一次訪問Jsp文件的時候,由於work目錄中並不存在源文件和字節碼文件,JSP引擎就必須完成這兩個工作,因此,有可能在第一次訪問JSP時會比較緩慢。當字節碼編譯出來加載后,第二次訪問時速度就會很快了。
3、JSP的三大元素
3.1 指令元素
語法:<%@ %>
指令 | 說明 |
---|---|
page指令 | 用於設置JSP頁面的相關信息以及編碼 |
include指令 | 用於靜態包含其他的JSP頁面代碼,所謂靜態包含,就是在編譯期,將另外的JSP頁面的代碼合並到當前的JSP中,最終只會產生一個java源文件 |
taglib | 這個指令用於引入標簽庫 |
3.2 動作元素
語法:< jsp:xxx >
動作 | 說明 |
---|---|
include | 動態包含其他JSP頁面的內容,所謂的動態包括是指在編譯期將不同的JSP文件轉義成不同java源文件,然后在運行時在將目標內容包含到當前的JSP頁面中 |
forward | 相當於Servlet中的轉發,轉發到其他的JSP頁面或者Servlet |
param | 用於傳遞參數,通常結合其他的動作一起使用,例如轉發時需要提交一些額外的參數 |
useBean | 用於在JSP頁面中使用JavaBean對象,通常結合setProperty和getProperty來使用,完成bean對象的賦值和取值操作 |
3.3 腳本元素
腳本元素主要就是在JSP中嵌入java腳本代碼,包括聲明、表達式、java腳本
聲明語法:<%! %>
表達式:<%= %>
java腳本:<% %>
示例代碼:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <%-- 聲明變量和方法,這里聲明的變量a是實例變量 --%> <%! int a = 10; public void say(){ System.out.println("hello"); } %> <%-- 表達式,注意:表達式后面是不允許有;號結束的 --%> 3 + 1 = <%=3+1%><br/> <%-- Java腳本,腳本代碼最終會生成在servlet中的service方法中作為代碼片段 --%> <table border="1"> <tr> <th>Name</th> <th>Age</th> </tr> <% for(int i=0;i<5;i++){%> <tr> <td>user<%=i%></td> <td><%=10+i%></td> </tr> <%}%> </table> </body> </html>
4、JSP內置對象
內置對象,是在容器運行時將創建好的9個對象內嵌在JSP中,在JSP里可以直接拿來使用的對象。
對象 | 說明 |
---|---|
out | 字符流輸出對象 |
config | 等同於Servlet中的ServletConfig |
page | 表示當前JSP頁面,類似於Java類中的this關鍵字 |
request | 等同於 Servlet中的HTTPServletRequest |
response | 等同於 Servlet中的HTTPServletResponse |
session | 等同於Servlet中的HTTPSession |
application | 等同於Servlet中的Servlet |
pageContext | 表示當前JSP頁面的上下文對象 |
exception | 表示當前JSP中的異常對象 |
示例代碼:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <%-- 常用內置對象 --%> <% //使用request,API使用同HttpServletRequest一樣 request.getParameter("userName"); request.setAttribute("user", "user1"); request.getAttribute("user"); //使用session,API使用等同於HttpSession session.setAttribute("user", "user2"); session.getAttribute("user"); session.getId(); //使用response //response.sendRedirect("demo.jsp"); //out對象,等同於字符輸出流對象,在JSP頁面輸出相關內容 out.println("hello world"); //這個是輸出在控制中 System.out.println("hello"); //使用application,等同於ServletContext application.setAttribute("userName", "user3"); //pageContext使用 //從pageContext中獲取相關的其他對象 HttpServletRequest req = (HttpServletRequest)pageContext.getRequest(); HttpServletResponse res = (HttpServletResponse)pageContext.getResponse(); HttpSession ses = pageContext.getSession(); ServletConfig conf = pageContext.getServletConfig(); ServletContext sc = pageContext.getServletContext(); //也可以通過pageContext來統一設置不同的作用域 //第三個參數表示要放入到哪個作用域,是一個int類型的參數 //1代表page作用域(當前頁面有效)PageContext.APPLICATION_SCONPE //2代表請求作用域 PageContext.SESSION_SCOPE //3代表會話作用域 PageContext.REQUEST_SCOPE //4代表上下文作用域 PageContext.PAGE_SCOPE pageContext.setAttribute("userName", "zhangsan", 2); //也可以指定中哪個作用域取出相應的值 String name = (String)pageContext.getAttribute("userName", 2); out.println(name); %> </body> </html>
EL(Expression Language),全球叫做表達式語言,是JSP2.0推出的一種技術。主要簡化了在JSP中使用Java腳本表達式。EL表達式的特點在於使用簡單,支持四則運算、邏輯運算等,並且還可以對Servlet API中的對象進行數據訪問。EL的語法:${expression}
運算:
示例 | 結果 |
---|---|
${1+1} | 2 |
${2*2} | 4 |
${1==1} | true |
${2>5} | false |
${"10"==10} | true |
其他 | ... |
數據訪問:
1.使用“.”來訪問
示例 | 說明 |
---|---|
${param.參數名} | 獲取請求參數的值,相當於使用request.getParameter()方法 |
${requestScope.xxx} | 從請求作用域中訪問數據 |
${sessionScope.xxx} | 從會話作用域中訪問數據 |
${applicationScope.xxx} | 從上下文作用域中訪問數據 |
${xxx} | 不指定作用域范圍時,默認按照作用域范圍從小到大的順序自動查找 |
其他 | ... |
示例代碼:
姓名:${param.userName}<br/> <%-- 訪問請求作用域 --%> 請求作用域:${requestScope.userName}<br/> 會話作用域: ${sessionScope.userName}<br/> 上下文作用域: ${applicationScope.userName}<br/> 自動從作用域中取值: ${userName}<br/> <h2>訪問對象中的屬性</h2> Name:${userName}<br/> Age:${age}
2.使用"[]"來訪問
示例 | 說明 |
---|---|
${requestScope[“userName”]} | 從請求作用域中取值 |
${sessionScope[“userName”]} | 從會話作用域取值 |
其他... | 同上 |
示例代碼:
<h2>使用"[]"來訪問數據</h2> <!-- 可以獲取使用帶.的參數名稱--> 請求作用域:${requestScope["user.userName"]}<br/> 會話作用域: ${sessionScope["user.userName"]}<br/> 上下文作用域: ${applicationScope["user.userName"]}<br/>
注意:通常使用"[]"來訪問數據的時候,主要是訪問一些特殊的名稱,例如:request.setAttribute("user.userName")
這種方式如果使用${requestScope.user.userName}是訪問不到的,應該改為
${requestScope["user.userName"]}來訪問
6、JSTL核心標簽庫
JSTL是JSP中的標准標簽庫,主要用於取代JSP中大量的Java腳本代碼,讓頁面看起來更趨向於HTML。使用也很簡單,通常結合EL表達式一起使用
示例:
核心標簽庫(core) | 說明 |
---|---|
c:out | 輸出標簽 |
c:set | 聲明某個變量並存入指定的作用域 |
c:redirect | 重定向標簽 |
c:if | 條件判斷 |
c:forEach | 循環標簽 |
其他 | ... |
備注:其他標簽庫請參閱相關官方文檔
代碼示例:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>JSTL</title> </head> <body> <% out.println("hello JSTL"); %> <!-- 使用輸出標簽替換上面的腳本 --> <c:out value="hello JSTL"></c:out> <% session.setAttribute("userName", "wangl"); %> <!-- 使用set標簽替換上面的腳本,var指定變量名,value指定值,scope指定要放入的作用域 --> <c:set var="userName" value="wangl" scope="session"></c:set> <!-- 重定向標簽 --> <%--<c:redirect url="demo.jsp"/>--%> <!-- 使用條件判斷 --> <c:set var="age" value="70"/> <c:if test="${age<=18}"> <h2>很年輕</h2> </c:if> <c:if test="${age>18 && age<50}"> <h2>還好</h2> </c:if> <c:if test="${age>60}"> <h2>老了</h2> </c:if> </body> </html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <!-- 聲明變量i,起始為0, 到10結束 --> <%-- <c:forEach var="i" begin="0" end="10"> ${i}<br/> </c:forEach> --%> <table border="1"> <tr> <th>姓名</th> <th>年齡</th> <th>性別</th> <th>籍貫</th> <th>班級</th> </tr> <!-- list中存放的map “List<Map<String,Object>>”--> <%-- <c:forEach items="${list}" var="m"> <tr> <td>${m.s_name}</td> <td>${m.s_age}</td> <td>${m.s_sex}</td> <td>${m.origin}</td> <td>${m.c_name}</td> </tr> </c:forEach> --%> <!-- list中放的是Object[] “List<Object[]>” --> <c:forEach items="${requestScope.list}" var="o"> <tr> <td>${o[0]}</td> <td>${o[1]}</td> <td>${o[2]}</td> <td>${o[3]}</td> <td>${o[4]}</td> </tr> </c:forEach> </table> </body> </html>