1、本章前言
對呀!現在都2020年了,時間過得真快,本文來說說現在Servlet它還有必要學嗎?因為Servlet已經是一個非常非常古老的技術了,而且在實際開發中幾乎不會用到,在面試中也幾乎不會問到Servlet相關的知識。所以我們不需要學習Servlet了嗎?這樣想就大錯特錯了。我們后面會學習到Struts2和SpringMVC框架,它兩的底層都是跟Servlet有關,所以Servlet還是很有必要的學習的,最好不要跳過它。我們只有打下堅實的基礎,后面的框架學習起來才能得心應手。
2、什么是Servlet
Servlet(Server Applet)是Java Servlet的簡稱,稱為小服務程序或服務連接器,用Java編寫的服務器端程序,主要功能在於交互式地瀏覽和修改數據,生成動態Web內容。這是百度百科上的一段話。說簡單點Servlet就是對客戶端發送過來的請求進行處理,並且作出相應的響應,其本質就是一個實現了Servlet接口的實現類。其過程如下:
- 客戶端發送請求至Web服務器端。
- 服務器將請求信息發送至Servlet。
- Servlet 生成響應內容並將其傳給服務器。響應內容動態生成,通常取決於客戶端的請求。
- 服務器將響應返回給客戶端。
注:servlet程序是由servlet容器(即tomcat服務器)進行管理,包括實例化、初始化、服務、銷毀的過程都由tomca在指定時間內完成。
服務器的三大組件:
- servlet:用於處理請求和響應
- filter:用於過濾請求和響應
- listener:用於監聽服務器的狀態
3、創建Servlet程序
在創建Servlet之前需要提前配置好環境:1、安裝好JDK;2、開發工具Eclipse或IDEA(推薦);3、安裝Tomcat。這三個條件是必須的,具體怎么配置網上教程很多,這里不多BB。
創建Servlet程序的流程如下:
- 編寫一個Java類,然后繼承HttpServlet(或者繼承GenericServlet,又或者直接實現Servlet接口)。
/**
* @author tanghaorong
* @date 2020-04-20
* @desc 創建一個Servlet程序
*/
public class MyServlet extends HttpServlet {
}
注意:我們一般都是繼承HTTPServlet,因為HttpServlet是指能夠處理HTTP協議請求的Servlet,它在原有Servlet接口上添加了一些與HTTP協議處理方法,它比Servlet接口的功能更為強大。因此開發人員在編寫Servlet時,通常應繼承這個類,而避免直接去實現Servlet接口和繼承GenericServlet。而且HttpServlet在實現Servlet接口時,覆寫了service方法,該方法體內的代碼會自動判斷用戶的請求方式,如為GET請求,則調用HttpServlet的doGet方法,如為Post請求,則調用doPost方法。因此,開發人員在編寫Servlet時,通常只需要覆寫doGet或doPost方法,而不要去覆寫service方法。
- 重寫HttpServlet類中的doGet和doPost方法(IDEA快捷鍵Ctrl+O)。
/**
* @author tanghaorong
* @date 2020-04-20
* @desc 重寫父類的doGet、doPost方法
*/
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("第一個Servlet程序");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
- 使用web.xml文件或者注解對servlet進行配置。推薦使用注解
前面兩步沒什么可說的,重點是在配置web.xml文件上,這一步決定了我們的請求和響應是哪個Servlet來完成的。上面說到配置Servlet有兩種方式:一種是使用web.xml文件配置,另外一種就是使用注解配置,所以下面我們來詳解介紹這兩種配置方式:
3.1、使用web.xml文件配置
我們打開WEB-INF/web.xml文件,在<web-app>
元素中編寫一個<servlet>
元素用於配置一個Servlet,它包含有兩個主要的子元素:<servlet-name>
和<servlet-class>
,分別用於設置Servlet的名稱和Servlet的完整類名。另外一個<servlet-mapping>
元素用於映射一個已注冊的Servlet的一個對外訪問路徑,它包含有兩個子元素:<servlet-name>
和<url-pattern>
,分別用於指定映射到哪個Servlet和Servlet的對外訪問路徑。配置詳細信息如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--配置一個Servlet-->
<servlet>
<!--Servlet名稱,最好見名知意-->
<servlet-name>HelloServlet</servlet-name>
<!--Servlet的全類名,即包+類-->
<servlet-class>com.thr.MyServlet</servlet-class>
</servlet>
<!--配置Servlet的映射-->
<servlet-mapping>
<!--映射到哪個Servlet,注意一點要與上面有的名稱一樣-->
<servlet-name>HelloServlet</servlet-name>
<!-- Servlet的對外訪問路徑 -->
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
</web-app>
完成上面的web.xml配置后,當服務器運行之后,Servlet程序就可以被外界訪問了,打開瀏覽器訪問如下地址:http://localhost:8080/HelloServlet。
注:如果訪問不了則在訪問地址加上項目名:http://localhost:8080/{項目名稱}/HelloServlet
然后查看可知打印數據:
補充:同一個Servlet可以被映射到多個URL上,即多個<servlet-mapping>
元素的<servlet-name>
子元素的設置值可以是同一個Servlet的名稱。 例如:
<!--配置一個Servlet-->
<servlet>
<!--Servlet名稱,最好見名知意-->
<servlet-name>HelloServlet</servlet-name>
<!--Servlet的全類名,即包+類-->
<servlet-class>com.thr.MyServlet</servlet-class>
</servlet>
<!--配置Servlet的映射-->
<servlet-mapping>
<!--映射到哪個Servlet,注意一點要與上面有的名稱一樣-->
<servlet-name>HelloServlet</servlet-name>
<!-- Servlet的對外訪問路徑 -->
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet/HelloServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet/HelloServlet/HelloServlet</url-pattern>
</servlet-mapping>
通過上面的配置,當我們想訪問名稱是MyServlet的Servlet,可以使用如下的幾個地址去訪問,但結果都是訪問的同一個Servlet:
- http://localhost:8080/HelloServlet
- http://localhost:8080/HelloServlet/HelloServlet
- http://localhost:8080/HelloServlet/HelloServlet/HelloServlet
3.2、使用注解配置(推薦)
注:用了注解,web.xml中就不能再配置該Servlet了
我們都知道使用web.xml文件來配置是很頭痛的事情,隨着系統的開發,配置文件肯定會越來越多,里面的文件也會看的眼花繚亂。所以Servlet3.0之后提供了注解(annotation),使得不再需要在web.xml文件中進行Servlet的配置,而是使用注解@WebServlet代替了web.xml,從而簡化開發流程。
下面是注解@WebServlet
源碼中的屬性列表:
屬性名 | 類型 | 描述 |
---|---|---|
name | String | 指定Servlet 的 name 屬性,等價於<servlet-name>。如果沒有顯式指定,則該 Servlet 的取值即為類的全限定名。 |
value | String[] | 該屬性等價於下面urlPatterns屬性。這兩個屬性不能同時使用。 |
urlPatterns | String[] | 指定一組Servlet的URL匹配模式,等價於<url-pattern>標簽。 |
loadOnStartup | int | 指定Servlet的加載順序,等價於<load-on-startup>標簽。 |
initParams | WebInitParam[] | 指定一組Servlet初始化參數,等價於<init-param>標簽 |
asyncSupported | boolean | 聲明Servlet是否支持異步操作,等價於<async-supported>標簽。 |
smallIcon | String | 此Servlet的小圖標。 |
largeIcon | String | 此Servlet的大圖標。 |
description | String | 該Servlet的描述信息,等價於<description>標簽。 |
displayName | String | 該Servlet的顯示名,通常配合工具使用,等價於<display-name>標簽。 |
使用注解配置Servlet的示例如下:
package com.thr;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author tanghaorong
* @date 2020-04-20
* @desc 使用注解@WebServlet配置Servlet
*/
//name = "MyServlet":servlet名稱,相當於web.xml中的<servlet-name>
//urlPatterns = "/HelloServlet":servlet的訪問路徑,相當於<url-pattern>
@WebServlet(name = "MyServlet",value = "/HelloServlet")
public class MyServlet extends HttpServlet {
public MyServlet() {
super();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
System.out.println("get 請求執行");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
System.out.println("post 請求執行");
}
}
上面實現的效果和web.xml中是一模一樣的,是不是這樣太爽了,所以一般推薦使用注解進行開發,現在無論任何系統開發都基本上摒棄了XML開發,因為開發效率不高,而且排錯也很麻煩。
使用 * 通配符模糊匹配映射Servlet程序,在前面的所有例子中我們映射的URL都是精確匹配,而在Servlet映射到的URL中也是可以使用 * 通配符進行模糊匹配的。
但是只能有兩種固定的格式:一種格式是"*.擴展名"(例如:*.do *.action),另一種格式是以正斜杠(/)開頭並以"/*"結尾。
它們的匹配規則如下:
- /*:匹配任何路徑映射到servlet。
- /abc/*:匹配/abc/下的任意路徑映射到servlet。
- /abc/def:只匹配/abc/def路徑下的servlet。
- *.do:匹配 任意名稱.do 的路徑映射到servlet。
其中例如:/abc/*.do、/*.do、abc*.do 這些都是非法的,啟動時候會報錯,我親自去試了一下,反正 * . 后綴名 這種格式前面是不能加正斜杠(/)的。
還有要注意的是,可能會出現這樣的情況,例如:我請求的URL為:/abc/edf,而這個路徑有兩個Servlet匹配(/* 和 /abc/*),那么它會選擇哪一個呢?
答:會選擇/abc/edf 的Servlet,因為匹配的原則是"誰長得更像就找誰"。
舉一個完整的Servlet栗子:
①、修改index.jsp頁面。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登錄</title>
</head>
<body>
<h1 align="center" style="color: red;">歡迎您登錄系統</h1><hr/>
<div align="center">
<form method="post" action="/login">
<table>
<tr>
<td>Username:</td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password"/></td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" value="登錄"/>
<input type="reset" value="重置"/>
</td>
</tr>
</table>
</form>
</div>
</body>
</html>
②、創建名為LoginServlet的Servlet類。並用@WebServlet注釋。
package com.thr;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author Administrator
* @date 2020-04-20
* @desc Servlet登錄的例子
*/
@WebServlet(name = "loginServlet",value = "/login")
public class LoginServlet extends HttpServlet {
//因為設置了為Post請求,使用doGet方法就不寫了
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 設置request的編碼
request.setCharacterEncoding("UTF-8");
// 獲取信息
String name = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(name+":"+password);
// 設置response的編碼
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
// 獲取PrintWriter對象
PrintWriter out = response.getWriter();
// 輸出信息
out.println("<HTML>");
out.println("<HEAD><TITLE>登錄信息</TITLE></HEAD>");
out.println("<BODY>");
out.println("姓名:" + name + "<br>");
out.println("密碼:" + password + "<br>");
out.println("</BODY>");
out.println("</HTML>");
// 釋放PrintWriter對象
out.flush();
out.close();
}
}
執行結果:
- 登錄頁面:
- 提交登錄結果信息:
4、Servlet生命周期
Servlet接口中定義了五個方法,我們看一看Servlet接口中方法:
package javax.servlet;
import java.io.IOException;
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
其中有三個為生命周期方法:init(),service(),destory():
- init()方法用於初始化該Servlet。當Servlet第一次被加載時,Servlet引擎調用這個Servlet的init()方法,而且只調用一次。
- service()方法用於處理請求。這是Servlet最重要的方法,是真正處理請求的地方。對於每個請求,Servlet引擎都會調用Servlet的service方法,並把Servlet請求對象和Servlet響應對象最為參數傳遞給它,並且判斷Servlet調用的是doGet方法還是doPost方法。
- destory()方法用於銷毀該Servlet。這是相對於init的可選方法,當Servlet即將被卸載時由Servlet引擎來調用,這個方法用來清除並釋放在init方法中所分配的資源。
此外,還有兩個非生命周期方法。
- getServletInfo()方法用於返回Servlet的一段描述,可以返回一段字符串。
- getServletConfig()方法用於返回由Servlet容器傳給init()方法的ServletConfig對象。
下面來編寫一個簡單的Servlet來驗證一下它的生命周期:
package com.thr;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
/**
* @author Administrator
* @date 2020-04-22
* @desc Servlet生命周期
*/
@WebServlet(value = "/HelloServlet1")
public class MyServlet1 implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("Servlet完成初始化--init()");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Servlet正在執行操作--service()");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("Servlet已經銷毀--destroy()");
}
}
服務器運行后我們在瀏覽器訪問:http://localhost:8080/HelloServlet1,控制台輸出了如下信息:
然后,我們在瀏覽器中刷新3遍:
接下來,我們關閉Servlet容器:
以上就是一個Servlet的整個生命周期了。可以發現,在Servlet的整個生命周期內,Servlet的init()方法只被調用一次。也就是說當客戶端多次Servlet請求時,服務器只會創建一個Servlet實例對象,而且Servlet實例對象一旦創建,它就會駐留在內存中,為后續的其它請求服務,直至web容器退出,Servlet實例對象才會銷毀。而對一個Servlet的每次訪問請求都導致Servlet引擎調用一次servlet的service方法。對於每次訪問請求,Servlet引擎都會創建一個新的HttpServletRequest請求對象和一個新的HttpServletResponse響應對象,然后將這兩個對象作為參數傳遞給它調用的Servlet的service()方法,service方法再根據請求方式分別調用doXXX方法。
上面說當客戶端在第一次訪問Servlet的時候會才創建Servlet實例對象,那如果這個Servlet程序要處理的信息很多,那就會造成第一次訪問的Servlet加載時間較長。所以為了解決這樣的問題Servlet提供了自動加載機制,就是在啟動服務器的時候就將Servlet加載起來,它的操作很簡單。我們可以在web.xml中配置也可以在注解中配置。
- 在web.xml中進行配置:
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.thr.MyServlet</servlet-class>
<!-- 讓servlet對象自動加載 -->
<load-on-startup>1</load-on-startup>
</servlet>
注意:<load-on-startup></load-on-startup>
中的整數值越大,創建優先級越低!
- 在注解中進行配置:
@WebServlet(name = "MyServlet",value = "/HelloServlet",loadOnStartup = 1)
public class MyServlet extends HttpServlet {
通過上面的實例,可以看到Servlet在創建時只會執行一次init()方法,后面每次點擊都只調用 service() 方法。那么Servlet的一次執行過程是什么樣的呢?
上面這幅圖可以這樣理解:
1、客戶端向 Web 服務器發送請求,服務器查詢 web.xml 文件配置。根據請求信息找到對應的 Servlet。
2、Servlet 引擎檢查是否已經裝載並創建了該 Servlet 的實例對象,如果有,則直接執行第4步,否則執行第3步,
3、Web 服務器加載 Servlet,並調用 Servlet 構造器(只會調用一次),創建 Servlet 的實例對象。並調用 init() 方法,完成 Servlet 實例對象的初始化(只會調用一次)。
4、Web 服務器把接收到的 http 請求封裝成 ServletRequest 對象,並創建一個 響應消息的 ServletResponse 對象,作為 service() 方法的參數傳入。(每一次訪問都會調用一次該方法)
5、執行 service()方法,並將處理信息封裝到 ServletResponse 對象中返回
6、瀏覽器拆除 ServletResponse 對象,形成 http 響應格式,返回給客戶端。
7、Web 應用程序停止或者重新啟動之前,Servlet 引擎將卸載 Servlet實例,並在卸載之前調用 destory() 方法
5、ServletConfig對象
ServletConfig表示一個Servlet的配置信息,每個Servlet對象都有一個封裝Servlet配置的ServletConfig對象。因此可通過此對象獲取servlet相關信息,也可以用來讀取web.xml中用<init-param>配置的Servlet初始化參數。
當我們的Servlet配置了初始化參數后,啟動服務器,web容器在創建Servlet實例對象后,接着ServletConfig對象就會被創建,而且會自動將初始化參數封裝到ServletConfig對象中,並在調用Servlet的init()方法時,將ServletConfig對象傳遞給創建好的Servlet。進而,我們通過ServletConfig對象就可以得到當前Servlet的初始化參數信息。
ServletConfig中有四個方法如下:
String getServletName()
:獲取當前Servlet的名稱,即:<servlet-name>中的內容。ServletContext getServletContext()
:獲取當前當前Web的應用上下文,即整個Servlet。String getInitParameter(String var1)
:通過名稱獲取指定初始化參數的值。Enumeration<String> getInitParameterNames()
:獲取所有初始化參數的名稱。
使用ServletConfig對象獲取初始化數據的簡單舉例:
①、在Servlet的配置文件web.xml中,可以使用一個或多個<init-param>標簽為servlet配置一些初始化參數。
<!--配置一個Servlet-->
<servlet>
<servlet-name>config</servlet-name>
<servlet-class>com.thr.ServletConfigDemo</servlet-class>
<!-- 初始參數:這些參數會在加載web應用的時候,封裝到ServletConfig對象中 -->
<init-param>
<param-name>name</param-name>
<param-value>tanghaorong</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
<!-- 讓servlet對象自動加載,注意:init-param要在自動加載之前 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--配置Servlet的映射-->
<servlet-mapping>
<servlet-name>config</servlet-name>
<!-- Servlet的對外訪問路徑 -->
<url-pattern>/config</url-pattern>
</servlet-mapping>
②、獲取web.xml中<init-param>標簽初始化參數,代碼如下:
package com.thr;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
/**
* @author tanghaorong
* @date 2020-04-22
* @desc 使用ServletConfig對象獲取初始化參數
*/
public class ServletConfigDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//第一種方式——getInitParameter
ServletConfig config = this.getServletConfig();
String name = config.getInitParameter("name");
String password = config.getInitParameter("password");
System.out.println(name+":"+password);
// 設置response的編碼
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
// 獲取PrintWriter對象
PrintWriter out = response.getWriter();
// 輸出信息
out.println("<HEAD><TITLE>初始化信息</TITLE></HEAD>");
out.println("姓名:" + name + "<br>");
out.println("密碼:" + password + "<br><hr>");
//第二種方式——getInitParameterNames
Enumeration<String> initParameterNames = this.getServletConfig().getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
String names = initParameterNames.nextElement();
String value = config.getInitParameter(names);
System.out.println(value);
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
response.getWriter().print(names + "=" + value + "<br/>");
}
// 釋放PrintWriter對象
out.flush();
out.close();
}
}
③、啟動 Tomcat 服務器,在瀏覽器的地址欄中輸入地址: http://localhost:8080/config 訪問Servlet,結果如圖所示。
從上圖中可以看出,web.xml 文件中配置的信息全部被讀取了出來。
6、ServletContext對象
ServletContext對象表示的當前整個上下文(應用程序),也就是整個Web應用。這個對象在Tomcat啟動的時候,會創建一個唯一的ServletContext對象代表當前的整個Web應用,該對象封裝了當前Web應用的所有信息。我們一般用來配置或者獲取整個應用的初始化配置信息、讀取資源文件、多個Servlet之間的通信等。所以ServletContext是相對於整個的應用,而ServletConfig是單個的應用。
下面對ServletContext對象獲取不同的資源分別進行講解。
6.1、獲取Web應用程序的初始化參數
我們在web.xml文件中,不僅可以配置Servlet的映射信息和初始化信息,也可以配置整個Web應用的初始化信息。Web應用初始化參數的配置方式具體如下所示:
<!--配置整個Web應用的初始化信息格式-->
<context-param>
<param-name>AAA</param-name>
<param-value>BBB</param-value>
</context-param>
<context-param>
<param-name>CCC</param-name>
<param-value>DDD</param-value>
</context-param>
注意:在上面的配置文件中,<context-param>
元素位於根元素 <web-app>
中,它的子元素 <param-name>
和 <param-value>
分別用於指定參數的名字和參數值。要想獲取這些參數名和參數值的信息,可以使用 ServletContext對象中定義的 getInitParameterNames()
和 getInitParameter(String name)
方法分別獲取。
下面通過案例演示如何使用 ServletContext對象獲取Web應用程序的初始化參數。
①、在項目的web.xml文件中配置初始化參數信息和Servlet信息,其代碼如下所示:
<!--配置整個Web應用的初始化信息-->
<context-param>
<param-name>name</param-name>
<param-value>tanghaorong</param-value>
</context-param>
<context-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</context-param>
<!--配置一個Servlet-->
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>com.thr.ServletContextDemo</servlet-class>
<!-- 讓servlet對象自動加載,注意:init-param要在自動加載之前 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--配置Servlet的映射-->
<servlet-mapping>
<servlet-name>context</servlet-name>
<!-- Servlet的對外訪問路徑 -->
<url-pattern>/context</url-pattern>
</servlet-mapping>
②、使用ServletContext對象獲取web.xml中配置的信息,代碼如下所示。
package com.thr;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
/**
* @author tanghaorong
* @date 2020-04-23
* @desc 使用ServletContext對象獲取整個Web應用的配置信息
*/
public class ServletContextDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//獲取ServletContext對象
ServletContext context = this.getServletContext();
Enumeration<String> names = context.getInitParameterNames();
while (names.hasMoreElements()) {
//獲取配置信息
String name = names.nextElement();
String value = context.getInitParameter(name);
//打印
System.out.println(name+":"+value);
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
response.getWriter().println("<head><title>獲取整個Web應用初始化信息</title></head>");
response.getWriter().println(name + "=" + value + "<br/>");
//釋放流資源
response.getWriter().close();
}
}
}
上述代碼中,當通過 this.getServletContext() 方法獲取到 ServletContext 對象后,首先調用getInitParameterNames()
方法,獲取到包含所有初始化參數名的 Enumeration
對象,然后遍歷 Enumeration 對象,根據獲取到的參數名,通過 getInitParamter(String name)
方法得到對應的參數值。
③、啟動 Tomcat 服務器,在瀏覽器的地址欄中輸入地址 http://localhost:8080/context 訪問,瀏覽器的顯示結果如圖所示。
從圖中可以看出,web.xml 文件中配置的信息被讀取了出來。
6.2、讀取 Web 應用下的資源文件
我們在實際開發過程中,不僅需要從web.xml文件中配置信息,有時候也會會需要讀取 Web 應用中的一些資源文件,如配置文件和日志文件等。為此,在 ServletContext 接口中定義了一些讀取 Web 資源的方法,這些方法是依靠 Servlet 容器實現的。Servlet 容器根據資源文件相對於 Web 應用的路徑,返回關聯資源文件的 I/O 流或資源文件在系統的絕對路徑等。
ServletContext對象中用於獲取資源路徑的相關方法。
Set getResourcePaths(String path)
:返回一個 Set 集合,集合中包含資源目錄中子目錄和文件的路徑名 稱。參數 path 必須以正斜線(/)開始,指定匹配資源的部分路徑String getRealPath(String path)
:返回資源文件在服務器文件系統上的真實路徑(文件的絕對路徑)。參數 path 代表資源文件的虛擬路徑,它應該以正斜線(/)開始,/ 表示當前 Web 應用的根目錄,如果 Servlet 容器不能將虛擬路徑轉換為文 件系統的真實路徑,則返回 nullURL getResource(String path)
:返回映射到某個資源文件的 URL 對象。參數 path 必須以正斜線(/)開始,/ 表示當前 Web 應用的根目錄InputStream getResourceAsStream(String path)
:返回映射到某個資源文件的 InputStream 輸入流對象。參數 path 的傳遞規則和 getResource() 方法完全一致
熟悉了下面的方法后,在通過使用 ServletContext 對象讀取資源文件舉例:
在項目的src目錄下創建一個名稱為userInfo.properties的文件,文件中的配置信息如下:
name=tanghaorong
password=654321
②、使用ServletContext對象獲取userInfo.properties中的資源文件配置信息,代碼如下所示:
package com.thr;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author tanghaorong
* @date 2020-04-23
* @desc 使用ServletContext對象獲取整個Web應用的配置信息
*/
public class ServletContextDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//第一種方式:使用ServletContext對象讀取資源文件
ServletContext context = this.getServletContext();
//獲取userInfo.properties文件
InputStream is = context.getResourceAsStream("/WEB-INF/classes/userInfo.properties");
//創建Properties並載入數據
Properties prop = new Properties();
prop.load(is);
//打印在網頁上
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
response.getWriter().println("<head><title>使用ServletContext對象讀取資源文件</title></head>");
response.getWriter().println("姓名"+":"+prop.getProperty("name")+ "<br/>");
response.getWriter().println("密碼"+":"+prop.getProperty("password")+ "<br/>");
//第二種方式:使用類裝載器讀取資源文件
ClassLoader loader = ServletContextDemo1.class.getClassLoader();
InputStream resourceAsStream = loader.getResourceAsStream("userInfo.properties");
Properties properties = new Properties();
properties.load(resourceAsStream);
String name = properties.getProperty("name");
String password = properties.getProperty("password");
//在控制台打印
System.out.println("姓名:"+name+",密碼:"+password);
}
}
③、啟動 Tomcat 服務器,在瀏覽器的地址欄中輸入地址 http://localhost:8080/context1 訪問,瀏覽器的顯示結果如圖所示。
從圖中可以看出,userInfo.properties 資源文件中的內容已經被讀取了出來。
6.3、多個Servlet之間的通信
ServletContext代表了整個Web應用,並且數據是共享的,而一個Web應用可以有多個Servlet實例,也就意味着多個Servlet是可以實現通信的。下面使用ServletContext實現多個Servlet之間的通信,相關的方法如下。
- Object getAttribute(String var1):獲取域對象中共享的數據
- void setAttribute(String var1, Object var2):向域對象中共享數據
- void removeAttribute(String var1):刪除域對象中共享的數據
我們創建兩個類ServletContextDemo2和ServletContextDemo3通過ServletContext對象實現通信。
ServletContextDemo2代碼如下:
package com.thr;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author tanghaorong
* @date 2020-04-23
* @desc 使用ServletContext對象實現多個Servlet通信,放入
*/
public class ServletContextDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name="tanghaorong";
String password="123456";
ServletContext context = this.getServletContext();
//向域對象中共享數據
context.setAttribute("name",name);
context.setAttribute("password",password);
}
}
ServletContextDemo3代碼如下:
package com.thr;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author tanghaorong
* @date 2020-04-23
* @desc 使用ServletContext對象實現多個Servlet通信,獲取
*/
public class ServletContextDemo3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext context = this.getServletContext();
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
//獲取域對象中共享的數據
response.getWriter().println("姓名"+":"+context.getAttribute("name")+ "<br/>");
response.getWriter().println("密碼"+":"+context.getAttribute("password")+ "<br/>");
}
}
先運行ServletContextDemo2,並且訪問該Servlet,將數據name和password數據存儲到ServletContext對象中。然后運行訪問ServletContextDemo3,就可以從ServletContext對象中取出數據了,這樣就實現了多個Servlet的通信,運行結果如下圖所示:
7、Servlet,GenericServlet和HTTPServlet
Servlet,GenericServlet和HTTPServlet三者之間的關系如下圖。
①、Servlet接口是Servlet程序的根接口,里面定義了5個方法。
public interface Servlet {
//初始化
void init(ServletConfig var1) throws ServletException;
//獲取ServletConfig對象
ServletConfig getServletConfig();
//用於處理請求和響應
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
//獲取Servlet的相關信息
String getServletInfo();
//銷毀
void destroy();
}
②、GenericServlet實現了Servlet接口和ServletConfig接口,它是一個抽象類,將Servlet接口中的init()、destroy()、getServletConfig()、getServletInfo()進行了重寫,並且將service()設置為抽象方法,因此若創建的Servlet繼承了GenericServlet,則只需要重寫service()即可,但是在實際中一般不會使用它,因為它有一個子類HttpServlet,功能更加強大。
GenericServlet抽象類相比於直接實現Servlet接口,有以下幾個好處:
- 為Servlet接口中的所有方法提供了默認的實現,則程序員需要什么就直接改什么,不再需要把所有的方法都自己實現了。
- 提供了一系列的方法,包括ServletConfig對象中的方法。
- 將init( )方法中的ServletConfig參數賦給了一個內部的ServletConfig引用從而來保存ServletConfig對象,不需要程序員自己去維護ServletConfig了。
package javax.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.ResourceBundle;
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameter(name);
}
}
public Enumeration<String> getInitParameterNames() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameterNames();
}
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletContext();
}
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletName();
}
}
}
③、HttpServlet繼承了GenericServlet,還記得在GenericServlet中的service()方法,將其定義為了一個抽象的方法,所以在HttpServlet中肯定會進行重寫,然后來具體的看一看HttpServlet抽象類是如何實現自己的service方法吧。
首先來看GenericServlet抽象類中是如何定義service方法的:
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
HttpServlet又是怎么重寫這個service方法的:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
this.service(request, response);
} else {
throw new ServletException("non-HTTP request or response");
}
}
可以發現HttpServlet在重寫GenericServlet中的service()時,將ServletRequest強制轉成了HttpServletRequest,ServletResponse強制轉成了HttpServletResponse,之所以將它兩轉為Http類型的因為它們的功能更加強大。然后又調用了一個擁有HttpServletRequest和HttpServletResponse為參數的service(),再來看看這個方法是如何實現的:
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取請求的類型;GET|POST|PUT|DELETE...
String method = req.getMethod();
long lastModified;
//是否為GET請求
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
//是否為HEAD請求
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
//是否為POST請求
} 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);
}
}
在這個方法中,先獲取了當前請求的請求方式,通過判斷請求的方式調用不同的方法,分別調用了doXXX()方法,例如:GET請求調用了doGet(),POST請求調用了doPost(),由於瀏覽器只能發送GET和POST請求,因此若Servlet繼承了HttpServlet,只需要重寫其中的doGet()和doPost()即可。
8、HttpServletRequest接口
HttpServletRequest表示Http環境中的Servlet請求。它是一個接口,繼承自javax.servlet.ServletRequest接口,它封裝了請求報文,因此可以通過此對象獲取請求報文中的數據以及請求轉發。HttpServletRequest在ServletRequest接口的基礎上添加下面這幾個方法:
String getContextPath()
:返回請求上下文的請求URI部分Cookie[] getCookies()
:返回一個cookie對象數組String getHeader(String var1)
:返回指定HTTP標題的值String getMethod()
:返回生成這個請求HTTP的方法名稱String getQueryString()
:返回請求URL中的查詢字符串HttpSession getSession()
:返回與這個請求相關的會話對象
8.1、HttpServletRequest內封裝的請求
8.2、通過request獲得請求行
獲得請求行的相關方法如下:
- String getMethod():獲取請求的方式(get/post)
- String getRequestURI():獲取請求的URI地址
- StringBuffer getRequestURL():獲取請求的URL地址
- String getContextPath():獲取web應用的名稱
- String getQueryString():獲取get提交url地址后的參數字符串
request獲得請求行示例代碼:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getMethod());
System.out.println(req.getRequestURI());
System.out.println(req.getRequestURL());
System.out.println(req.getContextPath());
System.out.println(req.getQueryString());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
獲取結果:
8.3、通過request獲得請求頭
獲得請求頭的相關方法如下:
- String getHeader(String name):根據請求頭的key獲取對應的value
- Enumeration getHeaderNames():獲取請求頭中所有的key
- Enumeration getHeaders(String name):根據請求頭的key獲取對應批量的value
- int getIntHeader(String name):根據請求頭的key獲取對應的value,它返回的是Int類型的值
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//根據請求頭的key獲取對應的value
System.out.println(req.getHeader("Host"));
//獲取請求頭中所有的key
Enumeration<String> names = req.getHeaderNames();
while (names.hasMoreElements()){
System.out.println(names.nextElement());
}
//根據請求頭的key獲取對應批量的value
Enumeration<String> accept = req.getHeaders("Accept");
while (accept.hasMoreElements()){
System.out.println(accept.nextElement());
}
//根據請求頭的key獲取對應的value,它返回的是Int類型的值
System.out.println(req.getIntHeader("Content-length"));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
補充:HTTP請求中的常用消息頭
- accept:瀏覽器通過這個頭告訴服務器,它所支持的數據類型
- Accept-Charset: 瀏覽器通過這個頭告訴服務器,它支持哪種字符集
- Accept-Encoding:瀏覽器通過這個頭告訴服務器,支持的壓縮格式
- Accept-Language:瀏覽器通過這個頭告訴服務器,它的語言環境
- Host:瀏覽器通過這個頭告訴服務器,想訪問哪台主機
- If-Modified-Since: 瀏覽器通過這個頭告訴服務器,緩存數據的時間
- Referer:瀏覽器通過這個頭告訴服務器,客戶機是哪個頁面來的 防盜鏈
- Connection:瀏覽器通過這個頭告訴服務器,請求完后是斷開鏈接還是何持鏈接
8.4、通過request獲得請求體
上面請求體中的內容是通過post提交的請求參數,格式是:
username=tanghaorong&password=123456
#對應的key-value格式為
username:tanghaorong
password:123456
以上面參數為例,通過一下方法獲得請求參數:
- String getParameter(String name):根據請求體中的key獲取value
- String[] getParameterValues(String name):根據請求體中的key獲取批量value
- Enumeration getParameterNames():獲取所有請求體中的key
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//根據請求體中的key獲取value
System.out.println(req.getParameter("username"));
System.out.println(req.getParameter("password"));
//根據請求體中的key獲取批量value
String[] usernames = req.getParameterValues("username");
for (String username : usernames) {
System.out.println(username);
}
//獲取所有請求體中的key
Enumeration<String> names = req.getParameterNames();
while (names.hasMoreElements()){
System.out.println(names.nextElement());
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
8.5、Request亂碼問題的解決方法
在service中使用的編碼解碼方式默認為:ISO-8859-1編碼,但此編碼並不支持中文,因此會出現亂碼問題,所以我們需要手動修改編碼方式為UTF-8編碼,才能解決中文亂碼問題,下面是發生亂碼的具體細節:
亂碼問題解決:
- 解決get提交的方式的亂碼:
- 在tomcat的配置文件server.xml,在71行左右,改端口號的標簽中,加入屬性URIEncoding="UTF-8"
- 或者使用String parameter = new String(parameter.getbytes("iso8859-1"),"utf-8");
- 解決post提交方式的亂碼:
- 在獲取請求參數之前設置
request.setCharacterEncoding("UTF-8");
- 在獲取請求參數之前設置
9、HttpServletResponse
HttpServletResponse也是一個接口,它繼承自ServletResponse接口,專門用來封裝HTTP響應報文,由於HTTP請求消息分為狀態行,響應消息頭,響應消息體三部分,因此,在HttpServletResponse接口中定義了向客戶端發送響應狀態碼,響應消息頭,響應消息體的方法。
9.1、HttpServletResponse內封裝的響應
9.2、Response的亂碼問題解決
//設置響應報文的編碼格式
response.setCharacterEncoding("UTF-8");
//設置響應報文中響應體的內容格式以及瀏覽器的解碼方式
response.setContentType("text/html;charset=UTF-8");
//向瀏覽器響應數據,就是將數據以響應體的方式響應到瀏覽器
PrintWriter writer = response.getWriter();
writer.print("helloworld<br>");
writer.write("你好");
10、Servlet的轉發和重定向
轉發:客戶端向服務器端發送請求,服務器將請求轉發到服務器內部,再響應給客戶端。
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//轉發
request.getRequestDispatcher("success.jsp").forward(request,response);
}
訪問后的地址:
重定向:客戶端向服務器端發送請求,服務器告訴客戶端你去重定向(狀態碼302,響應頭location=客戶端絕路路徑),客戶端繼續向服務器發送請求(請求地址已經成重定向的地址),服務器端給客戶端響應。
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//重定向的兩種方式
//方式一
//resp.sendRedirect(req.getContextPath()+"/success.html");
response.sendRedirect("https://www.baidu.com/");
//方式二
//response.setStatus(302);
//response.setHeader("location","https://www.baidu.com/");
}
訪問后的地址:
轉發和重定向的區別:
- 請求次數:轉發只發出了一次請求,而重定向發出了兩次請求。
- 地址欄變化:轉發不會改變地址欄中的URL,而重定向則會改變URL。
- 項目名稱:轉發不用寫項目名稱(默認:http://localhost:8080/項目名稱/),重定向需要編寫項目名稱(默認:http://localhost:8080/)。
- 跳轉范圍:轉發只能訪問到當前web應用中的內容,而重定向則可以訪問到任意web應用中的內容 。
- request對象作用范圍:轉發后,在轉發后的頁面中仍然可以使用原來的request對象,而重定向,原來的request對象則失去作用。
11、補充:web應用中的路徑問題(重要)
路徑分為相對路徑和絕對路徑:
- 相對路徑:目標資源相當於當前位置的路徑
- 絕對路徑
- Static web:絕對路徑指資源在磁盤上的完整路徑
- Web Application:絕對路徑指資源在服務器中的路徑,以/開頭的路徑都是絕對路徑
11.1、相對路徑的缺點
1、對於相同的資源,在不同的頁面中訪問路徑不同,即對於同一個資源沒有統一的訪問路徑
2、若當前位置或目標資源的位置發生了變化,則相對路徑有可能失效
3、由於轉發瀏覽器發送一次請求,且在服務器的內部跳轉到轉發的地址,因此地址欄不變,即訪問servlet的地址,因此造成了地址欄中的地址和頁面中顯示的內容不匹配,即當前位置發生了變化,就影響了頁面中所有的相對路徑
總結:相對路徑不靠譜,推薦使用絕對路徑
11.2、絕對路徑
在web應用中,以 / 開頭的路徑都是絕對路徑。絕對路徑又分為由瀏覽器解析的絕對路徑
和由服務器解析的絕對路徑
:
- 由瀏覽器解析的絕對路徑,/ 表示localhost:8080下訪問
- 由瀏覽器解析的絕對路徑的情況有:html標簽中所設置的絕對路徑(超鏈接、form標簽中的action、img、link、script)、JavaScript中的location對象所設置的絕對路徑、重定向中設置的絕對路徑
<a href="/HelloServlet">HelloServlet</a> <--瀏覽器會將其解析的地址為:localhost:8080/HelloServlet,如果你有上下文路徑的話則會報404錯誤!(上下文路徑就是你的項目名稱)-->
- 由服務器解析的絕對路徑,/ 表示localhost:8080/上下文路徑 下訪問
- 由服務器解析的絕對路徑的情況有:web.xml中url-pattern設置的絕對路徑、轉發中設置的絕對路徑、jsp中jsp指令和jsp動作標簽所設置的絕對路徑
11.3、瀏覽器解析的絕對路徑的解決方案
頁面中所設置的絕對路徑:手動添加上下文路徑,例如:
<a href="${pageContext.request.contextPath}/success.jsp">success.jsp</a>
重定向中所設置的絕對路徑:在絕對路徑前通過request.getContextPath()拼接上下文路徑,例如:
resp.sendRedirect(req.getContextPath() + "/success.jsp");
參考資料: