實現02 3.實現任務階段3-處理Servlet02 3.3Servlet規範設計 3.3.1MyServlet 該類模仿Servlet介面,為了簡化,只聲明瞭三個方法:init(),service(),destroy() package com.li.MyTomcat.servlet; impor ...
實現02
3.實現任務階段3-處理Servlet02
3.3Servlet規範設計
3.3.1MyServlet
該類模仿Servlet介面,為了簡化,只聲明瞭三個方法:init(),service(),destroy()
package com.li.MyTomcat.servlet;
import com.li.MyTomcat.http.MyRequest;
import com.li.MyTomcat.http.MyResponse;
import java.io.IOException;
/**
* @author 李
* @version 1.0
* 只保留了三個核心的方法聲明
*/
public interface MyServlet {
void init() throws Exception;
void service(MyRequest request, MyResponse response) throws IOException;
void destroy();
}
3.3.2MyHttpServlet
該類實現MyServlet介面,並聲明瞭doGet(),doPost()方法讓MyHttpServlet的子類去實現
package com.li.MyTomcat.servlet;
import com.li.MyTomcat.http.MyRequest;
import com.li.MyTomcat.http.MyResponse;
import java.io.IOException;
/**
* @author 李
* @version 1.0
*/
public abstract class MyHttpServlet implements MyServlet {
@Override
public void service(MyRequest request, MyResponse response) throws IOException {
//equalsIgnoreCase 比較字元串內容是否相同,不區分大小寫
if ("GET".equalsIgnoreCase(request.getMethod())) {
//這裡會有動態綁定
this.doGet(request, response);
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
//這裡會有動態綁定
this.doPost(request, response);
}
}
//這裡我們使用一個模板設計模式
//讓MyHttpServlet的子類去實現
public abstract void doGet(MyRequest request, MyResponse response);
public abstract void doPost(MyRequest request, MyResponse response);
}
3.3.3MyCalServlet
業務類servlet
package com.li.MyTomcat.servlet;
import com.li.MyTomcat.http.MyRequest;
import com.li.MyTomcat.http.MyResponse;
import com.li.MyTomcat.utils.WebUtils;
import java.io.IOException;
import java.io.OutputStream;
/**
* @author 李
* @version 1.0
*/
public class MyCalServlet extends MyHttpServlet {
@Override
public void doGet(MyRequest request, MyResponse response) {
//動態綁定機制,每次調用doGet方法都會執行service方法
//業務代碼
//完成計算任務
int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);
int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);
int sum = num1 + num2;
//返回結算結果給瀏覽器
//outputStream和當前連接的socket關聯
OutputStream outputStream = response.getOutputStream();
String respMes = MyResponse.respHeader + "<h1>" + num1 + "+" + num2 + "=" + sum + "</h1>";
try {
outputStream.write(respMes.getBytes());
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(MyRequest request, MyResponse response) {
doGet(request, response);
}
@Override
public void init() throws Exception {}
@Override
public void destroy() {}
}
3.3.4WebUtils
該類為工具類,供MyCalServlet使用
package com.li.MyTomcat.utils;
public class WebUtils {
public static int parseInt(String str, int defaultVal) {
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
System.out.println(str + " 不能轉成數字");
}
return defaultVal;
}
}
3.3.5RequestHandler
修改RequestHandler類(線程類),如下:
package com.li.MyTomcat.hander;
import com.li.MyTomcat.http.MyRequest;
import com.li.MyTomcat.http.MyResponse;
import com.li.MyTomcat.servlet.MyCalServlet;
import java.io.*;
import java.net.Socket;
/**
* @author 李
* @version 1.0
* RequestHandler是一個線程對象
* 用來處理一個http請求
*/
public class RequestHandler implements Runnable {
//定義一個Socket
private Socket socket = null;
//在創建RequestHandler對象的時候,將主線程的socket傳給線程對象來使用
public RequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//對客戶端進行交互
try {
System.out.println("當前線程=" + Thread.currentThread().getName());
InputStream inputStream = socket.getInputStream();
MyRequest myRequest = new MyRequest(inputStream);
//這裡我們可以通過myResponse對象返回數據給客戶端
MyResponse myResponse = new MyResponse(socket.getOutputStream());
//創建MyCalServlet對象(之後再用反射來構建對象,而不是new)
MyCalServlet myCalServlet = new MyCalServlet();
//之後再用反射來調用方法
myCalServlet.doGet(myRequest, myResponse);
inputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
//一定要確保socket關閉
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
MyTomcatV2類不變
3.3.6測試
運行MyTomcatV2,在瀏覽器中輸入地址http://localhost:8080/myCalServlet?num1=100&num2=200
,回車,瀏覽器顯示如下:
3.4容器設計
在上面的線程類RequestHandler中,我們是通過new來創建一個 實現了Servlet介面 的對象,但是實際的tomcat是反射來實例化該對象的。
一個問題:Servlet應該在哪裡被實例化,以及如何去管理呢?
思路:
將來的Servlet會很多,我們不可能去到MyTomcat源碼裡面去修改,應該用web.xml文件去配置Servlet
為了管理,應該創建兩個容器(使用HashMap或ConcurrentHashMap)
一個容器(ServletMapping)存放 :HashMap<ServletName,對應的實例>
另一個容器(ServletUrlMapping)存放:HashMap<url-pattern,ServletName>
當瀏覽器發送請求,我們獲取該請求的uri部分,去容器ServletUrlMapping裡面去查該uri,如果有,通過該uri獲取對應的ServletName。然後到另一個容器ServletMapping,根據ServletName去找對應的反射對象的實例,然後再去調用它。
3.4.1容器實現
- 首先在web.xml文件中配置自己設計的Servlet
<!--配置自己設計的Servlet-->
<servlet>
<servlet-name>MyCalServlet</servlet-name>
<!--忽略報錯-->
<servlet-class>com.li.MyTomcat.servlet.MyCalServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyCalServlet</servlet-name>
<url-pattern>/myCalServlet</url-pattern>
</servlet-mapping>
註意:<servlet-class>這裡會報錯,因為非原生的servlet,xml不會識別,我們忽l略報錯即。
記得在target\classes目錄手動拷貝一份web.xml [預設是自動拷貝,這裡不是原生tomcat,因此不會自動拷貝],配置信息可以從上一個項目web.xml拷貝修改即可。所有報紅都忽略
- 註意在pom.xml文件中導入dom4j的jar包:
<!--導入dom4j-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.1</version>
</dependency>
- MyTomcatV3
package com.li.MyTomcat;
import com.li.MyTomcat.hander.RequestHandler;
import com.li.MyTomcat.servlet.MyHttpServlet;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author 李
* @version 3.0
* 第三版MyTomcat,實現通過xml和反射來初始化容器
* (這裡設置為一啟動MyTomcat就實例化servlet)
*/
public class MyTomcatV3 {
//1.容器 ServletMapping 存放 ConcurrentHashMap<ServletName,對應的實例>
public static final ConcurrentHashMap<String, MyHttpServlet>
servletMapping = new ConcurrentHashMap<>();
//2.容器 ServletUrlMapping 存放 ConcurrentHashMap<url-pattern,ServletName>
public static final ConcurrentHashMap<String, String>
servletUrlMapping = new ConcurrentHashMap<>();
public static void main(String[] args) {
MyTomcatV3 myTomcatV3 = new MyTomcatV3();
myTomcatV3.init();
//啟動MyTomcatV3容器
myTomcatV3.run();
}
//啟動MyTomcatV3容器
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("=====MyTomcatV3在8080監聽=====");
while (!serverSocket.isClosed()) {
Socket socket = serverSocket.accept();
RequestHandler requestHandler = new RequestHandler(socket);
new Thread(requestHandler).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//直接對兩個容器進行初始化
public void init() {
//讀取web.xml文件 => dom4j(註意導jar包)
//path=/D:/IDEA-workspace/li_tomcat/target/classes/
String path = MyTomcatV3.class.getResource("/").getPath();
//使用dom4j技術完成web.xml文件的讀取
SAXReader reader = new SAXReader();
try {
Document document = reader.read(new File(path + "web.xml"));
//得到根元素
Element rootElement = document.getRootElement();
//得到根節點下麵的所有元素
List<Element> elements = rootElement.elements();
//遍歷並過濾到servlet和servlet-mapping
for (Element element : elements) {
//如果元素為servlet
if ("servlet".equalsIgnoreCase(element.getName())) {
//這是一個servlet配置
//System.out.println("發現servlet");
//使用反射將該servlet實例放入到容器servletMapping中
String servletName = element.element("servlet-name").getText().trim();
String servletClass = element.element("servlet-class").getText().trim();
servletMapping.put(servletName,
(MyHttpServlet) Class.forName(servletClass).newInstance());
} else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
//如果元素為servlet-mapping
//System.out.println("發現servlet-mapping");
String servletName = element.element("servlet-name").getText().trim();
String urlPattern = element.element("url-pattern").getText().trim();
//將uri對應的servletName放入容器servletUrlMapping中
servletUrlMapping.put(urlPattern, servletName);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改RequestHandler:
package com.li.MyTomcat.hander;
import com.li.MyTomcat.MyTomcatV3;
import com.li.MyTomcat.http.MyRequest;
import com.li.MyTomcat.http.MyResponse;
import com.li.MyTomcat.servlet.MyHttpServlet;
import java.io.*;
import java.net.Socket;
/**
* @author 李
* @version 1.0
* RequestHandler是一個線程對象
* 用來處理一個http請求
*/
public class RequestHandler implements Runnable {
//定義一個Socket
private Socket socket = null;
//在創建RequestHandler對象的時候,將主線程的socket傳給線程對象來使用
public RequestHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//對客戶端進行交互
try {
System.out.println("當前線程=" + Thread.currentThread().getName());
MyRequest myRequest = new MyRequest(socket.getInputStream());
//這裡我們可以通過myResponse對象返回數據給客戶端
MyResponse myResponse = new MyResponse(socket.getOutputStream());
//得到瀏覽器發送的http的uri,作為key,去查找容器里該servletName
//然後去另一容器里找實例,如有就調用,如果沒有就返回404
//1.得到uri=>就是servletUrlMapping的ur-pattern
String uri = myRequest.getUri();
String servletName = MyTomcatV3.servletUrlMapping.get(uri);
if (servletName == null) {
servletName = "";
}
//2.通過uri->servletName->servlet的實例
//註意:這裡myHttpServlet的編譯類型是MyHttpServlet,但是運行類型是MyHttpServlet的子類
MyHttpServlet myHttpServlet = MyTomcatV3.servletMapping.get(servletName);
//3.調用service方法,通過動態綁定機制,調用運行類型的doGet或doPost方法
if (myHttpServlet != null) {//如果找到到該servlet了
myHttpServlet.service(myRequest, myResponse);
} else {
//沒有這個servlet,返回404
String resp = MyResponse.respHeader + "<h1>404 Not Found</h1>";
OutputStream outputStream = myResponse.getOutputStream();
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.close();
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
//一定要確保socket關閉
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
為了測試,在MyCalServlet類中增加提示信息:
其他類保持不變
3.4.2測試
運行MyTomcatV3,在瀏覽器輸入 http://localhost:8080/myCalServlet?num1=99&num2=1
瀏覽器顯示如下:
在地址欄中輸入不存在的資源,返回404NotFound
後臺輸出:
4.練習
分析html等靜態資源,完成通過頁面提交數據進行計算,瀏覽器請求 http://localhost:8080/hspCalServlet
, 提交數據,完成計算任務,如果該servlet 不存在,返回 404
練習
思路:線上程類中,拿到瀏覽器請求的uri的時候,增加新的業務邏輯:
(1)判斷uri是什麼資源
(2)如果是靜態資源,就讀取該資源,並返回給瀏覽器(註意設置Content-Type text/html)
(3)因為是自定義的tomcat,所以maven項目的target不會自動更新
(4)需要自己將靜態文件等拷貝到該目錄下,瀏覽器才能訪問到(這裡把讀取的靜態資源放到target/classes/下)