一、Servlet是sun公司提供的一門用於開發動態web資源的技術,Sun公司在其API中提供了一個servlet接口,用戶若想用發一個動態web資源(即開發一個Java程序向瀏覽器輸出數據),須要完成如下2個步驟:html
1.編寫一個Java類,實現servlet接口.2.把開發好的Java類部署到web服務器中.例:java
package com.itcast.servlet; import java.io.IOException; import javax.servlet.*; public class ServletTest extends GenericServlet{ @Override public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { arg1.getOutputStream().write("ServletTest".getBytes()); } }
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <servlet> <servlet-name>ServletTest</servlet-name> <servlet-class>com.itcast.servlet.ServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletTest</servlet-name> <url-pattern>/ServletTest</url-pattern> </servlet-mapping> </web-app>
瀏覽器中輸入http://localhost:8080/工程名(可選)/ServletTest便可看到顯示的內容.程序員
ps:web應用目錄也就是web根目錄.web
一、從瀏覽器輸入http://localhost:8080/servletTest/ServletDemo,到看到hello word!頁面的整個訪問流程:數據庫
1>瀏覽器根據localhost:8080基於TCP鏈接,鏈接到web服務器apache
2>發送http請求到web服務器編程
3>web服務器從客戶端發送的HTTP請求中依次解析出客戶端要訪問的主機、web應用、web資源瀏覽器
4>根據web.xml文件找到要訪問的servlet資源,無論是否是靜態資源都走servlet,靜態資源由缺省的servlet管理,這個servlet在tomcat中,利用反射建立servlet實例對象,通過測試發現不管多少個客戶端,servlet只在第一次被訪問時建立一個緩存
5>servlet實例對象調用init方法完成servlet對象的初始化tomcat
6>建立表明請求的request對象和表明響應的response對象(request和response在每次請求時都會建立),而後調用service方法響應客戶端請求
7>service執行會向response對象寫入向客戶端輸出的數據
8>服務器從response中取出數據,構建一個HTTP響應,返回給客戶端
9>瀏覽器解析HTTP響應,提取數據顯示
二、servlet生命週期
在客戶端第一次訪問的時候建立servlet對象,執行init方法完成初始化,以後這個servlet對象常駐內存,而後執行service響應客戶端請求,每次請求來了都會執行該方法,在服務器關閉的時候執行destory方法摧毀servlet對象.
一、在eclipse中新建一個web project工程,eclipse會自動建立下圖所示目錄結構:
二、Servlet接口SUN公司定義了兩個默認實現類,分別爲:GenericServlet、HttpServlet.
HttpServlet指可以處理HTTP請求的servlet,它在原有Servlet接口上添加了一些與HTTP協議處理方法,它比Servlet接口的功能更爲強大.所以開發人員在編寫Servlet時,一般應繼承這個類,而避免直接去實現Servlet接口.HttpServlet在實現Servlet接口時,覆寫了service方法,該方法體內的代碼會自動判斷用戶的請求方式,如爲GET請求,則調用HttpServlet的doGet方法,如爲Post請求,則調用doPost方法.所以,開發人員在編寫Servlet時,一般只須要覆寫doGet或doPost方法,而不要去覆寫service方法.
一、同一個Servlet能夠被映射到多個URL上,即多個<servlet-mapping>元素的<servlet-name>子元素的設置值能夠是同一個Servlet的註冊名.
二、在Servlet映射到的URL中也可使用*通配符,可是隻能有兩種固定的格式:一種格式是「*.擴展名」,另外一種格式是以正斜槓(/)開頭並以「/*」結尾.(/adc/*.do這種是錯誤的)這樣有時一個URL能夠匹配多個映射,例:
三、Servlet是一個供其餘Java程序(Servlet引擎也就tomcat服務器)調用的Java類,它不能獨立運行,它的運行徹底由Servlet引擎來控制和調度.針對客戶端的屢次Servlet請求,一般狀況下,服務器只會建立一個Servlet實例對象,也就是說Servlet實例對象一旦建立,它就會駐留在內存中,爲後續的其它請求服務,直至web容器退出,servlet實例對象纔會銷燬.在Servlet的整個生命週期內,Servlet的init方法只被調用一次.而對一個Servlet的每次訪問請求都致使Servlet引擎調用一次servlet的service方法.對於每次訪問請求,Servlet引擎都會建立一個新的HttpServletRequest請求對象和一個新的HttpServletResponse響應對象,而後將這兩個對象做爲參數傳遞給它調用的Servlet的service()方法,service方法再根據請求方式分別調用doXXX方法.那麼有個問題大量併發的請求下會建立不少request和response服務器會不會崩掉呢,這裏不會,由於request和response的生命週期很短,一閃而過,這樣不會同時建立不少的對象.
四、若是在<servlet>元素中配置了一個<load-on-startup>元素,那麼WEB應用程序在啓動時,就會裝載並建立Servlet的實例對象、以及調用Servlet實例對象的init()方法,中間數字表明啓動級別,越小越先啓動.
<servlet> <servlet-name>invoker</servlet-name> <servlet-class> org.apache.catalina.servlets.InvokerServlet </servlet-class> <load-on-startup>2</load-on-startup> </servlet>
五、缺省的servlet
若是某個Servlet的映射路徑僅僅爲一個正斜槓(/),那麼這個Servlet就成爲當前Web應用程序的缺省Servlet. 凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它們的訪問請求都將交給缺省Servlet處理,也就是說,缺省Servlet用於處理全部其餘Servlet都不處理的訪問請求. 在<tomcat的安裝目錄>\conf\web.xml文件中(這個文件是全部web應用共享的),註冊了一個名稱爲org.apache.catalina.servlets.DefaultServlet的Servlet,並將這個Servlet設置爲了缺省Servlet,而且設置了<load-on-startup>1</load-on-startup>,也就是服務器啓動的時候就建立了這個servlet.當訪問Tomcat服務器中的某個靜態HTML文件和圖片時,其實是在訪問這個缺省Servlet,也就是全部的請求過來都會先訪問servlet,若是servlet能與對於的請求URL匹配,則訪問servlet資源,若是servlet資源沒有與URL匹配的,那麼就會去訪問這個缺省的servlet,去靜態資源中尋找,若是還找不到就會返回客戶端找不到資源頁面.因此本身不要映射缺省的servlet,不然全部的靜態資源將不能訪問.
一、當多個客戶端併發訪問同一個Servlet時,web服務器會爲每個客戶端的訪問請求建立一個線程,並在這個線程上調用Servlet的service方法,所以service方法內若是訪問了同一個資源的話,好比同一個文件,數據,數據庫等等,就有可能引起線程安全問題.
例1,這個代碼是不會有線程問題,由於沒有共享資源,每一個線程來了都會建立一個i:
public class ServletTest extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int i=0; i++; resp.getOutputStream().write(("i="+i).getBytes()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
例2,這個代碼有線程問題,i是共享資源,
public class ServletTest extends HttpServlet{ int i=0; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { i++; resp.getOutputStream().write(("i="+i).getBytes()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
例3:這個代碼也有線程問題,靜態變量是共享的,person類裏面有一個靜態的age
public class ServletTest extends HttpServlet{ int i=0; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Person p = new Person(); p.age++; resp.getOutputStream().write(("p.age"+p.age).getBytes()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
ps:這裏面就有一個可能引起內存泄露的問題,好比Person裏面有一個static的list,每一個線程來了add一個元素,過段時間系統就會崩掉,這種靜態的集合元素很容易引起內存泄露.
二、解決方法
1>在共享資源上加同步塊,可是不使用這種狀況,不然客戶端可能等很長時間.
2>servlet實現SingleThreadModel接口(這時一個標記接口,裏面啥都沒有),可是servlet引擎在處理客戶端請求的時候會針對每次請求建立一個servlet,天然沒有線程問題,可是很明顯時間久了會使內存崩潰,並且還有問題以下:
public class ServletTest extends HttpServlet implements SingleThreadModel{ int i=0; @Override//該子類方法不能拋出比父類更多的異常,因此不少異常必須處理 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { i++; resp.getOutputStream().write(("i="+i).getBytes()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
多個客戶端訪問,看到的i都是1,很明顯不是咱們想要的,因此這種方式已經廢棄了,到目前爲止,其實servlet尚未很好的處理線程問題.
一、ServletConfig
在Servlet的配置文件中,可使用一個或多個<init-param>標籤爲servlet配置一些初始化參數.當servlet配置了初始化參數後,web容器在建立servlet實例對象時,會自動將這些初始化參數封裝到ServletConfig對象中,並在調用servlet的init方法時,將ServletConfig對象傳遞給servlet(實際上是一個servlet一個ServletConfig).進而,程序員經過ServletConfig對象就能夠獲得當前servlet的初始化參數信息.例:
//ServletConfig用於封裝servlet的配置 public class ServletConfigTest extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //獲得一個參數 String value=this.getServletConfig().getInitParameter("data"); //獲得全部的 Enumeration e= this.getInitParameterNames(); while(e.hasMoreElements()){ String a=(String) e.nextElement(); String v=this.getServletConfig().getInitParameter(a); System.out.println(a+"="+v); } } }
<servlet> <servlet-name>ServletDemo5</servlet-name> <servlet-class>cn.itcast.ServletDemo5</servlet-class> <init-param> <param-name>data</param-name> <param-value>xxxx</param-value> </init-param> <init-param> <param-name>data2</param-name> <param-value>yyyy</param-value> </init-param> </servlet>
ps:這個對象的用處是有些數據不要程序中寫死,能夠經過配置的形式傳遞個程序,這樣之後修改的話不用從新修改源代碼編譯,直接修改配置文件便可,並且寫在配置中方便查找.
二、ServletContext
WEB容器在啓動時,它會爲每一個WEB應用程序都建立一個對應的ServletContext對象,它表明當前web應用,在服務器關閉時銷燬,與具體的請求沒法,注意這裏與ServletConfig生命週期的區別.ServletConfig對象中維護了ServletContext對象的引用,開發人員在編寫servlet時,能夠經過ServletConfig.getServletContext方法得到ServletContext對象.因爲一個WEB應用中的全部Servlet共享同一個ServletContext對象,所以Servlet對象之間能夠經過ServletContext對象來實現通信.ServletContext對象一般也被稱之爲context域對象(ServletContext實際上是一個容器,這個容器的做用範圍是web應用程序範圍).拿到ServletContext的兩種方式以下:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //獲得servletContext ServletContext context=this.getServletConfig().getServletContext(); ServletContext context2=this.getServletContext();//可直接拿到 }
三、servletContext的應用場景
一、多個Servlet經過ServletContext對象實現數據共享,例:
public class ServletDemo8 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String value = (String) this.getServletContext().getAttribute("data"); response.getOutputStream().write(value.getBytes()); } } public class ServletDemo7 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.getServletContext().setAttribute("data",123); } }
ps: servletContext的這個應用場景能夠用在聊天室中,具體寫法,略.
二、獲取WEB應用的初始化參數
web.xml中使用context-param配置,而後使用相似ServletConfig獲取配置的getInitParameter,getInitParameter這倆個方法來拿到參數,相似數據庫等數據能夠這樣配置.
<web-app> <context-param> <param-name>data</param-name> <param-value>zzzz</param-value> </context-param> </web-app>
三、實現Servlet的轉發(轉發客戶端只有一次請求,重定向有屢次)
由於servlet中不適合作輸出,要使用一行一行write,通常轉發給jsp去處理,開發中大量使用,例:
public class ServletDemo10 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String data="aaaaaaaaaa";
//目前只學了這種,可是有線程問題
this.getServletContext().setAttribute("data", data); RequestDispatcher rd=this.getServletContext().getRequestDispatcher("/1.jsp"); rd.forward(request, response); } }
<html> <body> <font color="red"> <% String data = (String)application.getAttribute("data");//application就是ServletContext out.write(data); %> </font> </body> </html>
ps:線程問題,當多個客戶端併發訪問同一個Servlet時,web服務器會爲每個客戶端的訪問請求建立一個線程,若是他們都訪問ServletContext時,這個ServletContext就是一個共享資源,會有線程問題,好比多個線程同時set一個key爲data的數據,那麼後一個線程可能覆蓋前一個線程的值,從而出現問題,因此開發中這裏通常使用request域.
四、利用ServletContext對象讀取資源文件
1>第一種方式
private void test1() throws IOException { //注意目錄的正確書寫 InputStream in=this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties"); //這時讀取properties的固定步驟 Properties props=new Properties(); props.load(in); System.out.println(props.getProperty("url")); }
二、只用傳統方式以下是不行的,以下:
private void test4() throws IOException { //這種相對路徑是tomcat的bin目錄,因此這種方法要在該目錄下創建文件夾classes,並把文件放在這裏 FileInputStream in2=new FileInputStream("classes/db.properties"); Properties props=new Properties(); props.load(in2); System.out.println(props.getProperty("url")); }
三、先獲得絕對路徑,在用傳統方式是能夠的,這種相對於第一種方式來講也是有需求的,好比下載中要得到文件的名稱,第一種方式直接轉爲了留沒法得到,而這種經過截取path便可獲得文件名.
private void test5() throws IOException { //獲得絕對路徑 String path=this.getServletContext().getRealPath("/WEB-INF/classes/db.properties"); FileInputStream in2=new FileInputStream(path); Properties props=new Properties(); props.load(in2); System.out.println(props.getProperty("url")); }
ps:上面是在servlet中經過ServletContext來讀取資源文件,可是實際開發中像加載數據庫等配置文件是不會在servlet中來讀取的,通常在dao中讀取,這時按以下方式讀取,不能傳遞ServletContext對象到dao層,破壞了軟件編程思想.
//若是讀取資源文件的程序不是servlet的話, //就只能經過類轉載器去讀了,文件不能太大,太大會直接撐破內存,那麼大文件怎麼讀取呢? //用傳遞參數方法很差,耦合性高 public class UserDao { private static Properties dbconfig=new Properties(); static { //方式一,這種類加載器只會加載一次,若是後期db.properties修改的話程序沒法讀到 InputStream in=UserDao.class.getClassLoader().getResourceAsStream("db.properties"); try { dbconfig.load(in); } catch (IOException e) { throw new ExceptionInInitializerError(e); } //方式二,後期db.properties修改的話程序能夠讀到(建議使用) URL url=UserDao.class.getClassLoader().getResource("db.properties"); String str=url.getPath(); //file:/C:/apache-tomcat-7.0.22/webapps/day05/WEB-INF/classes/db.properties try { InputStream in2=new FileInputStream(str); try { dbconfig.load(in2); } catch (IOException e) { throw new ExceptionInInitializerError(e); } } catch (FileNotFoundException e1) { throw new ExceptionInInitializerError(e1); } } public void update() { System.out.println(dbconfig.get("url")); } }
注:對於不常常變化的數據,在servlet中能夠爲其設置合理的緩存時間值,以免瀏覽器頻繁向服務器發送請求,提高服務器的性能.略