Servlet配置及使用詳解

1. Servlet介紹

Sun提供的一種動態web資源開發技術,本質上就是一段java小程序。

可以將Servlet加入到Servlet容器中運行。

tomcat既是web容器也是Servlet容器。


1.1 如何創建Servlet

先寫一個類,實現sun公司定義的Servlet接口:

package java_web.Servlet;//這裏是我自己定義的包,後面用到

import java.io.*;
import javax.servlet.*; //servlet-api.jar中的包

//GenericServlet類已經幫我們實現了Servlet的接口,只需繼承並編寫service即可
public class HelloServlet extends GenericServlet{
    public void service(ServletRequest req, ServletResponse res) {
        res.getWriter().write("hello Servlet");
    }
}

上面是參照javaee api文檔寫的一個簡單Servlet,然後在同目錄下運行指令:

javac HelloServlet.java

會報錯,找不到那些關於Servlet的包,因爲我們一般使用的是JavaSE,缺少這些必要的package,而tomcat實際上是自帶有的,所以我們可以通過命令設置臨時的classpath,以便編譯:

set classpath=%classpath%;%CATALINA_HOME%\lib\servlet-api.jar;

再運行javac HelloServlet.java,這時就應該可以成功了,不過我們需要的是一個文件夾package而不是單獨的class文件,所以應該使用指令:

javac -d . HelloServlet.java

會在當前目錄下生成一個包含了HelloServlet.classpackage,名爲java_web

接下來,將該整個包文件放到Tomcat中

tomcat\webapps\news\WEB-INF\classes\java_web

news是一個web應用,需要將剛創建的整個包文件java_web放到該應用的classes中。

還需要配置WEB-INF中的web.xml(可以參考conf中的web.xml):

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0">

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>java_web.Servlet.HelloServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/servlet/first</url-pattern>
    </servlet-mapping>

</web-app>

這樣就通過URL映射到了相應的類文件。

在瀏覽器地址欄中訪問localhost/news/servlet/first,即可看到剛纔創建的servlet文件效果。


1.2 使用IDEA創建Servlet項目

  1. 點擊Filenewproject

    newpro

  2. 選擇Java Enterprise,並在右邊配置好版本和路徑,點擊Next,再勾選Create project from template(或者在上一步勾選 Web Application):

    template

  3. 點擊Next,選擇項目的名稱和目錄,設置服務器爲tomcat

    proconf

  4. 點擊finish後,項目創建完畢,用默認的配置直接啓動Tomcat運行該項目,會生成out文件夾及其內部結構,並自動打開瀏覽器跳轉到index.jsp

    prodir

    src目錄存放的是一些待編譯的java文件,web目錄存放的是網頁的web資源。out目錄存放的是編譯輸出文件,其中web_war_exploded的名稱意爲,項目名稱爲web的應用資源經過打包的war文件展開後的結構。

    爲什麼會出現這個呢?其實當我們啓動項目,IDEA會自動生成這樣的war文件,部署運行在Tomcat服務器上,只是爲了方便所以開啓了exploded模式,展開顯示給開發者,可以在Project Structure中修改爲war模式。因此,某個module有了artifacts 就可以部署到應用服務器中了。

    官方對artifacts的解釋:

    An artifact is an assembly of your project assets that you put together to test, deploy or distribute your software solution or its part. Examples are a collection of compiled Java classes or a Java application packaged in a Java archive, a Web application as a directory structure or a Web application archive, etc.

    artifact是一個項目裏資源的整合,比如說一些Java類文件,web資源等,我們可以對它們進行測試、部署或發佈。

  5. 整個過程看似順理成章,但是別忘了,我們是用IDEA生成的項目默認配置啓動的,雖然運行成功,我們卻不知其所以然。下面對它的一些相關配置進行解釋。


1.3 項目相關配置

首先要明確,我們有兩塊需要配置:Project StructureRun/Debug Configurations,前者指的是剛創建的這個web項目的結構相關配置,後者指的是我們運行這個項目所用的相關配置,即Tomcat服務器的配置。


關於Project Struture

理解 IntelliJ IDEA 的項目配置和Web部署

IDEA裏面的facets和artifacts的講解

上面的文章已經對項目配置信息解釋得很清楚了,相信認真看完之後,都能明白如何配置。需要強調的是,別對目錄結構有疑惑,web資源原封不動輸出,src編譯輸出到WEB-INF,種種設計是爲了讓我們能夠更方便地開發web項目。


關於Run/Debug Configurations

理解了上面文章裏的項目配置後,關於Run/Debug ConfigurationsTomcat設置,也不在話下,需要注意的是,記得在Deployment選項中給Tomcat添加資源路徑,也就是將url與web應用的資源包進行映射,此處顯示的應用資源包就是outartifactsweb_war_exploded目錄。

tomconf


1.4 Servlet的調用過程

服務器處理請求的過程:

  1. 分析出當前請求的是哪臺虛擬主機:
    1. 查看請求頭中的Host,分析訪問的是哪臺主機。
    2. 如果沒有該屬性,則訪問缺省虛擬主機。
  2. 分析訪問的是虛擬主機中哪個web應用;
  3. 分析要訪問的是這個web應用中的哪個資源(靜態資源直接獲取);
  4. 查看web.xml文件,是否有對應的虛擬路徑,有則使用該路徑對應的資源(如Servlet)做響應。

Servlet生命週期:

Servlet在第一次被訪問到的時候,服務器會創建出Servlet對象,並立即調用init方法做初始化操作,創建出來的對象會一直駐留在內存中,之後對這個Servlet的訪問都會導致其中的Service方法執行。當web應用移除容器或服務器關閉後,Servlet會啓動destroy方法,進行銷燬。

Servlet的繼承結構:

Servlet接口定義了Servlet應該具有的基本方法,GenericServlet是通用的Servlet實現,對於不常用的方法在這個實現類中進行了基本實現,而Service設計爲了抽象方法,需要子類去實現。HttpServlet是在通用Servlet的基礎上基於HTTP協議進行了進一步的強化,實現了Service方法,並判斷當前的請求方式,對應去調用doGet或doPost方法,所以一般我們開發Servlet只需要繼承HttpServlet即可。


1.5 Servlet細節問題

  • 一個Servlet可以對應多個mapping,因此一個Servlet可以有多個路徑來訪問。

  • url-pattern中的路徑可以使用*匹配符號進行配置,但是要注意,只能是/開頭/*結尾,或者*.後綴這兩種方式。

  • 如果一個url匹配到多個Servlet,會調用更精確mapping規則的那個Servlet*.後綴的優先級最低。

  • 如果在servlet元素中配置了一個<load-on-startup>1</load-on-startup>元素,那麼web應用在啓動時就會裝載並創建Servlet的實例對象,以及調用init方法,而不會等到第一次使用才創建。標籤中的數字代表啓動順序。

  • 缺省的Servlet配置:如果有一個Servlet的url-pattern被配置成了/,那麼其他規則匹配不上的請求就會由這個Servlet來處理。其實對靜態資源的訪問,就是Tomcat提供的缺省Servlet來處理的,甚至404等提示頁面也是由缺省Servlet來處理的,所以一般我們不改動缺省配置。

  • 由於Servlet默認在內存中只有一個對象,當多個線程同時運行,可能會引發線程安全問題。即使用同步鎖來解決,效率也比較低,因此我們儘量避免使用類(靜態)變量。




2. 和Servlet相關的對象

2.1 ServletConfig

代表當前Servletweb.xml中的配置信息。

Servlet的配置文件中,可以使用一個或多個<init-param>標籤爲servlet配置一些初始化參數。當Servlet配置了初始化參數後,web容器在創建Servlet實例對象時,會自動將這些初始化參數封裝到ServletConfig對象中,並在調用servletinit方法時,將ServletConfig對象傳遞給servlet,進而開發者可以通過該對象獲取當前servlet的初始化參數信息。

<servlet>
    <servlet-name>DemoName</servlet-name>
    <servlet-class>cn.demo.FirstServlet</servlet-class>
    <init-param>
        <param-name>param1</param-name>
        <param-value>value1</param-value>
    </init-param>
</servlet>

可以獲取當前Servletweb.xml中配置的名稱;

可以獲取當前Servlet中配置的初始化參數;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //獲取Servlet的config對象
    ServletConfig config = this.getServletConfig();

    //獲取當前Servlet在web.xml中配置的名稱
    String sName = config.getServletName();

    //獲取配置的初始化參數
    String value = config.getInitParameter("param1");
}



2.2 ServletContext

web容器(Tomcat)在啓動時,會爲 每個 web應用程序都創建一個ServletContext對象,它代表當前web應用。

如何獲取一個ServletContext對象:

ServletConfig config = this.getServletConfig();
ServletContext context = config.getServletContext();
//或者直接使用this.getServletContext();其實一樣的原理
//this指的是HttpServlet對象


ServletContext對象有哪些作用?

作用一:數據共享

ServletContext作爲域對象,可以在整個web應用範圍內共享數據。

ServletContext context = this.getServletContext();

//一個Servlet設置屬性: void setAttribute(String, Object)
context.setAttribute("name", "Tom");

//在另一個Servlet中使用域對象獲取屬性: Object getAttribute(String)
//注意獲取的結果是一個對象,需要轉換類型才能輸出
String v = (String)context.getAttribute("name"); //Tom

//刪除屬性
context.removeAttribute("name");

生命週期

當服務器啓動web應用創建出ServletContext對象後,域產生。

當web應用被移除或服務器關閉,隨着web應用的銷燬域也銷燬。


作用二:獲取初始化參數

前面我們提到ServletConfig可以獲取當前Servlet的一些初始配置參數,那我們如何獲取ServletContext(web應用)的初始化配置信息呢?

配置共享參數

<context-param>
    <param-name>param1</param-name>
    <param-value>value1</param-value>
</context-param>

獲取初始化配置信息

//獲取域對象
ServletContext context = this.getServletContext();

//獲取應用的初始化配置信息
String value = context.getInitParameter("param1");

//可以使用枚舉,獲取完整的信息
Enumeration enumeration = context.getInitParameterNames();
while(enumeration.hasMoreElements()){
    String name = (String) enumeration.nextElement();
    String value = context.getInitParameter(name);
    System.out.println(name+":"+value);
}

現在需要理解幾個名詞:

請求參數:

瀏覽器發送過來的請求中的參數信息。

初始化參數:

在web.xml中爲ServletServletContext配置的初始化時帶有的基本參數。

域屬性:

四大作用域中存取的鍵值對。(後面會提到)


作用三:實現Servlet的轉發

請求轉發:服務器內部進行資源流轉。

注意:

請求轉發是一次請求一次響應,而請求重定向是兩次請求兩次響應。

DispatchServlet:

//獲取ServletContext對象,調用方法再獲取請求轉發對象
ServletContext sc = this.getServletContext();

//創建資源轉發對象RequestDispatcher,參數爲轉向的路徑
RequestDispatcher rd = sc.getRequestDispatcher("/news/goal");

//實現資源的轉發,目的地:DestServlet
rd.forward(request, response);

DestServlet:

//接收轉發過來的請求
response.getWriter().write("I received the request from the dispatcher");


作用四:加載資源文件

Servlet中讀取資源文件時:

由於路徑會相對於程序啓動的目錄,在web環境下,就是tomcat啓動的目錄,所以會找不到資源文件。爲了解決這樣的問題,ServletContext提供了getRealPath方法,在這個方法中傳入一個路徑,這個方法的底層會在傳入路徑前拼接當前web應用的硬盤路徑,從而得到當前資源的硬盤路徑,這種方式可以在任何發佈環境下獲取正確的資源路徑。

//可以利用ServletContext讀取資源文件,如:根目錄的config.properties

//先看看默認會在哪裏找資源
File file = new File("config.properties");
System.out.println(file.getAbsolutePath());
// ...tomcat\bin\config.properties 會去tomcat的啓動目錄中尋找資源

//用Properties讀取資源信息
Properties prop = new Properties();
//從指定的路徑加載讀取資源
prop.load(new FileReader(this.getServletContext().getRealPath("config.properties")));

System.out.println(prop.getProperty("username"));
System.out.println(prop.getProperty("password"));

在實際場景中,往往是Servlet收發請求,而業務邏輯的處理則在另外的Service類中執行,這種類沒有繼承HttpServlet,沒有ServletContext,該如何獲取資源文件呢?

如果在非Servlet環境下要讀取資源文件,可以採用類加載器加載文件的方式讀取資源。

//HandleServlet.java
//這裏主要收發請求
//調用service的方法去處理業務邏輯
Service ser = new Service();
ser.method1();
--------------------------------------
//Service.java
public class Service {
    //在這裏處理大部分的業務邏輯(Servlet只負責收發請求)
    public void method1() throws IOException {
        Properties prop = new Properties();

        //使用類加載器去加載資源,默認當前目錄是WEB-INF的classes下
        prop.load(new FileReader(Service.class.getClassLoader().getResource("../../config.properties").getPath()));
        //或者使用getResource("...").toString()獲取路徑

        //讀取文件
        System.out.println("ClassLoader get username:"+prop.getProperty("username"));
        System.out.println("ClassLoader get password:"+prop.getProperty("password"));
    }
}



2.3 ServletResponse

ServletResponse是通用的response,提供了一個響應應該具有的基本屬性和方法。
HttpServletResponseServletResponse的基礎上針對於Http協議強化了一些屬性和方法。

2.3.1 輸出數據

方式一 getOutputStream:

//將字符串編碼轉換爲輸出流發送
response.getOutputStream().write("中國".getBytes());
//如果getBytes()不指定編碼,則按照操作系統的默認去編碼,即GB2312
//而瀏覽器在網頁沒有指定charset時默認也是按照操作系統默認編碼來解碼

//一般網絡傳輸使用utf-8字符集,所以應該指定編碼
response.getOutputStream().write("中國".getBytes("utf-8"));
//同時應該在響應頭明確指定瀏覽器的解碼方式
response.setHeader("Content-Type", "text/html;charset=utf-8");

方式二 getWriter:

//效果和setHeader("Content-Type", "text/html;charset=utf-8")一樣
response.setContentType("text/html;charset=utf-8");

//指定在發送字符串時使用的編碼格式,如果不指定,會使用iso8859-1
//response.setCharacterEncoding("utf-8"); 
//其實這句在指定Content-Type時會自動調用

//編解碼格式一致,可以發送
response.getWriter().write("中國");


2.3.2 文件下載

文件下載需要使用到一個響應頭屬性:Content-Disposition,需要注意,在headers中只能出現iso8859-1中的字符,不能出現中文,因此文件名如果是是中文,需要經過url編碼才能傳輸,使用類URLEncoder編碼,URLDecoder解碼。

//headers中不能出現中文
response.setHeader("Content-Disposition","attachment;filename="+URLEncoder.encode("美女.jpg","utf-8"));

//獲取(文件)輸入流
InputStream in = new FileInputStream(this.getServletContext().getRealPath("1.jpg"));

//創建輸出流
OutputStream out = response.getOutputStream();

byte[] bs = new byte[1024];
int i = 0;
//寫入到輸出流
while((i=in.read(bs)) != -1){
    out.write(bs, 0, i);
}

//關閉輸入流
in.close();

編解碼:

String str = "中國";
String str2 = URLEncoder.encode(str, "utf-8");

String str3 = URLDecoder.decode(str2, "utf-8");//中國


2.3.3 實時刷新頁面

//每隔一秒刷新一次頁面,輸出時間
response.getWriter().write(new Date().toLocaleString());
response.setHeader("Refresh", "1");

//使用最多的:重定向
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("註冊成功,三秒後回到主頁...");
response.setHeader("refresh", "3;url=/index.jsp");

//更常用的,是在一個html頁面中設置重定向
<meta http-equiv="Refresh" content="3;url=/index.jsp">


2.3.4 請求重定向

利用response設置狀態碼爲302,並設置響應頭Location爲要重定向到的地址,就可以實現請求重定向操作了。

response.setStatus(302);
response.setHeader("Location", "/index.jsp");

//或者直接簡化成一條語句
response.sendRedirect("/index.jsp");

在大部分時候請求重定向和轉發的效果是差不多的,這時推薦使用轉發,以減少對服務器的訪問。而在某些情況下是需要使用轉發的,目的往往是爲了改變瀏覽器地址欄裏的地址(如登錄成功後轉到主頁)和更改刷新操作(如加入商品到購物車後轉到購物車頁面的操作)。


2.3.5 控制資源緩存

控制不做緩存:

response.setIntHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");

控制緩存時間:

//緩存一個月。setDateHeader代表設置屬性值爲日期類型
//是long類型的數值,爲時間戳,在這個時間戳之前緩存有效
response.setDateHeader("Expires", System.currentTimeMillis()+1000l*3600*24*30);
//別忘了加l,代表long型數值,只要運算中有任意一個數帶l,結果就是long型
//如果不加l,由於int類型容不下這麼大的數值,往上累計,最終會變成負數


2.3.6 Response注意事項

  • getOutputStreamgetWriter這兩個方法互相排斥,調用了其中的任何一個方法後,就不能再調用另一個方法。轉發是一次請求,一次響應。因此在Servlet轉發的時候,注意在兩個Servlet中使用相同的方法。

  • Servlet程序向ServletOutputStreamPrintWriter對象中寫入的數據將被Servlet引擎從response裏面獲取,這些數據會作爲響應消息的正文,然後再與響應狀態行和各響應頭組合後輸出到客戶端。

  • Servletservice方法結束後,Servlet引擎將檢查getWritergetOutputStream方法返回的輸出流對象是否已經調用過close方法,如果沒有,tomcat將自動調用close方法關閉該輸出流對象。所以一般不要自己在Servlet中關閉這個流,但輸入流需要自己關閉。


2.3.7 輸出驗證碼圖片

方法介紹:

  • 建立BufferedImage對象,指定圖片的長寬和類型

    BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
  • 取得Graphics對象,用來繪製圖片

    Graphics graphics = image.getGraphics();
  • 繪製背景顏色

    graphics.setColor(Color.WHITE);
    graphics.fillRect(0,0,width,height);
  • 繪製邊界

    graphics.setColor(Color.BLUE);
    graphics.drawRect(0,0,width-1,height-1);
  • 生成隨機數

    Random random = new Random();
    random.nextInt(n);//生成0到n的隨機數,包括0,不包括n
  • 繪製干擾線

    graphics.drawLine(x1,y1,x2,y2);//線段的兩個端點,隨機生成
  • 設置字體

    如果驗證碼是中文,要使用中文的字體庫
    graphics.setFont(new Font("宋體", Font.PLAIN, 20));//字體,粗細傾斜等,size
  • 通過詞庫生成隨機驗證碼內容

    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
    如果是漢字:\u4e00 —— \u9fa5
    graphics.drawString(str, x, y);//str是字符串,x是left,y是top(偏移量)
  • 設置旋轉

    Graphics2D graphics = (Graphics2D)image.getGraphics();
    graphics.rotate(theta, x, y);
    //注意這裏的theta是弧度,如果旋轉45度,要寫成 45d/180*Math.PI或0.25*Math.PI
    //否則45/180會是0,45d代表double類型的45
  • 釋放此圖形的上下文以及它使用的所有系統資源

    graphics.dispose();
  • 通過ImageIO對象的write靜態方法將圖片輸出(輸出後response已經commit了)

    ImageIO.write(image, "jpg", res.getOutputStream());

驗證碼的繪製

//1.在內存中構建一張圖片
int height = 30;
int width = 120;
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

//2.獲取圖像上的畫布
Graphics2D g = (Graphics2D)img.getGraphics();

//3.設置背景色
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0,0,width,height);

//4.設置邊框
g.setColor(Color.BLUE);
g.drawRect(0,0,width-1,height-1);

//5.畫干擾線,線的兩端座標都是隨機生成的
for (int i = 0; i < 5; i++) {
    g.setColor(Color.RED);
    g.drawLine(randNum(0, width), randNum(0, height), randNum(0, width), randNum(0, height));
}

//6.繪製漢字
String base = "\u7684\u4e00\u4e86\u662f";//這裏可以放置大量常用漢字的Unicode編碼
for (int i = 0; i < 4; i++) {
    //適當把rgb值往前調整,可避免字的顏色過分亮麗,影響人的識別
    g.setColor(new Color(randNum(0,150),randNum(0,150),randNum(0,150)));
    g.setFont(new Font("黑體", Font.BOLD, 20));
    //讓畫布適當旋轉
    int r = randNum(-45, 45);
    g.rotate(1.0*r/180*Math.PI, 5+(i*30), 22);
    g.drawString(base.charAt(randNum(0,base.length()-1))+"",5+(i*30), 22);
    //繪製完一個字,旋轉回原位,否則後面的字會在該基礎上繼續旋轉
    g.rotate(-1.0*r/180*Math.PI, 5+(i*30), 22);
}

//7.將圖片輸出到瀏覽器
ImageIO.write(img, "jpg", response.getOutputStream());
//注意,此時,response已經結束,如果在下面再寫setHeader、write是無效的

//細節問題
System.out.println(45/180*Math.PI);//0.0
System.out.println(45d/180*Math.PI);
System.out.println(0.25*Math.PI);

//一個用來生成隨機數的方法
private Random rand = new Random();
private int randNum(int begin, int end){
    return rand.nextInt(end-begin) + begin;
}

同時要讓驗證碼頁面不緩存,每次刷新:

response.setDateHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");

注意html頁面在使用驗證碼的時候,切換驗證碼只需改變src即可再次發送請求,後臺不需改動:

function changeImg(img){
    img.src = "/news/valiImg?time=" + new Date().getTime();
}



2.4 ServletRequest

同理,ServletRequest也是一個通用的request,提供一個request應該具有的基本方法,而HttpServletRequest是針對http協議進行了進一步的增強的request

2.4.1 獲取客戶機信息

//獲取客戶端請求的完整url
String url = request.getRequestURL().toString();

//獲取客戶端請求的資源部分名稱
String uri = request.getRequestURI();// /news/index.jsp

//獲取請求行中參數部分
String qStr = request.getQueryString();//name=Tom&age=18

//獲取請求客戶端的ip地址
String ip = request.getRemoteAddr();

//獲取客戶機的請求方式
String method = request.getMethod();

//獲取當前web應用的名稱,可以用這個來設置路徑,以便後續項目遷移
String name = request.getContextPath();
response.sendRedirect(request.getContextPath()+"/index.jsp")


2.4.2 獲取請求頭信息

String value = request.getHeader("Host");

//獲取所有請求頭
Enumeration<String> enumeration = request.getHeaderNames();
while(enumeration.hasMoreElements()){
    String name = enumeration.nextElement();
    String value = request.getHeader(name);
    System.out.println(name+":"+value);
}

//獲取具體類型的客戶機請求頭
getIntHeader(name)方法 —— int
getDateHeader(name)方法 —— long(日期對應毫秒)

使用referer請求頭防盜鏈

//假設別人在自己網站上使用一個a標籤盜用了我們的localhost/news.html,我們可以在請求頭中驗證referer是否爲我們可信的站點,如果不是,就不讓訪問,並且重定向到我們的主頁

String ref = request.getHeader("Referer");
if(ref==null || "".equals(ref) || !ref.startsWith("http://localhost")){
    response.sendRedirect(request.getContextPath()+"/index.html");
}


2.4.3 獲取請求參數

getParameter(name) —— String 通過name獲得值
getParameterValues —— String[] 通過name獲得多值 checkbox
getParameterNames —— Enumeration<String> 獲得所有name
getParameterMap —— Map<String,String[]> key:name,value:多值

解決參數傳遞過來亂碼的問題:

一般在html表單中數據是utf-8編碼,而服務器中默認使用的編碼是iso8859-1,對於表單中的中文數據是無法解碼的,因此需要顯式指定請求的解碼方式:

request.setCharacterEncoding("utf-8");

注意,上面指定的是服務器以什麼編碼來解碼http請求的實體內容,所以只適合POST請求,而GET請求的參數是附帶在URL後的,需要我們手動進行編解碼:

//對於get提交參數中的亂碼,需要我們先按iso8859-1編碼爲字節,然後再按utf-8解碼爲字符串。
String username = request.getParameter("username");
username = new String(username.getBytes("iso8859-1"), "utf-8");
//當然也可以解碼post請求


2.4.4 利用請求域傳遞對象

作用範圍:整個請求鏈上。

生命週期:當服務器收到一個請求,創建出代表請求的request對象,request域開始,當請求結束,服務器銷燬代表請求的request對象,request域結束。

方法:

setAttribute
getAttribute
removeAttribute
//其實四大作用域都有這三個方法。

使用請求域:

String value = (String) request.getAttribute("name"); //返回一個對象,需要類型轉換

String result = "apple";
request.setAttribute("result", result);

//對於請求附帶的參數,使用request域即可,ServletContext域範圍過大
//this.getServletContext().getRequestDispatcher("/servlet/demo").forward(request,response);
request.getRequestDispatcher("/show.jsp").forward(request,response);

//在show.jsp中使用request域的屬性
<%= (String) request.getAttribute("result") %>

作用:

在整個請求鏈範圍內共享數據,通常我們在Servlet中處理好的數據會存入request域後將請求轉發到jsp頁面來進行展示。


2.4.5 實現請求轉發和請求包含

可以像ServletContext對象一樣實現請求轉發:

request.getRequestDispatcher("").forward(request,response);

請求轉發時,如果已經有數據被寫入到了request的緩衝區,但這些數據還沒有被髮送到客戶端,則請求轉發時,這些數據將會被清空

response.getWriter().write("something...");
resquest.getRequestDispatcher("/servlet/demo").forward(request,response);

//servlet.demo
response.getWriter().write("other things...");

//最後輸出的只是other things,而something被清空了

但是被清空的只是響應中的實體內容頭信息並不會被清空(比如setContentType("text/html;charset=utf-8"))。

如果寫入了緩衝區,並且還強制刷新了緩衝區,則會報錯:

response.getWriter().write("something...");
response.getWriter().flush();//將緩衝區信息輸出
resquest.getRequestDispatcher("/servlet/demo").forward(request,response);//會報錯,無法轉發

請求轉發時,如果已經有數據發送給了瀏覽器,那麼再進行請求轉發,不能成功,會拋出異常。

一個Servlet中兩次請求轉發也是不行的。


請求包含:

將兩個資源的輸出進行合併後再輸出,同樣也是使用了轉發器對象。

this.getServletContext().getRequestDispatcher("").include(request,response);

request.getRequestDispatcher("").include(request,response);

被包含的Servlet程序不能改變響應消息的狀態碼和響應頭,如果它裏面存在這樣的語句,這些語句的執行結果將被忽略。

//servletA.java
response.getWriter().write("from servletA");
request.getRequestDispatcher("/servletB").include(request,response);

//servletB.java
response.getWriter().write("from servletB");

//最後會輸出 from servletA from servletB
//這就是請求包含的作用,一般常用來做頁面佈局,將重複的header footer包含


2.4.6 請求重定向和請求轉發

請求重定向:response.sendRedirect();
請求轉發:  request.getRequestDispatcher().forward();
請求包含:  request.getRequestDispatcher().include();
  • 如果需要在資源跳轉時利用request域傳遞域屬性則必須使用請求轉發。
  • 如果希望資源跳轉後修改用戶的地址欄則使用請求重定向。
  • 如果使用請求轉發和重定向都可以,則優先使用請求轉發,減少瀏覽器對服務器的訪問次數。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章