1. 程式人生 > 其它 >javaweb之servlet、request和response

javaweb之servlet、request和response

Servlet

Javaweb中使用的tomcat伺服器,利用servlet來接收和處理來自客戶端的動態請求。

1、簡單說明

首先通過簡單的案例來進行實現。

既然是通過servlet來接收並處理請求,那麼利用到的肯定有servlet的內容,如何和servlet來進行建立聯絡?因為servlet是介面,是一種規範,所以實現介面中的方法即可。

public class ServletHelloWorld  implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("初始化方法");
        System.out.println("servletConfig對應的值是:"+servletConfig);
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("hello,world");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
        System.out.println("銷燬方法");
    }
}

然後如何讓使用者和伺服器端的程式建立起來聯絡?一般來說,客戶在瀏覽器端輸入路徑,傳送請求給伺服器端,然後讓伺服器端來進行響應,所以路徑和servlet之間應該建立起來關聯:

在web.xml中來進行資訊配置:

    <servlet>
        <servlet-name>servletHelloWorld</servlet-name>
        <servlet-class>com.guang.quickstart.ServletHelloWorld</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>servletHelloWorld</servlet-name>
        <!--只要是hello的請求都將會交給ServletHelloWorld來進行處理-->
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

從上面的配置資訊中可以很明顯的理解到,如果訪問的是/hello這個字尾的,那麼這個請求將會交給com.guang.quickstart.ServletHelloWorld這個類來進行處理,這樣子來描述是比較準備的。servlet和servlet-mapping形成對映。

當ServletHelloWorld第一次被訪問的時候,會執行其生命週期方法。

上面寫的ServletHelloWorld實現servlet介面中的五個方法中,有三個是生命週期方法。

分別是init、service和destroy方法,分別對應的是初始化、服務、銷燬方法。

當ServletHelloWorld第一次被訪問,會首先執行init方法,進行一些操作,這個方法只會執行一次;

當ServletHelloWorld無論是在第一次還是在第N次被訪問的時候,都將會執行service方法;

在伺服器進行關閉的時候執行destroy方法。

當然,通常來說,我們會將init方法的執行提前到容器載入的時候,也就是說可以配置servlet類的初始化時機。

銷燬的時候也不會做一些其他的操作。

2、路徑配置

從名字上可以看到是路徑匹配,和正則表示式有點類似。

在上面的web.xml中的url-pattern標籤下,我們配置了/hello字尾的路徑交由com.guang.quickstart.ServletHelloWorld這個類來進行處理。

對於這裡的路徑有多種配置方式,後面可能會用到,所以在這裡來進行詳細的說明下:

url-pattern路徑的配置一共有三種:

1、完全路徑匹配;2、目錄匹配;3、副檔名匹配

完全路徑匹配:以/開頭

<!--完全路徑匹配: 以 / 打頭,以定義的路徑結尾-->
   <servlet-mapping>
        <servlet-name>hello0</servlet-name>
        <url-pattern>/hello0</url-pattern>
   </servlet-mapping>

目錄匹配:以/開頭,以*結尾

 <!-- 目錄匹配: 以/ 打頭, 以*結尾 *表示後面的內容可以有,也可以沒有-->
 <servlet-mapping>
        <servlet-name>hello02</servlet-name>
        <url-pattern>/hello02/*</url-pattern>
    </servlet-mapping>

副檔名匹配:不能夠以/開頭,而是以*開頭的

<!--字尾名匹配的方式 : 以*打頭, 以後綴名結尾-->
    <servlet-mapping>
        <servlet-name>hello02</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

url-pattern的訪問優先順序順序是:完全路徑匹配>目錄匹配>副檔名匹配

3、servlet的建立

在我們實現了servlet介面之後,實現了其必要的方法,可以看到的是,這裡的servlet實現類並非是我們自己來進行建立的,而是由tomcat伺服器來進行建立的。tomcat容器給我們建立的是servlet類例項是單例的,如果說多個執行緒來同時操作這個單例物件,如果在當前的servlet實現類中使用成員變數,那麼將會導致執行緒安全問題的發生,所以這點應該要做到避免。執行緒安全問題參考多執行緒章節。

多執行緒指的是每次請求都會創建出來一個新的執行緒來對請求來進行處理,最終都會呼叫單例物件的service方法。

4、ServletConfig

從名字中可以看出來這個物件是servlet的配置的意思。

servlet的配置資訊可以通過註解WebServlet來進行檢視:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
    // servlet的名字,這裡也就是在web.xml中配置的servlet的名字
    String name() default "";
	
    // 預設值
    String[] value() default {};
	
    // 路徑匹配。從這裡可以看到一個servlet可以對應的多個路徑
    // 額外說明下,一個路徑對應著一個servlet,多個路徑可以對應著一個servlet
    String[] urlPatterns() default {};

    // 載入啟動時機,-1預設第一次訪問的時候啟動。如果是1,2,3,數字越大代表優先順序越小
    int loadOnStartup() default -1;
	
    // 攜帶的初始化引數
    WebInitParam[] initParams() default {};

    boolean asyncSupported() default false;

    String smallIcon() default "";

    String largeIcon() default "";

    String description() default "";

    String displayName() default "";
}

從上面摘幾個分析下:

@WebServlet(urlPatterns = {"/one"},name = "UrlPatternServletOne",initParams = {@WebInitParam(name = "a",value = "b")})
public class UrlPatternServletOne implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        // 獲取得到裡面配置的值
        String a = servletConfig.getInitParameter("a");
        System.out.println(a); // name = "a",value = "b"中的b
        String servletName = servletConfig.getServletName();
        System.out.println(servletName); // name = "UrlPatternServletOne"中的name
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("hello,world");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

除此之外,還可以來進行配置啟動時機:

@WebServlet(urlPatterns = {"/one"},loadOnStartup = 1)
public class UrlPatternServletOne implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("hello,one");
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("hello,world");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

可以看到如果配置了這裡的啟動時機是大於0的,那麼在本地專案中啟動了之後就可以從控制檯上顯示出來。

但是如果說沒有配置大於0的值,而是預設的,那麼是在第一次訪問的時候來進行載入的。

5、Servlet的體系結構

檢視下官方文件說明:

定義所有 servlet 都必須實現的方法。 
servlet 是執行在 Web 伺服器中的小型 Java 程式。servlet 通常通過 HTTP(超文字傳輸協議)接收和響應來自 Web 客戶端的請求。 

要實現此介面,可以編寫一個擴充套件 javax.servlet.GenericServlet 的一般 servlet,或者編寫一個擴充套件 javax.servlet.http.HttpServlet 的 HTTP servlet。 

此介面定義了初始化 servlet 的方法、為請求提供服務的方法和從伺服器移除 servlet 的方法。這些方法稱為生命週期方法,它們是按以下順序呼叫的: 

構造 servlet,然後使用 init 方法將其初始化。 
處理來自客戶端的對 service 方法的所有呼叫。 
從服務中取出 servlet,然後使用 destroy 方法銷燬它,最後進行垃圾回收並終止它。 
除了生命週期方法之外,此介面還提供了 getServletConfig 方法和 getServletInfo 方法,servlet 可使用前一種方法獲得任何啟動資訊,而後一種方法允許 servlet 返回有關其自身的基本資訊,比如作者、版本和版權。 

從官方API中可以獲取得到一些重要的資訊:

1、servlet是執行在伺服器端的小型java程式;

2、tomcat容器已經實現了HTTP協議的封裝和解析,並對每個使用者的請求和響應做好了封裝。

3、想要寫一個servlet,推薦使用兩種方式。繼承GenericServlet或者是HttpServlet

4、生命週期方法以及生命週期方法的呼叫順序;

5、另外的兩個生命週期方法的作用。

那麼關於GenericServlet和HttpServlet如何來進行選擇,繼續 根據API文件來進行檢視:

GenericServlet說明:

定義一般的、與協議無關的 servlet。要編寫用於 Web 上的 HTTP servlet,請改為擴充套件 javax.servlet.http.HttpServlet。 
GenericServlet 實現 Servlet 和 ServletConfig 介面。servlet 可以直接擴充套件 GenericServlet,儘管擴充套件特定於協議的子類(比如 HttpServlet)更為常見。 

GenericServlet 使編寫 servlet 變得更容易。它提供生命週期方法 init 和 destroy 的簡單版本,以及 ServletConfig 介面中的方法的簡單版本。GenericServlet 還實現 log 方法,在 ServletContext 介面中對此進行了宣告。 

要編寫一般的 servlet,只需重寫抽象 service 方法即可。

從這裡可以看到:如果要是編寫web上的servlet,推薦使用HttpServlet;

因為GenericServlet已經實現了Servlet 和 ServletConfig 介面介面,我們自己寫的可以直接來進行擴充套件GenericServlet

這裡說明了,即使GenericServlet已經提供了servlet原始方法中的簡單版本,但是依然推薦使用HttpServlet。

再看下HttpServlet的說明:

提供將要被子類化以建立適用於 Web 站點的 HTTP servlet 的抽象類。HttpServlet 的子類至少必須重寫一個方法,該方法通常是以下這些方法之一: 
doGet,如果 servlet 支援 HTTP GET 請求 
doPost,用於 HTTP POST 請求 
doPut,用於 HTTP PUT 請求 
doDelete,用於 HTTP DELETE 請求 
init 和 destroy,用於管理 servlet 的生命週期內儲存的資源 
getServletInfo,servlet 使用它提供有關其自身的資訊 
幾乎沒有理由重寫 service 方法。service 通過將標準 HTTP 請求分發給每個 HTTP 請求型別的處理程式方法(上面列出的 doXXX 方法)來處理它們。 

同樣,幾乎沒有理由重寫 doOptions 和 doTrace 方法。 

servlet 通常執行在多執行緒伺服器上,因此應該意識到 servlet 必須處理併發請求並小心地同步對共享資源的訪問。共享資源包括記憶體資料(比如例項或類變數)和外部物件(比如檔案、資料庫連線和網路連線)。有關在 Java 程式中處理多個執行緒的更多資訊

這裡可以看到HttpServlet是一個抽象類,子類中必須重寫其中的一個doXxx方法。

但是這裡應該有一個問題,那就是為什麼原生的servlet中的介面中的宣告方法這裡沒有?而是重寫了doXxx方法?

通過上面的一句話來看出來:

service 通過將標準 HTTP 請求分發給每個 HTTP 請求型別的處理程式方法(上面列出的 doXXX 方法)來處理它們。 

也就是說對於每個請求來說,都將會請求分發出去對應的處理程式方法,也就是doXxx方法處理,那麼看下對應的方法呼叫關係:

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            // 通過強制型別轉換,轉換成http型別的
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException("non-HTTP request or response");
        }
		// 然後進行了方法過載
        this.service(request, response);
    }

再看下這個方法,可以看到在這個過載的service方法中呼叫了doXxx方法:

 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 獲取得到請求方式
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

這裡也就是上面為什麼說幾乎沒有理由重寫 service 方法的原因了。

但是對於一般的web開發來說,只需要實現doPost方法和doGet方法即可。

所以現在的開發方式對於我們來說,推薦使用的是繼承HttpServlet類,重寫其中的doGet方法和doPost方法:

在idea中直接使用快捷鍵即可:

@WebServlet(name = "HelloServlet", value = "/HelloServlet")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

可以看到自己寫的servlet類繼承了HttpServlet類之後,直接重寫了doGet和doPost方法,而不是去重寫service方法了。

6、ServletContext

servlet上下文物件,在一個servlet專案中,這個物件有且只有一個。也就是說在一個web專案中,都會有這樣一個servletContext物件的存在,這個物件代表的就是這個專案的,而不是屬於某一個servlet的。

這個物件的作用是什麼?如何來進行獲取?

這個servletContext物件作用分類:

1、可以存放資料和獲取得到資料,實現servlet之間的資料共享;

2、獲取得到檔案的MIME;

3、獲取得到檔案資源;

4、獲取得到全域性配置檔案;

因為第一個用的最多,所以就拿第一個來舉例子來進行說明,後面的後續用到了再說:

存取資料

新建兩個servlet類

@WebServlet(name = "HelloServletOne", value = "/HelloServletOne")
public class HelloServletOne extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.getServletContext().setAttribute("hello",new String("hello,world"));
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
@WebServlet(name = "HelloServletTwo", value = "/HelloServletTwo")
public class HelloServletTwo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object hello = this.getServletContext().getAttribute("hello");
        System.out.println("hello");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

先訪問One,再訪問Two,可以看到控制檯,輸出了對應的內容。

但是需要注意的是:如果沒有存入值,就直接來進行獲取,那麼將會出現NPE異常。

7、Request

在Servlet API中,定義了一個HttpServletRequest介面,它繼承自ServletRequest介面,專門用來封裝HTTP請求訊息。由於HTTP請求訊息分為請求行、請求頭和請求體三部分,因此,在HttpServletRequest介面中定義了獲取請求行、請求頭和請求訊息體的相關方法。Web伺服器收到客戶端的http請求,會針對每一次請求,分別建立一個用於代表請求的request物件、和代表響應的response物件。

request作用:

  • 操作請求行、頭、體的中的內容
  • 請求轉發
  • 儲存資料,能夠像servletContext一樣儲存資料

7.1、操作請求行

獲取客戶機資訊

​ 請求方式 請求路徑(URI) 協議版本

http://localhost:8080/servlet02_war_exploded/HelloServletThree?username=zs&password=123456

  • getMethod();獲取請求方式

  • getRemoteAddr() ;獲取客戶機的IP地址(知道是誰請求的)

  • getContextPath();獲得當前應用工程名(部署的路徑);

  • getRequestURI();獲得請求地址,不帶主機名

  • getRequestURL();獲得請求地址,帶主機名

  • getServerPort();獲得服務端的埠

  • getQueryString();獲的請求引數(get請求的,URL的?後面的. eg:username=zs&password=123456)

編寫程式碼來實現一下:

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String method = request.getMethod();
        System.out.println("請求方式是:----"+method);
        String requestURI = request.getRequestURI();
        System.out.println("請求的URI是:----"+requestURI);
        StringBuffer requestURL = request.getRequestURL();
        System.out.println("請求的URL是:----"+requestURL);
        String remoteAddr = request.getRemoteAddr();
        System.out.println("請求的遠端地址是:----"+remoteAddr);
        String contextPath = request.getContextPath();
        System.out.println("請求上下文路徑是:----"+contextPath);
        int serverPort = request.getServerPort();
        System.out.println("請求埠號是:----"+serverPort);
        String queryString = request.getQueryString();
        System.out.println("get請求方式引數是:----"+queryString);
    }

檢視控制檯輸出:

請求方式是:----GET
請求的URI是:----/servlet02_war_exploded/HelloServletThree
請求的URL是:----http://localhost:8080/servlet02_war_exploded/HelloServletThree
請求的遠端地址是:----0:0:0:0:0:0:0:1
請求上下文路徑是:----/servlet02_war_exploded
請求埠號是:----8080
get請求方式引數是:----username=zs&password=123456

獲取得到請求體中的引數的時候,因為tomcat>=8的時候,亂碼問題已經處理好了,而留給我們的只是處理post方式的。

因為客戶端提交給伺服器端的是一些中文資料,而伺服器端在進行解析的時候,因為解碼和編碼方式不同,導致了伺服器端拿到的是亂碼,所以一般來說這些都是交友給我們來設定的。

        req.setCharacterEncoding("UTF-8");

7.2、請求轉發

請求轉發是在伺服器內部來進行請求呼叫的,也就是說只能夠在伺服器內部來進行使用,不能夠跳轉到外部去。

request.getRequestDispatcher(url).forward(request, response);  //轉發

下面來通過靜態資源和動態資源分別來舉個例子:

/**
 * 將這個請求轉發到PictureServlet去
 */
@WebServlet(name = "HelloServletFour", value = "/HelloServletFour")
public class HelloServletFour extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("請求轉發");
        // 相對路徑
        request.getRequestDispatcher("PictureServlet").forward(request,response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
/**
 * 通過請求轉發,訪問到一個靜態頁面
 */
@WebServlet(name = "PictureServlet", value = "/PictureServlet")
public class PictureServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("我在這裡等著請求,等著關門打狗");
        // 來進行響應一下
        request.getRequestDispatcher("/WEB-INF/pic/git.png").forward(request,response);
//        request.getRequestDispatcher("/WEB-INF/en.html").forward(request,response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

可以看到,對於瀏覽器端來說:http://localhost:8080/servlet02_war_exploded/HelloServletFour

請求的是HelloServletFour這個servlet,但是因為請求轉發交由給PictureServlet來進行處理,然後由伺服器端來進行響應。

從這裡可以看到瀏覽器端地址是沒有發生變化的。

7.3、操作請求體

主要是用來獲取得到請求引數的。

獲得請求引數

法名 描述
String getParameter(String name) 獲得指定引數名對應的值。如果沒有則返回null,如果有多個獲得第一個。 例如:username=jack
String[] getParameterValues(String name) 獲得指定引數名對應的所有的值。此方法專業為複選框提供的。 例如:hobby=抽菸&hobby=喝酒&hobby=敲程式碼
Map<String,String[]> getParameterMap() 獲得所有的請求引數。key為引數名,value為key對應的所有的值。

現在我們已經可以使用request物件來獲取請求引數,但是,如果引數過多,我們就需要將資料封裝到物件。

​ 以前封裝資料的時候,實體類有多少個欄位,我們就需要手動編碼呼叫多少次setXXX方法,因此,我們需要BeanUtils來解決這個問題。

​ BeanUtils是Apache Commons元件的成員之一,主要用於簡化JavaBean封裝資料的操作。

使用步驟:

  1. 匯入jar

commons-beanutils-1.8.3.jar 和 commons-logging-1.1.1.jar

  1. 使用BeanUtils.populate(user,map)

通過一個例子來進行說明:

/**
 * 獲取得到引數,將其轉賬到物件中去
 */
@WebServlet(name = "ParamterServlet", value = "/ParamterServlet")
public class ParamterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        this.doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 獲取得到的引數
        String id = request.getParameter("id");
        String username = request.getParameter("username");
        System.out.println("對應的id是:"+id);
        System.out.println("對應的username是:"+username);
        System.out.println("--------------------");
        System.out.println("--------------------");
        System.out.println("--------------------");
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {
            String key = stringEntry.getKey();
            String[] value = stringEntry.getValue();
            for (String s : value) {
                System.out.println("對應的key是"+key+",對應的value是"+s);
            }
        }
        // 但是這種使用方式太過於麻煩,所以來使用API來進行操作
        User user = new User();
        try {
            BeanUtils.populate(user,parameterMap);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println(user);
    }
}

檢視控制檯輸出:

對應的id是:2
對應的username是:liguang
--------------------
--------------------
--------------------
對應的key是id,對應的value是2
對應的key是username,對應的value是liguang
User{id=2, username='liguang'}

7.4、存取值

這個也是request使用最多的地方

​ ServletContext: 可以存和取值,範圍是整個應用程式, AServlet 存值, BServlet能取值

​ request範圍: 一次請求內有效!!!

​ 域物件是一個容器,這種容器主要用於Servlet與Servlet/JSP之間的資料傳輸使用的。

Object getAttribute(String name) 
void setAttribute(String name,Object object)  
void removeAttribute(String name)  

可以看下這裡的操作

@WebServlet("/first")
public class FirstServlet  extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //存值
        req.setAttribute("username" , "張三");


        //自己取值
        String username = (String) req.getAttribute("username");
        System.out.println("FirstServlet::username=" + username);;


        System.out.println("first::hashcode" + req.hashCode());
        System.out.println("使用請求轉發跳轉到SecondServlet" );
        //使用請求轉發的方式跳轉到SecondServlet
        req.getRequestDispatcher("second").forward(req,resp);
    }
}

8、Response

在Servlet API中,定義了一個HttpServletResponse介面(doGet,doPost方法的引數),它繼承自ServletResponse介面,專門用來封裝HTTP響應訊息。由於HTTP響應訊息分為響應行、響應頭、響應體三部分,因此,在HttpServletResponse介面中定義了向客戶端傳送響應狀態碼、響應頭、響應體的方法

response的作用

相對request來說,response的功能就比較簡單了。

用來操作響應行、響應頭和響應體

操作響應行中,API提供的一個最重要的方法就是操作狀態碼。所以一定要記熟常見的狀態碼。

​ 200:成功

​ 302:重定向

​ 304:訪問快取

​ 404:客戶端錯誤

​ 500:伺服器錯誤

等等比較常見的狀態碼。

8.1、操作響應頭

這裡用來告知客戶端需要對伺服器端相應的流來做些什麼操作。web開發中就是告知瀏覽器端需要做一些什麼事情。

關注的方法: setHeader(String name,String value);

​ 常用的響應頭

​ Refresh:定時跳轉 (eg:伺服器告訴瀏覽器5s之後跳轉到百度)

​ Location:重定向地址(eg: 伺服器告訴瀏覽器跳轉到xxx)

​ Content-Disposition: 告訴瀏覽器下載

​ Content-Type:設定響應內容的MIME型別(伺服器告訴瀏覽器內容的型別)

8.2、操作響應體

響應體中封裝的內容影響這客戶端的展示情況,所以這裡也是非常重要的。

可以看下API文件,有兩種方式來進行操作:

ServletOutputStream getOutputStream() 
          Returns a ServletOutputStream suitable for writing binary data in the response. 
 PrintWriter getWriter() 
          Returns a PrintWriter object that can send character text to the client. 

但是這兩種方式獲取得到的流是互斥的,也就是說只能夠來選擇其中的一種來進行使用。

8.3、解決亂碼

解決字元流輸出中文亂碼問題

response.setContentType("text/html;charset=utf-8");

使用位元組輸出流輸出中文亂碼問題

//設定瀏覽器開啟方式
response.setHeader("Content-type", "text/html;charset=utf-8");
//得到位元組輸出流
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write("你好".getBytes("utf-8"));// 使用平臺的預設字元(utf-8)集將此 String 編碼為 byte 序列

可以看到對於響應這一步來說,相對於請求來說,麻煩了一步。請求是直接設定即可。

讓其和請求資料對比:

請求亂碼:

request.setCharacterEncoding("utf-8");

響應亂碼:

response.setContentType("text/html;charset=utf-8");

8.4、重定向

重定向是兩次請求,也就是說第一次請求的時候,伺服器端響應給客戶端的是另外一個請求的地址;

第二次客戶端重新發起請求,去尋找目標資源;

在這其中,瀏覽器端的位址列會發生變化。

request域物件中存放的資料不能夠獲取得到,因為這個時候第二次的請求就相當於是一次新的請求了,無法再次訪問得到上次的資料了。

這個時候會有兩種情況的發生:

1、如果請求的是外界資源,那麼什麼都不用做;

2、但是如果重定向後請求的是內部資源,那麼重定向不能夠訪問到web-inf資料夾下的資源。

直到今天才想明白tomcat就是一個最基本的jar包而已,只是說其可以用來處理web請求和響應的。

是因為其支援這種操作,支援對應的處理而已。

檔案下載:

/**
 * 下載檔案三部曲:設定兩個頭和一個流
 *
 * 需要利用到輸出流和輸入流來結合進行使用
 */
@WebServlet(name = "DownloadServlet", value = "/DownloadServlet")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
	
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        String fileName = "commons-beanutils-1.8.3.jar";
        // 獲取得到流物件
        InputStream resourceAsStream = servletContext.getResourceAsStream("/WEB-INF/lib/"+fileName);
        String mimeType = servletContext.getMimeType(fileName);
        // 設定兩個頭
        response.setContentType(mimeType);
        response.setHeader("Content-Disposition" , "attachment;filename="+fileName);
        ServletOutputStream outputStream = response.getOutputStream();
        byte [] buffer = new byte[1024];
        int len = 0 ;
        while( (len = resourceAsStream.read(buffer)) != -1 ){
            outputStream.write(buffer , 0 , len);
        }
        // 關不關無所謂,框架底層都會關的
        outputStream.close();
        resourceAsStream.close();
    }
}

9、總結

servlet規範:生命週期方法、單例多執行緒、訪問路徑和體系結構(如何正確選取使用哪種servlet)

request操作請求行、請求頭和請求體:

response操作響應行、響應頭和響應體:

亂碼產生原因和處理方式

千里之行,始於足下。不積跬步,無以至千里