平時(shí)我們?cè)趯?xiě)一般的應(yīng)用程序的時(shí)候,無(wú)論如何都會(huì)有一個(gè)main函數(shù)入口。而在進(jìn)行web開(kāi)發(fā)的時(shí)候喳钟,從頭到尾我們都沒(méi)有寫(xiě)過(guò)一個(gè)main函數(shù)。最后部署時(shí)在岂,打了一個(gè)war包奔则,傳到web容器下面就可以了。到底這后面發(fā)生了什么蔽午,帶著疑問(wèn)讓我開(kāi)始吧易茬。
基礎(chǔ)知識(shí)
先了解一下java web應(yīng)用目錄組織結(jié)構(gòu):
- WebRoot :Web應(yīng)用所在目錄,一般情況下虛擬目錄要配置到此文件夾當(dāng)中及老。
- WEB-INF:此文件夾必須位WebRoot文件夾里面抽莱,而且必須以這樣的形式去命名,字母都要大寫(xiě)骄恶。
- Web.xml:配置文件食铐,有格式要求,此文件必須以這樣的形式去命名僧鲁,并且必須放置到WEB-INF文件夾中虐呻。
在看看一個(gè)瀏覽器與服務(wù)器交互的流程:
Servlet
在java web項(xiàng)目中,我們無(wú)論是用什么框架寞秃,Spring MVC也好斟叼,Spring Boot也好,最后都是Servlet在起著決定性的作用春寿。
Servlet是什么朗涩?
Servlet 是運(yùn)行在 Web 服務(wù)器或應(yīng)用服務(wù)器上的程序,它是作為來(lái)自瀏覽器或其他 HTTP 客戶(hù)端的請(qǐng)求和 HTTP 服務(wù)器上的數(shù)據(jù)庫(kù)或應(yīng)用程序之間的中間層绑改。
Servlet在Web應(yīng)用程序的位置如下圖所示:
Servlet程序是由web容器調(diào)用馋缅,也就解釋了為什么我們所寫(xiě)的web應(yīng)用程序不需要main函數(shù)的原因,它是由web容器根據(jù)請(qǐng)然后創(chuàng)建的绢淀。
Servlet主要作用有:
- 讀取客戶(hù)端(瀏覽器)發(fā)送的數(shù)據(jù) 。
- 處理數(shù)據(jù)并生成結(jié)果瘾腰。這個(gè)過(guò)程可能需要訪問(wèn)數(shù)據(jù)庫(kù)皆的,調(diào)用 Web 服務(wù),或者直接計(jì)算得出對(duì)應(yīng)的響應(yīng)蹋盆。平時(shí)的業(yè)務(wù)邏輯就是在這個(gè)部分實(shí)現(xiàn)费薄。
- 發(fā)送處理過(guò)后的數(shù)據(jù)給客戶(hù)端(瀏覽器)硝全。
Servlet的運(yùn)行過(guò)程
web容器收到客戶(hù)端的訪問(wèn)請(qǐng)求后將進(jìn)行如下處理:
- Web容器首先檢查是否已經(jīng)裝載并創(chuàng)建了該Servlet的實(shí)例對(duì)象。如果是楞抡,則直接執(zhí)行第4步伟众,否則,執(zhí)行第2步召廷。
- 裝載并創(chuàng)建該Servlet的一個(gè)實(shí)例對(duì)象凳厢。
- 調(diào)用Servlet實(shí)例對(duì)象的init()方法。
- 創(chuàng)建一個(gè)用于封裝HTTP請(qǐng)求消息的HttpServletRequest對(duì)象和一個(gè)代表HTTP響應(yīng)消息的HttpServletResponse對(duì)象竞慢,然后調(diào)用Servlet的service()方法并將請(qǐng)求和響應(yīng)對(duì)象作為參數(shù)傳遞進(jìn)去先紫。
- WEB應(yīng)用程序被停止或重新啟動(dòng)之前,Servlet引擎將卸載Servlet筹煮,并在卸載之前調(diào)用Servlet的destroy()方法遮精。
具體來(lái)講可以用下面的圖說(shuō)明:
其時(shí)序圖可以用下圖表示:
一個(gè)Servlet例子
有了上面的理論基礎(chǔ)我們就可以寫(xiě)一個(gè)例子加深理解。最終實(shí)現(xiàn)一個(gè)現(xiàn)實(shí)header的效果败潦。
源碼如下:
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DisplayHeaderServlet extends HttpServlet {
// 處理 GET 方法請(qǐng)求的方法
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// 設(shè)置響應(yīng)內(nèi)容類(lèi)型
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String title = "HTTP Header 請(qǐng)求";
String docType =
"<!DOCTYPE html> \n";
out.println(docType +
"<html>\n" +
"<head><meta charset=\"utf-8\"><title>" + title + "</title></head>\n"+
"<body bgcolor=\"#f0f0f0\">\n" +
"<h1 align=\"center\">" + title + "</h1>\n" +
"<table width=\"100%\" border=\"1\" align=\"center\">\n" +
"<tr bgcolor=\"#949494\">\n" +
"<th>Header 名稱(chēng)</th><th>Header 值</th>\n"+
"</tr>\n");
Enumeration headerNames = request.getHeaderNames();
while(headerNames.hasMoreElements()) {
String paramName = (String)headerNames.nextElement();
out.print("<tr><td>" + paramName + "</td>\n");
String paramValue = request.getHeader(paramName);
out.println("<td> " + paramValue + "</td></tr>\n");
}
out.println("</table>\n</body></html>");
}
// 處理 POST 方法請(qǐng)求的方法
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
寫(xiě)完之后本冲,還需要將文件便以為class文件,項(xiàng)目部署到tomcat目錄下劫扒。啟動(dòng)tomcat Web容器訪問(wèn)對(duì)應(yīng)路徑即可檬洞。
在web.xml中配置一下加載的Servlet
<servlet>
<!-- servlet名,一般寫(xiě)成類(lèi)名粟关,并不一定嚴(yán)格是類(lèi)名 -->
<servlet-name>DisplayHeaderServlet</servlet-name>
<!-- 所在的包 -->
<servlet-class>com.xxx.xxx.DisplayHeaderServlet</servlet-class>
</servlet>
<servlet-mapping>
<!-- 與上面的servlet-name相對(duì)應(yīng)-->
<servlet-name>DisplayHeaderServlet</servlet-name>
<!-- 訪問(wèn)的網(wǎng)址 -->
<url-pattern>/TomcatTest/DisplayHeaderServlet</url-pattern>
</servlet-mapping>
Servlet需要注意
Servlet訪問(wèn)URL映射配置
由于客戶(hù)端是通過(guò)URL地址訪問(wèn)web服務(wù)器中的資源疮胖,所以Servlet程序若想被外界訪問(wèn),必須把servlet程序映射到一個(gè)URL地址上闷板。
這個(gè)工作在web.xml文件中使用<servlet>
元素和<servlet-mapping>
元素完成澎灸。<servlet>
元素用于注冊(cè)Servlet,它包含有兩個(gè)主要的子元素:<servlet-name>
和<servlet-class>
遮晚,分別用于設(shè)置Servlet的注冊(cè)名稱(chēng)和Servlet的完整類(lèi)名性昭。
一個(gè)<servlet-mapping>
元素用于映射一個(gè)已注冊(cè)的Servlet的一個(gè)對(duì)外訪問(wèn)路徑,它包含有兩個(gè)子元素:<servlet-name>
和<url-pattern>
县遣,分別用于指定Servlet的注冊(cè)名稱(chēng)和Servlet的對(duì)外訪問(wèn)路徑糜颠。
- 同一個(gè)Servlet可以被映射到多個(gè)URL上,即多個(gè)
<servlet-mapping>
元素的<servlet-name>
子元素的設(shè)置值可以是同一個(gè)Servlet的注冊(cè)名萧求。 - 同一個(gè)Url不能對(duì)應(yīng)多個(gè)Servlet其兴。否則會(huì)報(bào)錯(cuò)
Caused by: java.lang.IllegalArgumentException: The servlets named [xxx] and [xxx] are both mapped to the url-pattern xxx
Servlet訪問(wèn)URL使用*通配符映射
在Servlet映射到的URL中也可以使用*通配符,但是只能有兩種固定的格式:一種格式是"*.擴(kuò)展名"
夸政,另一種格式是以正斜杠(/)開(kāi)頭并以"/*"
結(jié)尾
但是如果存在了沖突元旬,比如都有*
。規(guī)則又是怎樣的呢?
請(qǐng)求Url | Url1 | Url2 | 規(guī)則 |
---|---|---|---|
/abc/a.html | /abc/* | /* | Servlet引擎將調(diào)用/abc/* |
/abc | /abc/* | /abc | Servlet引擎將調(diào)用/abc |
/abc/a.do | /abc/* | *.do | Servlet引擎將調(diào)用/abc/* |
/a.do | /* | *.do | Servlet引擎將調(diào)用/* |
/xxx/yyy/a.do | /* | *.do | Servlet引擎將調(diào)用/* |
匹配的原則就是"誰(shuí)長(zhǎng)得更像就找誰(shuí)" 匀归。但是當(dāng)請(qǐng)求url完全匹配的時(shí)候就走完全匹配的rul坑资。如上面的第二條。
Servlet與普通Java類(lèi)的區(qū)別
Servlet是一個(gè)供其他Java程序(Servlet引擎穆端,比如tomcat web容器)調(diào)用的Java類(lèi)袱贮,它不能獨(dú)立運(yùn)行,它的運(yùn)行完全由Servlet引擎來(lái)控制和調(diào)度体啰。這也是開(kāi)發(fā)Web項(xiàng)目沒(méi)有些main方法的原因攒巍。
針對(duì)客戶(hù)端的多次Servlet請(qǐng)求,通常情況下狡赐,服務(wù)器只會(huì)創(chuàng)建一個(gè)Servlet實(shí)例對(duì)象窑业,也就是說(shuō)Servlet實(shí)例對(duì)象一旦創(chuàng)建,它就會(huì)駐留在內(nèi)存中枕屉,為后續(xù)的其它請(qǐng)求服務(wù)常柄,直至web容器退出,servlet實(shí)例對(duì)象才會(huì)銷(xiāo)毀搀擂。
在Servlet的整個(gè)生命周期內(nèi)西潘,Servlet的init方法只被調(diào)用一次。而對(duì)一個(gè)Servlet的每次訪問(wèn)請(qǐng)求都導(dǎo)致Servlet引擎調(diào)用一次servlet的service方法哨颂。
對(duì)于每次訪問(wèn)請(qǐng)求喷市,Servlet引擎都會(huì)創(chuàng)建一個(gè)新的HttpServletRequest請(qǐng)求對(duì)象和一個(gè)新的HttpServletResponse響應(yīng)對(duì)象,然后將這兩個(gè)對(duì)象作為參數(shù)傳遞給它調(diào)用的Servlet的service()方法威恼,service方法再根據(jù)請(qǐng)求方式分別調(diào)用doXXX方法品姓。
如果在<servlet>
元素中配置了一個(gè)<load-on-startup>
元素,那么WEB應(yīng)用程序在啟動(dòng)時(shí)箫措,就會(huì)裝載并創(chuàng)建Servlet的實(shí)例對(duì)象腹备、以及調(diào)用Servlet實(shí)例對(duì)象的init()方法。
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>
org.apache.catalina.servlets.InvokerServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
可以用于為web應(yīng)用寫(xiě)一個(gè)InitServlet斤蔓,這個(gè)servlet配置為啟動(dòng)時(shí)裝載植酥,為整個(gè)web應(yīng)用創(chuàng)建必要的數(shù)據(jù)庫(kù)表和數(shù)據(jù)。很多第三方框架也需要在應(yīng)用一加載就實(shí)例化Serlvet弦牡,比如SpringMVC中的org.springframework.web.servlet.DispatcherServlet
<!-- springMVC的核心控制器 --> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:springMVC-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
缺省Servlet
如果某個(gè)Servlet的映射路徑僅僅為一個(gè)正斜杠(/)友驮,那么這個(gè)Servlet就成為當(dāng)前Web應(yīng)用程序的缺省Servlet。
凡是在web.xml文件中找不到匹配的<servlet-mapping>
元素的URL驾锰,它們的訪問(wèn)請(qǐng)求都將交給缺省Servlet處理卸留,也就是說(shuō),缺省Servlet用于處理所有其他Servlet都不處理的訪問(wèn)請(qǐng)求椭豫。
比如在tomcat中\(zhòng)conf\web.xml文件中耻瑟,注冊(cè)了一個(gè)名稱(chēng)為org.apache.catalina.servlets.DefaultServlet的Servlet买喧,并將這個(gè)Servlet設(shè)置為了缺省Servlet。
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
當(dāng)訪問(wèn)Tomcat服務(wù)器中的某個(gè)靜態(tài)HTML文件和圖片時(shí)匆赃,實(shí)際上是在訪問(wèn)這個(gè)缺省Servlet。
Servlet的線(xiàn)程安全問(wèn)題
當(dāng)多個(gè)客戶(hù)端并發(fā)訪問(wèn)同一個(gè)Servlet時(shí)今缚,web服務(wù)器會(huì)為每一個(gè)客戶(hù)端的訪問(wèn)請(qǐng)求創(chuàng)建一個(gè)線(xiàn)程算柳,并在這個(gè)線(xiàn)程上調(diào)用Servlet的service方法,因此service方法內(nèi)如果訪問(wèn)了同一個(gè)資源的話(huà)姓言,就有可能引發(fā)線(xiàn)程安全問(wèn)題瞬项。
所有在Servlet中盡量避免使用實(shí)例變量 ,最好使用局部變量何荚。
Servlet如何處理多個(gè)請(qǐng)求訪問(wèn)
Servlet容器默認(rèn)是采用單實(shí)例多線(xiàn)程的方式處理多個(gè)請(qǐng)求的:
- 當(dāng)web服務(wù)器啟動(dòng)的時(shí)候(或客戶(hù)端發(fā)送請(qǐng)求到服務(wù)器時(shí))囱淋,Servlet就被加載并實(shí)例化(只存在一個(gè)Servlet實(shí)例)
- 容器初始化化Servlet主要就是讀取配置文件(例如tomcat,可以通過(guò)servlet.xml的<Connector>設(shè)置線(xiàn)程池中線(xiàn)程數(shù)目,初始化線(xiàn)程池通過(guò)web.xml,初始化每個(gè)參數(shù)值等等餐塘。
- 當(dāng)請(qǐng)求到達(dá)時(shí)妥衣,Servlet容器通過(guò)調(diào)度線(xiàn)程(Dispatchaer Thread) 調(diào)度它管理下線(xiàn)程池中等待執(zhí)行的線(xiàn)程(Worker Thread)給請(qǐng)求者。
- 線(xiàn)程執(zhí)行Servlet的service方法戒傻。
- 請(qǐng)求結(jié)束税手,放回線(xiàn)程池,等待被調(diào)用需纳;
從上面可以看出(好處):
- Servlet單實(shí)例芦倒,減少了產(chǎn)生servlet的開(kāi)銷(xiāo);
- 通過(guò)線(xiàn)程池來(lái)響應(yīng)多個(gè)請(qǐng)求不翩,提高了請(qǐng)求的響應(yīng)時(shí)間兵扬;
- Servlet容器并不關(guān)心到達(dá)的Servlet請(qǐng)求訪問(wèn)的是否是同一個(gè)Servlet還是另一個(gè)Servlet,直接分配給它一個(gè)新的線(xiàn)程口蝠。
- 如果是同一個(gè)Servlet的多個(gè)請(qǐng)求器钟,那么Servlet的service方法將在多線(xiàn)程中并發(fā)的執(zhí)行;
- 每一個(gè)請(qǐng)求由ServletRequest對(duì)象來(lái)接受請(qǐng)求亚皂,由ServletResponse對(duì)象來(lái)響應(yīng)該請(qǐng)求俱箱;
Servlet相關(guān)對(duì)象
ServletConfig
上面講了可以設(shè)置Servlet對(duì)應(yīng)的url,那么如何需要配置Serlvet初始化參數(shù)怎么辦,這就需要用到ServletConfig灭必。在Servlet的配置文件web.xml中狞谱,可以使用一個(gè)或多個(gè)<init-param>
標(biāo)簽為servlet配置一些初始化參數(shù)。
<servlet>
<servlet-name>ServletConfigDemo1</servlet-name>
<servlet-class>com.xxx.xxx.xxx</servlet-class>
<!--配置ServletConfig的初始化參數(shù) -->
<init-param>
<param-name>name</param-name>
<param-value>xxx</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123</param-value>
</init-param>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</servlet>
當(dāng)servlet配置了初始化參數(shù)后禁漓,web容器在創(chuàng)建servlet實(shí)例對(duì)象時(shí)跟衅,會(huì)自動(dòng)將這些初始化參數(shù)封裝到ServletConfig對(duì)象中,并在調(diào)用servlet的init方法時(shí)播歼,將ServletConfig對(duì)象傳遞給servlet伶跷。進(jìn)而掰读,我們通過(guò)ServletConfig對(duì)象就可以得到當(dāng)前servlet的初始化參數(shù)信息。
獲取方式如下:
/**
* 定義ServletConfig對(duì)象來(lái)接收配置的初始化參數(shù)
*/
private ServletConfig config;
/**
* 當(dāng)servlet配置了初始化參數(shù)后叭莫,web容器在創(chuàng)建servlet實(shí)例對(duì)象時(shí)蹈集,
* 會(huì)自動(dòng)將這些初始化參數(shù)封裝到ServletConfig對(duì)象中,并在調(diào)用servlet的init方法時(shí)雇初,
* 將ServletConfig對(duì)象傳遞給servlet拢肆。進(jìn)而,程序員通過(guò)ServletConfig對(duì)象就可以
* 得到當(dāng)前servlet的初始化參數(shù)信息靖诗。
*/
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//獲取在web.xml中配置的初始化參數(shù)
//獲取指定的初始化參數(shù)
String paramVal = this.config.getInitParameter("name");
response.getWriter().print(paramVal);
response.getWriter().print("<hr/>");
//獲取所有的初始化參數(shù)
Enumeration<String> e = config.getInitParameterNames();
while(e.hasMoreElements()){
String name = e.nextElement();
String value = config.getInitParameter(name);
response.getWriter().print(name + "=" + value + "<br/>");
}
}
比如在使用SpringMVC的時(shí)候郭怪,在web.xml就使用到了ServletConfig
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置ServletConfig的初始化參數(shù) -->
<init-param>
<description>SpringMVC</description>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
<!--web容器一旦加載就會(huì)創(chuàng)建這個(gè)servlet,并且會(huì)調(diào)用init方法-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
ServletContext
對(duì)于ServletContext需要我們注意一下幾點(diǎn):
- WEB容器在啟動(dòng)時(shí)刊橘,它會(huì)為每個(gè)WEB應(yīng)用程序都創(chuàng)建一個(gè)對(duì)應(yīng)的ServletContext對(duì)象鄙才,它代表當(dāng)前web應(yīng)用。
- ServletConfig對(duì)象中維護(hù)了ServletContext對(duì)象的引用促绵,開(kāi)發(fā)人員在編寫(xiě)servlet時(shí)攒庵,可以通過(guò)ServletConfig.getServletContext方法獲得ServletContext對(duì)象。
- 由于一個(gè)WEB應(yīng)用中的所有Servlet共享同一個(gè)ServletContext對(duì)象绞愚,因此Servlet對(duì)象之間可以通過(guò)ServletContext對(duì)象來(lái)實(shí)現(xiàn)通訊叙甸。ServletContext對(duì)象通常也被稱(chēng)之為context域?qū)ο蟆?/li>
類(lèi)似于ServletContext(應(yīng)用上下文)這種設(shè)計(jì)思想,很多地方都有類(lèi)似的應(yīng)用位衩。比如iOS中的畫(huà)圖CGContext 裆蒸。
來(lái)一個(gè)數(shù)據(jù)共享的例子:
存儲(chǔ)數(shù)據(jù):
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String data = "data";
/**
* ServletConfig對(duì)象中維護(hù)了ServletContext對(duì)象的引用,開(kāi)發(fā)人員在編寫(xiě)servlet時(shí)糖驴,
* 可以通過(guò)ServletConfig.getServletContext方法獲得ServletContext對(duì)象僚祷。
*/
//獲得ServletContext對(duì)象
ServletContext context = this.getServletConfig().getServletContext();
//將data存儲(chǔ)到ServletContext對(duì)象中
//將data存儲(chǔ)到ServletContext對(duì)象中
context.setAttribute("data", data);
}
讀取數(shù)據(jù):
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext context = this.getServletContext();\
//從ServletContext對(duì)象中取出數(shù)據(jù)
String data = (String) context.getAttribute("data");
response.getWriter().print("data="+data);
}
更多的情況下是通過(guò)web.xml中讀取初始化參數(shù)。如下:
<!-- 配置WEB應(yīng)用的初始化參數(shù) -->
<context-param>
<param-name>url</param-name>
<param-value>xxx.xxx.xxxx</param-value>
</context-param>
讀取web.xml中的初始化參數(shù)
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext context = this.getServletContext();
//獲取整個(gè)web站點(diǎn)的初始化參數(shù)
String contextInitParam = context.getInitParameter("url");
response.getWriter().print(contextInitParam);
}
除了讀取web.xml中的初始化參數(shù)贮缕,還可以通過(guò)ServletContext讀取對(duì)象文件辙谜。比如常見(jiàn)在項(xiàng)目中使用的xxx.properties文件。很多第三方也是用的這種方式來(lái)讀取對(duì)應(yīng)的配置文件
/**
* 讀取src目錄下的com.xxx.xxx包中的xxx.properties配置文件
* @param response
* @throws IOException
*/
private void readPropCfgFile2(HttpServletResponse response)
throws IOException {
InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/com/xxx/xxx/xxx.properties");
Properties prop = new Properties();
prop.load(in);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
response.getWriter().println("讀取src目錄下的com.xxx.xxx包中的xxx.properties配置文件:");
response.getWriter().println(
MessageFormat.format(
"driver={0},url={1},username={2},password={3}",
driver,url, username, password));
}
除了通過(guò)ServletContext讀取資源文件感昼,還可以通過(guò)類(lèi)裝載器讀取資源文件装哆。也有很多第三方是通過(guò)這樣的方式讀取資源文件
/**
* 讀取類(lèi)路徑下的資源文件
* @param response
* @throws IOException
*/
private void test1(HttpServletResponse response) throws IOException {
//獲取到裝載當(dāng)前類(lèi)的類(lèi)裝載器
ClassLoader loader = ServletContextDemo7.class.getClassLoader();
//用類(lèi)裝載器讀取src目錄下的db1.properties配置文件
InputStream in = loader.getResourceAsStream("db1.properties");
Properties prop = new Properties();
prop.load(in);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
response.getWriter().println("用類(lèi)裝載器讀取src目錄下的db1.properties配置文件:");
response.getWriter().println(
MessageFormat.format(
"driver={0},url={1},username={2},password={3}",
driver,url, username, password));
}
Web容器
上一篇講了Servlet,提到Servlet由容器調(diào)用定嗓。拿么Web容器是干嘛的呢蜕琴。
比較權(quán)威的解釋?zhuān)?/p>
web容器是一種服務(wù)程序,在服務(wù)器一個(gè)端口就有一個(gè)提供相應(yīng)服務(wù)的程序宵溅,而這個(gè)程序就是處理從客戶(hù)端發(fā)出的請(qǐng)求凌简,如JAVA中的Tomcat容器,ASP的IIS或PWS都是這樣的容器恃逻。比如tomcat容器雏搂,它提供的接口嚴(yán)格遵守 J2EE 規(guī)范中的 WEB APPLICATION 標(biāo)準(zhǔn)藕施。我們把遵守該標(biāo)準(zhǔn)的 WEB 服務(wù)器就叫做 J2EE 中的 WEB 容器。 這套標(biāo)準(zhǔn)就是當(dāng)有客戶(hù)端請(qǐng)求凸郑,自動(dòng)調(diào)用對(duì)應(yīng)的Servlet的標(biāo)準(zhǔn)
為了加強(qiáng)理解這里再次說(shuō)明一下:
servlet沒(méi)有main方法裳食,那我們?nèi)绾螁?dòng)一個(gè)servlet,如何結(jié)束一個(gè)servlet芙沥,如何尋找一個(gè)servlet等等胞谈,都受控于另一個(gè)java應(yīng)用,這個(gè)應(yīng)用我們就稱(chēng)之為web容器憨愉。
比如tomcat就是這樣一個(gè)容器。如果web服務(wù)器應(yīng)用得到一個(gè)指向某個(gè)servlet的請(qǐng)求卿捎,此時(shí)服務(wù)器不是把servlet交給servlet本身配紫,而是交給部署該servlet的容器。要有容器向servlet提供http請(qǐng)求和響應(yīng)午阵,而且要由容器調(diào)用servlet的方法躺孝,如doPost或者doGet。
接下來(lái)就以Tomcat為例子
Tomcat
Tomcat是Apache 軟件基金會(huì)(Apache Software Foundation)的Jakarta 項(xiàng)目中的一個(gè)核心項(xiàng)目底桂,由Apache植袍、Sun 和其他一些公司及個(gè)人共同開(kāi)發(fā)而成。由于有了Sun 的參與和支持籽懦,最新的Servlet 和JSP 規(guī)范總是能在Tomcat 中得到體現(xiàn)于个,Tomcat 5支持最新的Servlet 2.4 和JSP 2.0 規(guī)范。因?yàn)門(mén)omcat 技術(shù)先進(jìn)暮顺、性能穩(wěn)定厅篓,而且免費(fèi),因而深受Java 愛(ài)好者的喜愛(ài)并得到了部分軟件開(kāi)發(fā)商的認(rèn)可捶码,成為目前比較流行的Web 應(yīng)用服務(wù)器羽氮。
體系結(jié)構(gòu)
- Tomcat服務(wù)器的啟動(dòng)是基于一個(gè)server.xml文件的,Tomcat啟動(dòng)的時(shí)候首先會(huì)啟動(dòng)一個(gè)Server惫恼,Server里面就會(huì)啟動(dòng)Service档押,Service里面就會(huì)啟動(dòng)多個(gè)Connector(連接器)。
- 每一個(gè)Connector(連接器)都在等待客戶(hù)機(jī)的連接祈纯,當(dāng)有用戶(hù)使用瀏覽器去訪問(wèn)服務(wù)器上面的web資源時(shí)令宿,首先是連接到Connector(連接器),Connector(連接器)是不處理用戶(hù)的請(qǐng)求的盆繁,而是將用戶(hù)的請(qǐng)求交給一個(gè)Engine(引擎)去處理掀淘。
- Engine(引擎)接收到請(qǐng)求后就會(huì)解析用戶(hù)想要訪問(wèn)的Host,然后將請(qǐng)求交給相應(yīng)的Host油昂。
- Host收到請(qǐng)求后就會(huì)解析出用戶(hù)想要訪問(wèn)這個(gè)Host下面的哪一個(gè)Web應(yīng)用,一個(gè)web應(yīng)用對(duì)應(yīng)一個(gè)Context革娄。
一個(gè)簡(jiǎn)單的配置例子倾贰,對(duì)照著上面的體系結(jié)構(gòu)來(lái)看:
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="conf/.keystore" keystorePass="123456"/>
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
<!-- host對(duì)應(yīng)的根目錄 -->
<Host name="www.xxx.xxx" appBase="F:\JavaWebApps">
<!-- 具體context對(duì)應(yīng)的目錄 -->
<Context path="" docBase="F:\JavaWebApps\xxxx"/>
</Host>
</Engine>
</Service>
</Server>
Tomcat web.xml
應(yīng)用服務(wù)器啟動(dòng)時(shí)web.xml加載過(guò)程,至于這些節(jié)點(diǎn)在xml文件中的前后順序沒(méi)有關(guān)系拦惋,不過(guò)有些應(yīng)用服務(wù)器,比如websphere就嚴(yán)格要求web.xml的節(jié)點(diǎn)順序,否則部署不成功匆浙,所以還是最好按照web.xml標(biāo)準(zhǔn)格式寫(xiě) :
具體的配置和加載順序:content-param --> listener --> filter --> servlet
結(jié)合上面所講的ServletConfig及ServletContext。
- 啟動(dòng)WEB項(xiàng)目的時(shí)候,應(yīng)用服務(wù)器會(huì)去讀它的配置文件web.xml.讀兩個(gè)節(jié)點(diǎn):
<context-param></context-param>
和<listener></listener>
- 緊接著,容器創(chuàng)建一個(gè)ServletContext(上下文),這個(gè)WEB項(xiàng)目所有部分都將共享這個(gè)上下文厕妖。
- 容器將
<context-param></context-param>
轉(zhuǎn)化為鍵值對(duì),并交給ServletContext首尼。 - 容器創(chuàng)建
<listener></listener>
中的類(lèi)實(shí)例,即創(chuàng)建監(jiān)聽(tīng)。 - 在監(jiān)聽(tīng)中會(huì)有
contextInitialized(ServletContextEvent args)
初始化方法,在這個(gè)方法中獲得:ServletContext = ServletContextEvent.getServletContext(); context-param的值 = ServletContext.getInitParameter("context-param的鍵")
; - 得到這個(gè)
context-param
的值之后,你就可以做一些操作了.注意,這個(gè)時(shí)候你的WEB項(xiàng)目還沒(méi)有完全啟動(dòng)完成言秸,這個(gè)動(dòng)作會(huì)比所有的Servlet都要早软能。換句話(huà)說(shuō),這個(gè)時(shí)候,你對(duì)<context-param>中的鍵值做的操作,將在你的WEB項(xiàng)目完全啟動(dòng)之前被執(zhí)行.如果想在項(xiàng)目啟動(dòng)之前就打開(kāi)數(shù)據(jù)庫(kù),那么這里就可以在<context-param>中設(shè)置數(shù)據(jù)庫(kù)的連接方式,在監(jiān)聽(tīng)類(lèi)中初始化數(shù)據(jù)庫(kù)的連接,這個(gè)監(jiān)聽(tīng)是自己寫(xiě)的一個(gè)類(lèi),除了初始化方法,它還有銷(xiāo)毀方法.用于關(guān)閉應(yīng)用前釋放資源.比如說(shuō)數(shù)據(jù)庫(kù)連接的關(guān)閉。
順序
以 filter 為例举畸,web.xml 中當(dāng)然可以定義多個(gè) filter查排,與 filter 相關(guān)的一個(gè)配置節(jié)是 filter-mapping,這里一定要注意抄沮,對(duì)于擁有相同 filter-name 的 filter 和 filter-mapping 配置節(jié)而言跋核,filter-mapping 必須出現(xiàn)在 filter 之后,否則當(dāng)解析到 filter-mapping 時(shí)叛买,它所對(duì)應(yīng)的 filter-name 還未定義砂代。
web 容器啟動(dòng)時(shí)初始化每個(gè) filter 時(shí),是按照 filter 配置節(jié)出現(xiàn)的順序來(lái)初始化的率挣,當(dāng)請(qǐng)求資源匹配多個(gè) filter-mapping 時(shí)刻伊,filter 攔截資源是按照 filter-mapping 配置節(jié)出現(xiàn)的順序來(lái)依次調(diào)用 doFilter() 方法的。 servlet 同 filter 類(lèi)似椒功!
比如filter 需要用到 bean 娃圆,但加載順序是: 先加載filter 后加載spring,則filter中初始化操作中的bean為null蛾茉;所以讼呢,如果過(guò)濾器中要使用到 bean,可以將spring 的加載 改成 Listener的方式
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
節(jié)點(diǎn)詳解
web-app
根節(jié)點(diǎn)
<web-app></web-app>
context-param
用來(lái)設(shè)定web站臺(tái)的環(huán)境參數(shù)谦炬。
它包含兩個(gè)子元素:
- <param-name></param-name> 用來(lái)指定參數(shù)的名稱(chēng)
- <param-value></param-value> 用來(lái)設(shè)定參數(shù)值
在此設(shè)定的參數(shù)悦屏,可以在servlet中用 getServletContext().getInitParameter("my_param")
來(lái)取得。
例子:
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath*:/log4j.properties</param-value>
</context-param>
listener
用來(lái)設(shè)定Listener接口键思,它的主要子元素為:
- <listener-class></listener-class> 定義Listener的類(lèi)名稱(chēng)
例子:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
filter
是用來(lái)聲明filter的相關(guān)設(shè)定
- <filter-name></filter-name> 這當(dāng)然就是指定filter的名字
- <filter-class></filter-class> 這是用來(lái)定義filter的類(lèi)的名稱(chēng)
- <init-param></init-param> 用來(lái)定義參數(shù)础爬,它有兩個(gè)子元素:
- <param-name></param-name> 用來(lái)指定參數(shù)的名稱(chēng)
- <param-value></param-value> 用來(lái)設(shè)定參數(shù)值
例子:
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
servlet
來(lái)聲明一個(gè)servlet的數(shù)據(jù),主要有以下子元素:
- <servlet-name></servlet-name> 指定servlet的名稱(chēng)
- <servlet-class></servlet-class> 指定servlet的類(lèi)名稱(chēng)
- <jsp-file></jsp-file> 指定web站臺(tái)中的某個(gè)JSP網(wǎng)頁(yè)的完整路徑
- <init-param></init-param> 用來(lái)定義參數(shù),和前面的<init-param>差不多
同樣,與<servlet></servlet>一起使用的是<servlet-mapping></servlet-mapping> 用來(lái)定義servlet所對(duì)應(yīng)的URL酝枢,包含兩個(gè)子元素:<servlet-name></servlet-name> 指定servlet的名稱(chēng)<url-pattern></url-pattern> 指定servlet所對(duì)應(yīng)的URL
<servlet>
<servlet-name>DemoServlet</servlet-name>
<servlet-class>com.test.DemoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DemoServlet</servlet-name>
<url-pattern>/demoServlet</url-pattern>
</servlet-mapping>
description
是對(duì)站臺(tái)的描述
例子:
<description>傳道、授業(yè)供炎、解惑</description>
display-name
定義站臺(tái)的名稱(chēng)
例子:
<display-name>我的站點(diǎn)</display-name>
icon
icon元素包含small-icon和large-icon兩個(gè)子元素.用來(lái)指定web站臺(tái)中小圖標(biāo)和大圖標(biāo)的路徑.
<small-icon>/路徑/smallicon.gif</small-icon>
small-icon元素應(yīng)指向web站臺(tái)中某個(gè)小圖標(biāo)的路徑,大小為16 X 16 pixel,但是圖象文件必須為GIF或JPEG格式,擴(kuò)展名必須為:.gif或.jpg.
<large-icon>/路徑/largeicon-jpg</large-icon>
large-icon元素應(yīng)指向web站臺(tái)中某個(gè)大圖表路徑,大小為32 X 32 pixel,但是圖象文件必須為GIF或JPEG的格式,擴(kuò)展名必須為; gif或jpg.
例子:
<icon>
<small-icon>/images/small.gif</small-icon>
<large-icon>/images/large.gir</large-icon>
</icon>
distributable
是指定該站臺(tái)是否可分布式處理
session-config
用來(lái)定義web站臺(tái)中的session參數(shù)
包含一個(gè)子元素:
<session-timeout></session-timeout> 用來(lái)定義這個(gè)web站臺(tái)所有session的有效期限渴逻,單位為 分鐘
mime-mapping
定義某一個(gè)擴(kuò)展名和某一個(gè)MIME Type做對(duì)應(yīng)該
包含兩個(gè)子元素:
- <extension></extension> 擴(kuò)展名的名稱(chēng)
- <mime-type></mime-type> MIME格式
例子:
<mime-mapping>
<extension>doc</extension>
<mime-type>application/vnd.ms-word</mime-type>
</mime-mapping>
<mime-mapping>
<extension>xls</extension>
<mime-type>application/vnd.ms-excel</mime-type>
</mime-mapping>
error-page
例子
<error-page> <error-page>
<error-code>500</error-code>
<location>/message.jsp</location>
</error-page>
<error-page>
<error-code>400</error-code>
<location>/message.jsp</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/message.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/message.jsp</location>
</error-page>
<error-page>
<error-code>502</error-code>
<location>/index.jsp</location>
</error-page>
jsp-config
例子
<jsp-config>
<taglib>
<taglib-uri>/struts-tags</taglib-uri>
<taglib-location>/WEB-INF/struts-tags.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/struts-dojo-tags</taglib-uri>
<taglib-location>/WEB-INF/struts-dojo-tags.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/s</taglib-uri>
<taglib-location>/WEB-INF/struts-tags.tld</taglib-location>
</taglib>
</jsp-config>
welcome-file-list
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
resource-ref
定義利用JNDI取得站臺(tái)可利用的資源
有五個(gè)子元素:
- <description></description> 資源說(shuō)明
- <rec-ref-name></rec-ref-name> 資源名稱(chēng)
- <res-type></res-type> 資源種類(lèi)
- <res-auth></res-auth> 資源經(jīng)由Application或Container來(lái)許可
- <res-sharing-scope></res-sharing-scope> 資源是否可以共享,有Shareable和Unshareable兩個(gè)值音诫,默認(rèn)為Shareable
比如惨奕,配置數(shù)據(jù)庫(kù)連接池就可在此配置
<resource-ref>
<description>JNDI JDBC DataSource of shop</description>
<res-ref-name>jdbc/sample_db</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>