Servlet簡介
1.Servlet是sun公司提供的一門用於開發動態web資源的技術
*靜態web資源:固定數據文件
*動態web資源:通過程序動態生成數據文件
2.Servlet技術基於Request-Response編程模型 ---- HTTP協議也是基於請求響應模型
*Servlet技術用來開發基於HTTP web應用程序
3.Servlet快速入門
1)創建web project
2)編寫class繼承HttpServlet
3)在web.xml配置Servlet程序,虛擬訪問路徑
*用戶在瀏覽器上通過這個路徑訪問編寫Servlet程序
4)覆蓋doGet或者doPost方法進行輸出
eg:
package cn.lsl.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class HelloServlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("get請求"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("post請求"); } }
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!-- 為HelloServlet配置瀏覽器可以訪問虛擬路徑 --> <servlet> <!-- 為Servlet程序命名 --> <servlet-name>HelloServlet</servlet-name> <!-- Servlet全路徑:包名.類名 --> <servlet-class>cn.lsl.servlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <!-- 為Servlet程序制定瀏覽器訪問虛擬路徑 --> <servlet-name>HelloServlet</servlet-name> <!-- 用戶在瀏覽器通過/hello訪問Servlet --> <url-pattern>/hello</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
4.Servlet動態生成網頁文件
eg:
package cn.lsl.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class HelloServlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("get請求"); //設置響應流編碼問題 resp.setContentType("text/html;charset=utf-8"); PrintWriter out = resp.getWriter();//獲得向瀏覽器輸出流 out.println("<html>"); out.println("<head>"); out.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">"); out.println("</head>"); out.println("<body>"); out.println("<h1>這是一個由Servlet動態生成頁面!</h1>"); out.println("</body>"); out.println("</html>"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("post請求"); } }
Servlet生命周期
1.Servlet接口定義了Servlet生命周期
init()方法:服務器調用該方法初始化Servlet
service()方法:初始化完畢,服務器調用該方法響應客戶的請求
destory()方法:服務器調用該方法消滅servlet對象
1)tomcat服務器啟動時,沒有創建Servlet對象
2)第一次訪問時,tomcat構造Servlet對象,調用init,執行service
3)從第二次以后訪問tomcat不會從新創建Servlet對象,也不會調用init ---- 每一次訪問都會調用service
4)當服務器重啟或正常關閉,調用destroy(正常關閉 shutdown.bat)
eg:
package cn.lsl.servlet; import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class InitServlet implements Servlet{ public InitServlet(){ System.out.println("構造了InitServlet對象..."); } @Override public void init(ServletConfig arg0) throws ServletException { // TODO Auto-generated method stub System.out.println("初始化...."); } @Override public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { // TODO Auto-generated method stub System.out.println("服務...."); } @Override public void destroy() { // TODO Auto-generated method stub System.out.println("銷毀...."); } @Override public ServletConfig getServletConfig() { // TODO Auto-generated method stub return null; } @Override public String getServletInfo() { // TODO Auto-generated method stub return null; } }
//第一次訪問輸出的結果:
//構造了InitServlet對象...
//初始化....
//服務....
//第二次訪問輸出的結果:
//服務....
其中,init()方法只在Servlet第一次被請求加載的時候被調用一次,當有客戶再請求Servlet服務時,Web服務器將啟動一個新的線程,在該線程中,調用service方法響應客戶的請求。
2.Servlet對象是tomcat創建的,每次請求調用Servlet中service方法,tomcat服務器會在每次調用Servlet的service方法時,為該方法創建Request對象和Response對象。
*JavaEE API中沒有Request和Response實現類 ----- 實現類由Servlet服務器提供的,tomcat提供實現類(weblogic提供實現類)。
Servlet是一個供其他Java程序(Servlet引擎)調用的Java類,它不能獨立運行,它的運行完全由Servlet引擎來控制和調度。
3.針對客戶端的多次Servlet請求,通常情況下,服務器只會創建一個Servlet實例對象,也就是說Servlet實例對象一旦創建,它就會駐留在內存中,為后續的其他請求服務,直至web容器退出,Servlet實例對象才會銷毀。
4.service方法和HttpServlet doGet/doPost關系區別?
在HttpServlet代碼實現中,根據請求方式不同 調用相應doXXX方法,get方式請求--- doGet post方式 --- doPost
5.配置Servlet隨tomcat服務器啟動時,進行初始化 --- <load-on-startup>
*<load-on-startup>參數可以是一個數字 0-9 代表服務器加載優先級,0最高
eg:
<servlet> <servlet-name>InitServlet</servlet-name> <servlet-class>cn.itcast.servlet.InitServlet</servlet-class> <!-- 配置 Servlet在服務器啟動時 進行加載 --> <load-on-startup>1</load-on-startup> </servlet>
總結:
在Servlet的整個生命周期內,Servlet的init方法只被調用一次。而對一個Servlet的每次訪問請求都導致Servlet引擎調用一次servlet的service方法。
對於每次訪問請求,Servlet引擎都會創建一個新的HttpServletRequest請求對象和一個新的HttpServletResponse響應對象,然后將這兩個對象作為參數傳遞給它調用的Servlet的service()方法,
service方法再根據請求方式分別調用doXXX方法。
路徑匹配
一個Servlet可以配置多個url-pattern
URL配置格式三種:
1.完全路徑匹配(以/開始)例如:/hello /init
查錯:
* 當前工程沒有被正確發布,訪問該工程所有靜態資源、動態資源 發生404 ----- 工程啟動時出錯了
* 查看錯誤時 分析錯誤
1) 單一錯誤 : 從上到下 查看第一行你自己寫代碼 (有的錯誤與代碼無關,查看錯誤信息)
2)復合錯誤 Caused by ---- 查看最后一個Caused by
* Invalid <url-pattern> init2 in servlet mapping
2.目錄匹配(以/開始) 例如:/* /abc/*
/代表網站根目錄
3.擴展名匹配(不能以/開始) 例如: *.do *.action
典型錯誤 /*.do
優先級:完全匹配>目錄匹配>擴展名匹配
路徑問題
編寫九九乘法表
1、需要用戶在客戶端輸入一個數字
2、Servlet接受客戶端輸入數字,打印對應乘法表
eg:
package cn.lsl.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ChengfabiaoServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //獲得請求提交數據number String numberStr = request.getParameter("number"); int number = 0; try { number = Integer.parseInt(numberStr); } catch (NumberFormatException e) { throw new RuntimeException("輸入的不是整數"); } //打印九九表 PrintWriter out = response.getWriter(); for(int i=1; i<=number; i++){ for(int j=1; j<=i; j++){ out.print(j + "*" + i + "=" + j * i + " "); } out.print("<br>"); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
在根目錄下的chengfabiao.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>乘法表</title> </head> <body> <!-- 相對路徑 chengfabiao --> <form action="/ServletTest/chengfabiao" method="post"> 請輸入一個數字<input type="text" name="number"> <input type="submit" value="打印乘法表"/> </form> </body> </html>
在aaa文件夾下的chengfabiao.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>乘法表</title> </head> <body> <!-- 相對路徑 ../chengfabiao --> <form action="/ServletTest/chengfabiao" method="post"> 請輸入一個數字<input type="text" name="number"> <input type="submit" value="打印乘法表"/> </form> </body> </html>
分析:
在chengfabiao.html 通過 action 訪問 ChengfabiaoServlet 路徑可以用絕對路徑和相對路徑
相對路徑:相對當前網頁地址 路徑 例如 chengfabiao ./chengfabiao ../chengfabiao
例如: http://localhost/ServletTest/chengfabiao.html 提交 action="chengfabiao"
* 將url最后地址換成相對路徑
結果: http://localhost/ServletTest/chengfabiao ----- 服務器端 /chengfabiao
例如: http://localhost/ServletTest/aaa/chengfabiao.html 提交 action="chengfabiao"
結果: http://localhost/ServletTest/aaa/chengfabiao ----- 服務器 /chengfabiao
* /aaa/chengfabiao 與服務器 /chengfabiao 不匹配 出現404
http://localhost/ServletTest/aaa/chengfabiao.html 提供 action="../chengfabiao"
結果:http://localhost/ServletTest/aaa/../chengfabiao ---- > ..和/aaa抵消 http://localhost/ServletTest/chengfabiao 可以匹配服務器 /chengfabiao
結論:如果用相對路徑提交請求,考慮當前路徑, 當前訪問服務器資源路徑不同 ---- 相對路徑寫法不同
絕對路徑 解決相對路徑,會根據當前地址改變問題。 例如: /ServletTest/chengfabiao 、http://localhost/ServletTest/chengfabiao
絕對路徑 以/開始 /訪問服務器根目錄
例如: 客戶端訪問服務器,不管當前路徑是什么 --- / 服務器根目錄 http://localhost
/ServletTest --- 找到虛擬目錄ServletTest工程 /ServletTest/chengfabiao --- 找到 ServletTest工程下配置 虛擬路徑/chengfabiao
結論: 客戶端路徑 /工程虛擬目錄/servlet虛擬路徑 例如:/ServletTest/chengfabiao
服務器端 配置web.xml 不需要寫工程虛擬目錄 只要直接寫/servlet虛擬路徑 例如:/chengfabiao
ServletConfig
1.init方法 --- init(ServletConfig) --- 通過ServletConfig獲得Servlet初始化參數
2.在web.xml中<servlet>標簽內通過<init-param>標簽為Servlet配置初始化參數
<init-param> <param-name>address</param-name> <param-value>軟件園</param-value> </init-param>
3.在Servlet程序中通過ServletConfig對象獲得address對應數據
getInitParameter ----- 通過name獲得value
getInitParameterNames ---- 獲得所有name
*思考:如何在doGet 或 doPost 方法中 獲得 Servlet初始化參數
GenericServlet 已經將ServletConfig 保存成員變量 ----- 在子類中通過 getServletConfig方法 獲得 初始化參數
結論:子類Servlet不需要覆蓋init(ServletConfig),只需要通過GenericServlet中getServletConfig()獲得ServletConfig對象
應用:在init-param 指定配置文件位置和名稱,配置Servlet隨服務器啟動創建 load-on-startup
*ServletConfig配置初始化數據,只能在配置Servlet獲得,其他Servlet無法獲得 --- 每個Servlet程序都對應一個ServletConfig對象
ServletContext
ServletContext是Servlet上下文對象
每一個工程對應創建單獨ServletContext對象,這個對象代表當前web工程
操作ServletContext必須通過ServletConfig獲得對象
應用:
1.獲得整個web應用初始化參數
<!-- 配置全局初始化參數,所有Servlet都可以訪問 --> <context-param> <param-name>hobby</param-name> <param-value>唱歌</param-value> </context-param>
package cn.lsl.servlet; import java.io.IOException; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ContextServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //獲取hobby全局參數 //通過ServletConfig獲得ServletContext //ServletContext context = getServletConfig().getServletContext(); //上面寫法可以簡化一下 ServletContext context = getServletContext(); //讀取全局初始化參數 System.out.println(context.getInitParameter("hobby")); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }
*ServletConfig對象配置參數,只對配置Servlet有效(ServletConfig每個Servlet對應一個)
ServletContext對象配置參數對所有Servlet都可以訪問(ServletContext每個工程(web應該)對應一個)
config參數<init-param>
context參數<context-param>
2.實現全局數據共享
package cn.lsl.servlet; import java.io.IOException; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class CountServlet extends HttpServlet { @Override public void init() throws ServletException { //想ServletContext保存訪問次數 ServletContext context = getServletContext(); //保存數據setAttribute context.setAttribute("visittimes", 0); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //從ServletContext中獲得訪問次數 ServletContext context = getServletContext(); int times = (Integer)context.getAttribute("visittimes"); //訪問次數+1 times++; //將訪問次數更新回去 ServletContext context.setAttribute("visittimes", times); System.out.println("網站被訪問了一次"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
package cn.lsl.servlet; import java.io.IOException; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class CountShowServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext context = getServletContext(); int times = (Integer)context.getAttribute("visittimes"); response.getWriter().println("web site has been visitted:" + times + " times!"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }
在ServletContext中,保存站點訪問次數,每一個用戶訪問站點,將訪問次數+1
在ServletContext初始化過程中,向ServletContext保存訪問次數0,-------ServletContext setAttribute
每次訪問次數+1 --- 數據存放ServletContext中,所有Servlet都可以獲得該數據
*在ServletContext中保存數據,所有Servlet都可以訪問
3.實現服務器端轉發功能
什么是轉發?轉發和重定向區別?
轉發:如果服務器端處理客戶端請求時,如果需要多個服務器程序同時進行處理,需要采用轉發操作,轉發操作對客戶端一次請求,在服務器端通過多個程序連續執行,進行處理
重定向:服務器進行處理后,需要通知客戶端訪問下一個目標程序繼續處理
區別:
1)轉發產生一次請求一次響應,重定向產生兩次請求兩次響應
2)轉發對客戶端不可見的(未知),重定向客戶端可以察覺到(可見)
3)轉發是url顯示轉發錢資源路徑,重定向url顯示定向后資源路徑
使用轉發還是重定向? --- 轉發性能好於重定向,請求次數少
轉發的例子
eg:
package cn.lsl.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LetterCountServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String content = request.getParameter("content"); content = content.toUpperCase(); //將內容轉為大寫 //分析統計 --- 忽略大小寫 int times[] = new int[26]; //遍歷每一個字母 for(int i=0; i<content.length(); i++){ char c = content.charAt(i); //判斷字母是不是26個字母之一 if(Character.isLetter(c)){ times[c - 'A']++; } } //交個下一個Servlet顯示,將統計結果保存ServletContext ServletContext context = getServletContext(); context.setAttribute("times", times); //轉發跳轉另一個Servlet RequestDispatcher dispatcher = context.getRequestDispatcher("/servlet/result"); dispatcher.forward(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }
package cn.lsl.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LetterResultServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //從ServletContext獲得數據 ServletContext context = getServletContext(); int[] times = (int[])context.getAttribute("times"); response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); for(int i=0; i<times.length; i++){ char c = (char)('A' + i); //次數 int ltimes = times[i]; out.println("字母:" + c + "出現了" + ltimes + "次!<br/>"); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }
eg:
package cn.lsl.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("AServlet 執行..."); // 轉發 ServletContext context = getServletContext(); // 轉發給BServlet 這里 / 當前web 工程 /day05 RequestDispatcher dispatcher = context.getRequestDispatcher("/B"); dispatcher.forward(request, response); // // 重定向 / 代表當前web服務器 // response.setStatus(302); // response.setHeader("Location", "/ServletTest/B"); // 從客戶端訪問,必須含有工程路徑 } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }
package cn.lsl.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class BServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("BServlet 執行 ..."); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }
4.讀取web工程資源文件
package cn.lsl.servlet; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ReadFileServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //讀取a1.txt出現WEB-INF/classes String filename2 = "/WEB-INF/classes/a1.txt"; filename2 = getServletContext().getRealPath(filename2); readfile(filename2); //讀取a2.txt位於網站根目錄 String filename = "/a2.txt"; //通過/開始路徑獲得絕對磁盤路徑 filename = getServletContext().getRealPath(filename); System.out.println(filename); readfile(filename); //因為a1.txt位於/WEB-INF/classes --- 類路徑中 --- 通過Class對象讀取文件 Class c = ReadFileServlet.class; //返回磁盤絕對路徑 String filename3 = c.getResource("/a1.txt").getFile(); System.out.println(filename3); readfile(filename3); } public static void readfile(String filename) throws IOException{ BufferedReader in = new BufferedReader(new FileReader(filename)); String line; while((line = in.readLine()) != null){ System.out.println(line); } in.close(); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }
使用Servlet讀取文件,只能讀取WebRoot下所有文件 --- 必須使用絕對磁盤路徑
通過站點根目錄絕對路徑 獲得磁盤絕對路徑 ------ getServletContext().getRealPath(“/WEB-INF/info.txt”)
* 因為 WEB-INF/classes 非常特殊 (存放.class文件目錄),被類加載器加載,通過Class類對象讀取 該目錄下文件
String filename3 = c.getResource("/a1.txt").getFile(); ----- / 代表 /WEB-INF/classes
結論:在web工程中,必須將 文件路徑轉換絕對磁盤路徑 c:\xxx e:\xxx\xxx ----- getServletContext().getRealPath("/xxx"); /代表WebRoot
如果讀取文件 恰好位於 WEB-INF/classes ----- 通過 類名.class.getResource("/文件名").getFile(); 獲得絕對磁盤路徑 / 代表 /WEB-INF/classes
缺省Servlet
缺省Servlet功能:處理其他Servlet都不處理請求
tomcat/conf/web.xml org.apache.catalina.servlets.DefaultServlet作為缺省Servlet
package cn.lsl.servlet; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class DefaultServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("缺省Servlet執行了"); //將用戶訪問 //1.獲得用戶訪問目標資源路徑 String path = request.getRequestURI().substring(request.getContextPath().length()); System.out.println(path); //2.判斷文件是否存在 --- 讀取磁盤絕對路徑 String filename = getServletContext().getRealPath(path); File file = new File(filename); if(file.exists()){ InputStream in = new FileInputStream(file); OutputStream out = response.getOutputStream(); int b; while((b = in.read()) != -1){ out.write(b); } in.close(); out.close(); }else{ response.setStatus(404); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }