自己動手寫一個“tomcat”
很多初學或將學java web的朋友總是被一系列異於常規java project的流程結構所困惑,搞不清事情的本質,這裡就以最簡單的方式來讓初出茅廬的新手對java web專案有個清晰明瞭的認識。
學java web的必定先行學過java基礎,眾所周知,java專案運行於一個public類中的一個pulblic static void main(String[])函式,然而java web中既沒有了常規的main方法入口點同時各種紛亂的東西如jsp、html、tomcat以及servlet等也很容易讓人陷入迷茫,就此使許多人在心中把java專案與web專案之間划起了天塹鴻溝。這裡就帶著這些問題看我給大家簡單寫上一個簡單的“tomcat”來幫助初學者把java web與常規java專案統一起來,以利於朋友們在最初就能對java web專案有個較常規的認識。
首先,我們來研究下java web專案的執行過程。與普通java程式的main入口點不同,web似乎完全找不到了其入口點,然而需明確一點的是web專案中的servlet本身就是java類,同樣是需要編譯成.class被載入的,即使是jsp檔案也是會經由jsp引擎轉化為一個servlet被執行(html檔案則僅被用來呆板的傳輸給雙方瀏覽器解釋)。所以web專案本質上還是一個java專案。那麼它與傳統java專案的差異又該作何解釋?java專案根據程式流程觸發,web專案則是基於網路訪問事件觸發,故本質上是一個事件驅動系統,然而任何事件驅動系統本身還是有一個確定的入口的,這點web也不例外。入口是有的,main也是有的,只是這些東西都被隱藏了起來,就是tomcat(亦或是其他web容器)的入口。這一點正如微軟的MFC庫封裝了c++常規的main或WinMain入口一樣,Tomcat也封裝了java的main入口。
下面我們就來動手寫一個簡單的tomcat模擬程式,此處必然繞不開java的反射機制(事實上所謂的Bean、控制反轉、依賴注入等概念均離不開反射機制)。
1.首先我們來建立一個java專案Tomcat,建立一個Servlet.MyServletIn介面,新增一個service方法,以模擬servlet中的service方法。
2.將我們的Servlet.MyServletIn介面匯出為ServletPkg.jar檔案作為我們的servlet庫。
3.在專案根目錄下新增TomcatConf.txt檔案作為Tomcat的配置檔案,這裡簡單起見採用普通文字而非Xml檔案,此檔案中存放兩行內容 ,第一行所要部署servlet專案目錄,第二行你自己的真實Servlet類名(包含包路徑)。
4.建立一個Core.TomcatCore類,並在其中新增main,作為整個容器的入口,在main中完成初始化tomcat本身、通過TomcatConf.txt配置檔案下的servlet檔案系統路徑及類包路徑資訊,載入所部署servlet等工作。
5.另建立一個java專案MyWeb(此專案不需要main,用來模擬我們的web專案,當然這裡只有servlet而已。)
6.將我們的”Servlet庫“ServletPkg.jar匯入我們的”Web專案“。
7.在MyWeb中新增一個實現了MyServletIn介面的類MyServlet。
8.實現MyServlet的抽象方法service模模擬實Servlet的行為。
9.部署我們的"web專案“到我們的Tomcat,即將我們的"web專案"根下的bin路徑寫入Tomcat根下的配置檔案(TomcatConf.txt)的第一行,並將我們的Servlet類名寫入配置檔案第二行。如下:
F:\Users\smy\workspace-eclipse\MyServlet\bin
MyWeb.MyServlet
10.執行我們的"tomcat"。
下面看具體程式碼實現:
Tomcat專案:
TomcatCore.java :
package Core;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import Servlet.MyServletIn;
public class TomcatCore {
public static void main(String[] args) throws InterruptedException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
// TODO Auto-generated method stub
System.out.println("Hellow,I'm MyTomcat ,and I'm in the initialization... ");
Thread.sleep(3000);
System.out.println("Now,I have finished the initialization .");
Thread.sleep(1000);
System.out.println("Now I will start the Servlet.");
//讀取配置檔案獲取Servlet的專案路徑名及類包路徑名
FileReader reader = new FileReader("./TomcatConf.txt");
BufferedReader br = new BufferedReader(reader);
String path = br.readLine();
String className = br.readLine();
br.close();
reader.close();
//根據已獲取Servlet的專案路徑名及類包路徑名通過URL類載入器載入檔案系統中的某個.class
File file = new File(path);
URL url = file.toURI().toURL();//這裡取檔案系統的URL地址
@SuppressWarnings("resource")
URLClassLoader loader = new URLClassLoader(new URL[]{url});//建立持有我們所部署的"web專案"路徑的URL類載入器,以使Tomcat之外的"web"納入Tomcat的classpath之中。
Class<?> tidyClazz = loader.loadClass(className); //利用反射載入類
MyServletIn serv=(MyServletIn)tidyClazz.newInstance();//轉化為Servlet介面執行service操作。
serv.service();//當然,實際的tomcat並不在這裡呼叫service,而僅僅是進入事件迴圈,在有瀏覽器請求時才呼叫service。
}
}
MyServletIn.java:
package Servlet;
public interface MyServletIn {
public void service();
}
TomcatConf.txt:
F:\Users\smy\workspace-eclipse\MyWeb\bin
MyWeb.MyServlet
現在來看看我們的"Web專案"----MyWeb:
MyServlet.java:
package MyWeb;
import Servlet.MyServletIn;
public class MyServlet implements MyServletIn {
public void service() {
// TODO Auto-generated method stub
System.out.println("Hellow I'm the servlet.");
System.out.println("Now let's start our web http travel.");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
DAOOperator dao = new DAOOperator("Mysql");
try {
dao.operate();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Now I will beleave the web world...");
System.out.println("Bye-bye.");
}
}
DAOOperator.java:package MyWeb;
//這裡只是模擬資料庫操作
public class DAOOperator {
private String name;
public DAOOperator(String _name)
{
name = _name;
}
public void operate() throws InterruptedException
{
System.out.println("I will do much work...");
Thread.sleep(3000);
System.out.println(name + "DAOOperator exec succeed.");
}
}
OK!
整個專案的程式碼也就是這個樣子了,現在試執行Tomcat專案將會得到以下輸出:
1.Hellow,I'm MyTomcat ,and I'm in the initialization...
2.Now,I have finished the initialization .
3.Now I will start the Servlet.
4.Hellow I'm the servlet.
5.Now let's start our web http travel.
6.I will do much work...
7.MysqlDAOOperator exec succeed.
8.Now I will beleave the web world...
9.Bye-bye.
方便起見,我為每一行編上號碼。
首先第一行輸出顯示Tomcat會做一些自身的初始化,雖然這裡實質上只是列印這麼一句話而已但真正的容器的確是在這裡做了許多初始化的。
第二行第三行顯示tomcat完成初始化並將進入servlet,注意這裡我們的程式碼還執行在我們的Tomcat專案中。
而第四行開始我們的程式碼就進入了我們的MyWeb專案之中開始了我們的Web之旅,並在之後的數行做其他much work並操作資料庫直至最後退出web之旅。
然而實際的Tomcat並不會直接在這裡執行我們的service而是初始化web專案並在一個等待狀態中伺機觸發service方法,並進入其中的doXXX等函式處理實際請求,這裡僅本著突出特點的角度也是足夠了的。
可以看到整個Tomcat是完全沒有與Web專案產生直接的關聯的,這一點正符合實際的Tomcat與常規web專案的工作方式,兩者之間的聯絡僅在TomcatConf.txt中配置而已,我們的Tomcat通過此配置檔案而找到了我們的MyWeb專案並進入其中載入MyWeb的.class中另外實現的MyServlet類(這裡Tomcat本身是完全察覺不到此類的資訊的,這也正是反射的優雅之處,看到這裡聰明的朋友也許已經想到Bean以及依賴注入技術了吧,其實本質上是一回事,只是做了些巧妙地規定罷了)。
實際的Tomcat的配置檔案中將包含更豐富而未必複雜的配置,但本質還是雷同的。
我想到了這裡java專案與java web專案的內涵應該已經統一起來了吧,簡而言之,你可以寫java project,我也可以寫,只是某天恰巧有個貓貓愛好者寫個java project,而這個java project又恰好能理解所謂的jsp檔案並能承載servlet類以及其他一些複雜而強大的功能,隨後他又恰好為這個java project起了一個相當cute的名字tomcat,而我們也就可以基於此專門寫一些能被他載入的servlet,我們所寫的這些東西也就是所謂的Java Web專案了。
6.將我們的”Servlet庫“ServletPkg.jar匯入我們的”Web專案“。