javaEE(3)_servlet基礎

1、Servlet簡介

一、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便可看到顯示的內容.程序員

2、Servlet在web應用中的位置

ps:web應用目錄也就是web根目錄.web

3、servlet的調用過程和生命週期

一、從瀏覽器輸入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對象.

4、在Eclipse中開發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方法.

5、Servlet的一些細節

一、同一個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,不然全部的靜態資源將不能訪問.

6、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尚未很好的處理線程問題.

7、把servlet自身學明白,再把服務器傳遞給servlet的對象搞明白(servlet是由服務器來調用的),整個servlet就學通了,服務器傳遞給servlet的對象有Request、Response、ServletConfig、ServletContext、Cookie、Session等.

一、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中能夠爲其設置合理的緩存時間值,以免瀏覽器頻繁向服務器發送請求,提高服務器的性能.略

相關文章
相關標籤/搜索