遠古期 - 靜態(tài)頁面時代
講Java Web開發(fā)的歷史進程牌芋,不得不提Web開發(fā)的歷史進程乎完。
在互聯(lián)網(wǎng)剛發(fā)展的時候,那時候的網(wǎng)站功能是很簡單的亡容。那時候的網(wǎng)站還都是靜態(tài)的嗤疯。這里所說的靜態(tài)是指,請求訪問的網(wǎng)頁都是事先編輯好的萍倡,不能改變的身弊。
這里先講下當時一個請求是如何返回結(jié)果的。
比如列敲,你想訪問新浪上的一張圖片阱佛,會在瀏覽器鍵入這個圖片的地址:
瀏覽器會根據(jù)地址像新浪服務器發(fā)送HTTP請求。新浪服務器上的HTTP Server接收到請求后戴而,會根據(jù)路徑地址/img/12345.jpg
查找的這個文件凑术,然后read文件,再把圖片數(shù)據(jù)發(fā)送給客戶端所意,客戶端的瀏覽器就能正確展示圖片了淮逊。
也就是說,這里的URL對服務器來說就是查找文件的地址扶踊,而文件必須實實在在存在于服務器中的特定目錄下的泄鹏。
缺點
很明顯,訪問的資源必須事先已經(jīng)存在秧耗,否則訪問不到备籽。而動態(tài)展示也是沒法實現(xiàn)的。比如:某人剛發(fā)布了一篇文章分井,想在首頁立即看到是不可能的车猬。只能重新手動編輯首頁,把文章鏈接加進去
混沌期 - CGI時代
然而尺锚,如果頁面一直是靜態(tài)的額珠闰,也就不會有現(xiàn)在紛繁復雜的網(wǎng)站了。那么動態(tài)展示頁面的解決方案是什么呢瘫辩?是CGI伏嗜!
CGI全稱是通用網(wǎng)關(guān)接口(Common Gateway Interface)。那么它的作用是啥呢伐厌?
CGI是啥
首先阅仔,要清楚CGI是啥?
CGI是一個可執(zhí)行的程序或者可運行的腳本弧械。幾乎所有語言都能寫CGI八酒,像python,C刃唐,甚至shell羞迷。
舉個例子界轩。下面一段C代碼,經(jīng)過編譯成可執(zhí)行程序后衔瓮,就是一個CGI浊猾。
int _tmain(int argc, _TCHAR* argv[])
{
printf("Content-type:text/html\n\n");
printf("%s",getenv("QUERY_STRING")); //打印get獲取的信息
return 0;
}
再或者,下面一個python腳本热鞍,也是一個CGI
#!/usr/bin/python
# -*- coding: UTF-8 -*-
print "Content-type:text/html"
print # 空行葫慎,告訴服務器結(jié)束頭部
print '<html>'
print '<head>'
print '<meta charset="utf-8">'
print '<title>Hello Word - 我的第一個 CGI 程序!</title>'
print '</head>'
print '<body>'
print '<h2>Hello Word! 我是來自菜鳥教程的第一CGI程序</h2>'
print '</body>'
print '</html>'
OK薇宠,知道了CGI是可執(zhí)行的程序或腳本偷办,但是怎么工作的呢?
CGI怎么用
如上圖澄港,當瀏覽器發(fā)送一個CGI請求后椒涯,服務器會啟動一個進程運行CGI程序或腳本,由CGI來處理數(shù)據(jù)回梧,并將結(jié)果返回給服務器废岂,服務器再將結(jié)果返回給瀏覽器。
舉個表單提交的例子:
<form id="form" name="form" method="post" action="http://localhost/cgi-bin/test/cgi_test.cgi">
<p>輸入內(nèi)容:
<input type="text" name="user" id="user" />
</p>
<p>
<input type="submit" name="submit" id="submit" value="提交" />
</p>
</form>
上面是一個表單提交的html代碼狱意,展示的效果是下面這個樣子:
細心的你會發(fā)現(xiàn)湖苞,action
的值是http://localhost/cgi-bin/test/cgi_test.cgi
。這里详囤,cgi_test.cgi
就是一個cgi程序财骨。
還記得上面那段C++代碼嗎?
int _tmain(int argc, _TCHAR* argv[])
{
printf("Content-type:text/html\n\n");
printf("%s",getenv("QUERY_STRING")); //打印get獲取的信息
return 0;
}
cgi_test.cgi就是這段代碼編譯出來的可執(zhí)行程序纬纪。
這段代碼的作用是什么呢蚓再?
作用是將表單提交的信息直接打印出來滑肉。
如何做到的包各?
只有兩行代碼,第二行代碼是關(guān)鍵靶庙。getenv()是C函數(shù)庫中的函數(shù)
问畅,getenv("QUERY_STRING")
的意思是讀取環(huán)境變量QUERY_STRING
的值。而QUERY_STRING
的值就是表單提交的信息六荒。
OK护姆,這個CGI的功能就清晰了。表單提交后展示下面的結(jié)果也就不奇怪了:
我們再通過一個圖梳理下上述流程:
綜上掏击,CGI工作模式示意圖如下:
CGI的特點
- 由Http Server喚起卵皂。常見的Http Server如Apache,Lighttpd砚亭,nginx都支持CGI
- CGI單獨啟動進程灯变,并且每次調(diào)用都會重新啟動進程
- 可以用任何語言編寫殴玛,只要該語言支持標準輸入、輸出和環(huán)境變量
CGI的缺點
- 消耗資源多:每個請求都會啟動一個CGI進行添祸,進程消耗資源15M內(nèi)存的話滚粟,同時到達100個請求的話,就會占用1.5G內(nèi)存刃泌。如果請求更多凡壤,資源消耗是不可想象的弦赖。
- 慢:啟動進程本身就慢疗涉。每次啟動進程都需要重新初始化數(shù)據(jù)結(jié)構(gòu)等,會變得更慢厅瞎。
引申
為了解決CGI重復啟動進程和初始化的問題林艘,后來出現(xiàn)了FastCGI
開荒期 - Servlet時代
在CGI繁榮發(fā)展的時代盖奈,Java還沒有發(fā)展起來。當Java開始參與歷史狐援,引領(lǐng)潮流的時候钢坦,也必然會借鑒和改進之前的技術(shù)和思想。
鑒于CGI的一些缺點啥酱,Java Web在開始設(shè)計的時候就想出了一種解決方案 -- Servlet
同樣爹凹,第一個問題,Servlet是啥镶殷?
Servlet是啥禾酱?
舉個例子,網(wǎng)站一般都有注冊功能绘趋。當用戶填寫好注冊信息颤陶,點擊“注冊”按鈕時,誰來處理這個請求陷遮?用戶名是否重復誰來校驗滓走?用戶名和密碼需要寫入數(shù)據(jù)庫,誰來寫入帽馋?是Servlet搅方!
Servlet是實現(xiàn)javax.servlet.Servlet
接口的類。一般處理Web請求的Servlet還需要繼承javax.servlet.http.HttpServlet
abstract class HttpServlet implements Servlet{
void doGet();
void doPost();
}
doGet()
方法處理GET請求
doPost()
方法處理POST請求
瀏覽器發(fā)來的請求是怎么被Servlet處理的呢绽族?還是舉表單提交的例子姨涡。
我們假設(shè)表單樣式如下,只是簡單提交兩個數(shù)據(jù):網(wǎng)址名和網(wǎng)址吧慢。并假設(shè)處理URL為http://localhost:8080/TomcatTest/HelloForm
瀏覽器工作
當表單使用GET方式提交時涛漂,瀏覽器會把表單數(shù)據(jù)組裝成這樣的URL:http://localhost:8080/TomcatTest/HelloForm?name=菜鳥教程&url=www.runoob.com
好,現(xiàn)在瀏覽器的任務暫時告一段落检诗,開始Java Web服務工作了匈仗。
Java Web服務
首先底哗,我們得指定http://localhost:8080/TomcatTest/HelloForm
這個URL由誰來處理。這個映射關(guān)系需要在web.xml中配置:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>HelloForm</servlet-name>
<servlet-class>com.runoob.test.HelloForm</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloForm</servlet-name>
<url-pattern>/TomcatTest/HelloForm</url-pattern>
</servlet-mapping>
</web-app>
web.xml中配置的意思是:當URI為/TomcatTest/HelloForm
時锚沸,交給com.runoob.test.HelloForm
處理跋选。而HelloForm
正是個Servlet。
因此哗蜈,我們需要編寫HelloForm
這樣一個Servlet:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class HelloForm
*/
@WebServlet("/HelloForm")
public class HelloForm extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public HelloForm() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 設(shè)置響應內(nèi)容類型
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String title = "使用 GET 方法讀取表單數(shù)據(jù)";
// 處理中文
String name =new String(request.getParameter("name").getBytes("ISO8859-1"),"UTF-8");
String docType = "<!DOCTYPE html> \n";
out.println(docType +
"<html>\n" +
"<head><title>" + title + "</title></head>\n" +
"<body bgcolor=\"#f0f0f0\">\n" +
"<h1 align=\"center\">" + title + "</h1>\n" +
"<ul>\n" +
" <li><b>站點名</b>:"
+ name + "\n" +
" <li><b>網(wǎng)址</b>:"
+ request.getParameter("url") + "\n" +
"</ul>\n" +
"</body></html>");
}
// 處理 POST 方法請求的方法
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
由于請求方式是GET前标,因此需要doGet()
方法來處理。仔細閱讀doGet()
方法的代碼距潘,發(fā)現(xiàn)處理邏輯只是把表單數(shù)據(jù)放入到了一段html代碼中炼列。這段html代碼會被傳輸給瀏覽器,然后瀏覽器渲染出結(jié)果音比,如下圖所示:
Servlet的特點
Servlet相對于CGI有了很大的改進俭尖,效率更高,功能更強大洞翩,更容易移植稽犁。主要表現(xiàn)在一下幾個方面:
- CGI每個請求啟動一個進程,而Servlet是更輕量的線程骚亿。線程和進程的對比和優(yōu)劣請自行Google已亥。
- CGI每個進程都需要初始化,Servlet只初始化一次實例就行
- Servlet依托于Java語言来屠,具有很好的跨平臺型虑椎。CGI根據(jù)語言的不同,跨平臺型不同
- CGI與數(shù)據(jù)庫連接需要重連俱笛,Servlet可以使用數(shù)據(jù)庫連接池捆姜。
- Java有豐富的、各種各樣的庫函數(shù)
Servlet的缺點
看上面的代碼迎膜,會發(fā)現(xiàn)泥技,html代碼是寫在Java代碼中的。對于前端人員來說星虹,這種形式非常非常難以開發(fā)和修改零抬。
Servlet的升級 -- JSP
Servlet是在Java代碼中寫HTML代碼镊讼。與之對應的就是在HTML代碼中寫Java代碼宽涌,這就是JSP。
JSP是啥蝶棋?
JSP:JavaServer Pages
簡單點說卸亮,就是可以在html中寫Java代碼。
還是先從例子中大概了解下JSP:
還是上面表單處理的例子玩裙。表單的html代碼就不展示了兼贸,我們直接模擬GET請求段直,即在瀏覽器中輸入地址:http://localhost:8080/testjsp/main.jsp?name=菜鳥教程&url=http://www.runoob.com
很明顯,這個URL的關(guān)鍵是main.jsp
溶诞。這個文件的內(nèi)容是啥呢鸯檬?
main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.io.*,java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鳥教程(runoob.com)</title>
</head>
<body>
<h1>使用 GET 方法讀取數(shù)據(jù)</h1>
<ul>
<li><p><b>站點名:</b>
<%= request.getParameter("name")%>
</p></li>
<li><p><b>網(wǎng)址:</b>
<%= request.getParameter("url")%>
</p></li>
</ul>
</body>
</html>
這就是JSP,在html代碼中插入Java代碼螺垢。java代碼被<% %>
所包圍喧务。
<%= request.getParameter("name")%>
表示獲取請求參數(shù)name的值,<%= request.getParameter("url")%>
表示獲取請求參數(shù)url的值枉圃。最終展示結(jié)果是怎樣的呢功茴?看下圖:
JSP是如何工作的?
為啥html代碼中可以寫Java代碼呢孽亲?看下圖:
其實原理是這樣的:
就像其他普通的網(wǎng)頁一樣坎穿,您的瀏覽器發(fā)送一個HTTP請求給服務器。
Web服務器識別出這是一個對JSP網(wǎng)頁的請求返劲,并且將該請求傳遞給JSP引擎玲昧。通過使用URL或者.jsp文件來完成。
JSP引擎從磁盤中載入JSP文件篮绿,然后將它們轉(zhuǎn)化為servlet酌呆。這種轉(zhuǎn)化只是簡單地將所有模板文本改用println()語句,并且將所有的JSP元素轉(zhuǎn)化成Java代碼搔耕。
JSP引擎將servlet編譯成可執(zhí)行類隙袁,并且將原始請求傳遞給servlet引擎。
Web服務器的某組件將會調(diào)用servlet引擎弃榨,然后載入并執(zhí)行servlet類菩收。在執(zhí)行過程中,servlet產(chǎn)生HTML格式的輸出并將其內(nèi)嵌于HTTP response中上交給Web服務器鲸睛。
Web服務器以靜態(tài)HTML網(wǎng)頁的形式將HTTP response返回到您的瀏覽器中娜饵。
最終,Web瀏覽器處理HTTP response中動態(tài)產(chǎn)生的HTML網(wǎng)頁官辈,就好像在處理靜態(tài)網(wǎng)頁一樣箱舞。
用一句話來講:每個JSP都最終會變成對應的Servlet執(zhí)行
JSP的缺點
在HTML代碼中寫Java代碼,方便了前端人員拳亿,但是苦了后端人員晴股。因此,單純使用JSP肺魁,開發(fā)效率依舊不高电湘。
后來,有牛人發(fā)現(xiàn),Servlet天生非常適合邏輯處理(因為主要是Java代碼)寂呛,而JSP非常適合頁面展示(因為主要是html代碼)怎诫,那么在結(jié)合Servlet和JSP各自的優(yōu)缺點后,誕生了Web開發(fā)中最常用和最重要的架構(gòu)設(shè)計模式:MVC
發(fā)展期 - MVC時代
MVC模式(Model-View-Controller)是軟件工程中的一種軟件架構(gòu)模式贷痪,把軟件系統(tǒng)分為三個基本部分:模型(Model)幻妓、視圖(View)和控制器(Controller):
- Controller——負責轉(zhuǎn)發(fā)請求,對請求進行處理
- View——負責界面顯示
- Model——業(yè)務功能編寫(例如算法實現(xiàn))劫拢、數(shù)據(jù)庫設(shè)計以及數(shù)據(jù)存取操作實現(xiàn)
簡而言之涌哲,請求發(fā)來后,會首先經(jīng)過Controller層處理尚镰,需要返回的結(jié)果封裝成對象傳遞給JSP阀圾,然后JSP負責取出數(shù)據(jù)展示就夠了。這樣狗唉,后端開發(fā)人員只負責編寫Servlet初烘,前端人員負責JSP,極大提升了開發(fā)效率分俯。
@WebServlet("/userPosts")
public class UserPostController extends HttpServlet {
private static final long serialVersionUID = -4208401453412759851L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
User user = Data.getByUsername(username);
List<Post> posts = Data.getPostByUser(user);
req.setAttribute("posts", posts);
req.setAttribute("user", user);
RequestDispatcher dispatcher = req.getRequestDispatcher("/templates/userPost.jsp");
dispatcher.forward(req, resp);
}
}
像上面這段代碼肾筐,UserPostController
就是一個Servlet,負責邏輯處理缸剪。需要返回的數(shù)據(jù)封裝到HttpServletRequest
對象中吗铐,傳遞給jsp頁面。而負責展示的就是/templates/userPost.jsp
這個jsp文件杏节。
繁盛期 - 框架時代
有了Servlet和JSP唬渗,相當于有了武器。有了MVC奋渔,相當于有了戰(zhàn)術(shù)镊逝。但是武器和戰(zhàn)術(shù)之間還缺少一層,就是具體實施者嫉鲸。
實踐證明撑蒜,單純使用Servlet、JSP和MVC開發(fā)玄渗,依然會面臨諸多的問題座菠。而程序員普遍存在一種特質(zhì),就是懶藤树。因為懶浴滴,所以才想著能有更簡單的解決辦法。因為懶也榄,針對一些通用問題巡莹,才會想出通用解決方法√鹱希可以說降宅,因為懶,科技才有了進步囚霸。腰根。。這時候拓型,為了解放勞動力额嘿,一些開源框架營運而出。這些框架的目的只有一個:讓開發(fā)簡單劣挫,簡單册养,更簡單
提到Java Web框架,就不得不提幾乎所有開發(fā)者都知道的三大框架:SSH
SSH
關(guān)于三大框架压固,這里不做介紹了球拦,網(wǎng)上的文章鋪天蓋地。想要說的是:無論什么框架帐我,都是對常見問題的抽象和封裝坎炼,再出什么新的框架,也萬變不離其宗拦键,脫離不了Servlet這個根基谣光。學習的時候千萬不能跟著框架走,框架讓做什么就做什么芬为,而是要想為什么這樣做萄金。否則,今天學會了一個框架媚朦,明天又出了新的框架捡絮,又會抓瞎了。