Servlet
介於Servlet是Java Web開發的基礎,因此好好看了一下Servlet3.0.1的源碼,於是有了這篇記錄。
Servlet架構圖
Servlet和JSP是衆多java EE定義的技術當中的兩種,其他還有JMS,EJB等等,運行JEE程序需要一個JEE容器,如GlassFish、JBOSS、WebLogic等,Servlet\JSP也可以部署在JEE容器中,不過用Servlet/JSP容器已經足夠了,而且比JEE容器更加輕量化,Tomcat和Jetty不屬於JEE容器,不能運行EJB或JMS。
Servlet API概述
首先,下載Servlet-api源碼,可以使用IDE,maven等方式,本文使用maven下載,命令如下(或者自行搜索下載源碼):
maven dependency:sources
servlet API中有4個Java包,包括:
-
javax.servlet 定義Servlet和Servlet容器之間的七月類和接口。
-
javax.servlet.http 定義HTTP Servlet與Serlvet容器之間的契約和接口
-
javax.servlet.annotation 包含對Servlet、Filter和Listener進行標註的註解。還爲標註元件指定元數據。
-
javax.servlet.descriptor 包含爲Web應用程序的配置星系提供編程式訪問的類型。
javax.servlet包
不完整截圖:
javax.servlet方法一覽
Servlet包中的主要成員:
Servlet主要成員
Servlet接口是核心接口,是所有Serlvet都必須直接或間接實現的一個接口,Servlet接口定義了Servlet與Servlet容器之間的約定,總的來說就是Servlet容器會把Servlet類加載到內存中,並在Servlet實例中調用特定方法,在一個應用程序中,每個Servlet類型只能有一個實例。當用戶的請求引發service方法,並給這個方法傳入一個ServletRequest實例和一個ServletResponse實例。ServletRequest封裝當前的HTTP請求,讓開發者不必去解析和操作原始的HTTP數據,同理,ServletResponse表示當前用戶的HTTP響應,它的作用是使得將響應回傳給用戶更容易。Servlet容器還爲每個應用程序創建一個ServletContext實例。這個對象封裝context(應用程序)的環境細節,而每個context只有一個ServletContext。每個Servlet實例還有一個封裝Servlet配置信息的ServletConfig。
Servlet
下面先看看Servlet接口,(如果別人問你什麼是Servlet,你可以告訴他,就是一個java接口,分分鐘定義出來給你看,不過人們常說的Servlet應該是指任何實現了Servlet接口的類)看源碼發現有234行(3.0.1版),仔細一看也就5個抽象方法,其他的全是註釋,所以兄弟們,看源碼不要虛,這就是傳說中的和Servlet容器之間的約定(有沒有很熟悉的感覺):
Servlet接口
-
init 第一次請求我們編寫的Serlvet時,Servlet容器調用此方法,後續不在調用,可以利用這個方法做一些初始化的工作。在調用這個方法時,Servlet容器會傳遞一個ServletConfig。一般會將這個ServletConfig賦給一個類級變量,以方便其他方法也可以使用這個對象。
-
service 每次用戶請求service時,servlet容器都會調用這個方法,我們對請求的處理就是在這裏完成的。
-
destroy 要銷燬Servlet時,Servlet容器就會調用這個方法,它通常發生在卸載應用程序,或者關閉Servlet容器的時候,這裏一般我們會寫一些資源清理相關的代碼。
Servlet中另外另個費生命週期的方法:getServletInfo和getServletConfig
-
getServletInfo 就是字面意思,返回Servlet的描述
-
getServletConfig 這個方法返回由Servlet容器傳給init方法的ServletConfig,上面說了,一般在init方法中將ServletConfig賦給一個類級變量,免得本方法返回null。
PS:由於Servlet不是線程安全的,一個應用程序中所有的用戶公用一個Servlet實例,因此不建議使用類級別的變量(只使用局部變量最好),除非是隻讀的或者java.utilconcurrent.atomic包中的成員。
Servlet基礎應用程序
現在來寫一個Servlet應用程序,寫起來很簡單,只要創建一個目錄,並將Servlet類放在指定目錄中就可以了,同時,如果要運行這個應用程序,你還需要安裝一個Servlet容器,如Tomcat或者Jetty(安裝方法自行搜索)。
編寫Servlet應用
Servlet應用目錄結構
以上爲Servlet的目錄結構,要編譯servlet,類路徑中還要有servlet API,tomcat容器中已經自帶了servlet-api.jar。另外珍愛生命,還是用IDE來創建吧,博主試過,自己去一步一步創建配置雖說也可以,但是確實會花費不少時間,如果你就是要自己一步一步創建,覺得這樣能學到更多東西,我只能說加油騷年!
應用程序中一般會有JSP、HTML、圖像等其他資源,這些都應該放在應用程序的目錄下面,並且經常放在子目錄下,如上圖,html放在html文件下,jsp放在jsp目錄下。放在應用程序目錄下的任何資源,用戶可以通過資源的URL直接訪問(放在應用程序目錄下當然要可以訪問了),如果希望某個資源可以被Servlet訪問,但不能被用戶訪問,那就應該放在WEB-INF目錄下(是不是找到該目錄的作用了)。
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
//name可選,提供servlet名,關鍵urlPatterns指定匹配當前servlet的模式
@WebServlet(name="/FirstServlet",urlPatterns={ "/myapp" })
public class FirstServlet implements Servlet {
private transient ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
//將config給類變量,擴大使用範圍
this.config=config;
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public String getServletInfo() {
return "My Servlet";
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
String servletName=config.getServletName();
response.setContentType("text/html");
PrintWriter pw=response.getWriter();
pw.write("hello from "+servletName);
}
@Override
public void destroy() {}
}
以上程序部署完成,啓動Servlet容器,就可以通過url在瀏覽器中訪問了:
通過訪問 http://localhost:8080/App01/FirstServlet
效果圖
ServletRequest & ServletResponse
對於每一個HTTP請求,servlet容器會創建一個封裝了HTTP請求的ServletRequest實例傳遞給servlet的service方法,ServletResponse則表示一個Servlet響應,其影藏了將響應發給瀏覽器的複雜性。通過ServletRequest的方法你可以獲取一些請求相關的參數,而ServletResponse則可以將設置一些返回參數信息,並且設置返回內容。返回內容之前一般會調用setContentType方法設置響應的內容類型,如果沒有設置,大多數瀏覽器會默認以html的形式響應,不過爲了避免出問題,我們一般都設置該項。
值得注意的是ServletResponse中定義的getWriter方法,它返回可以將文本傳給客戶端的java.io.PrintWriter。在默認的情況下,PrintWriter對象使用ISO-8859-1編碼,這有可能引起亂碼。
以下爲ServletRequest和ServletResponse的大部分方法:
ServletRequest
ServletResponse
ServletConfig
ServletConfig封裝可以通過@WebServlet或者web.xml傳給一個Servlet的配置信息,以這種方式傳遞的每一條信息都稱做初始化信息,初始化信息就是一個個K-V鍵值對。爲了從一個Servlet內部獲取某個初始參數的值,init方法中調用ServletConfig的getinitParameter方法或getinitParameterNames方法獲取,除此之外,還可以通過getServletContext獲取ServletContext對象。方法簽名:
ServletConfig
通過WebServlet傳遞配置信息示例:
WebServlet初始化參數
ServletContext
ServletContext是代表了Servlet應用程序。每個Web應用程序只有一個context。在分佈式環境中,一個應用程序同時部署到多個容器中,並且每臺Java虛擬機都有一個ServletContext對象。有了ServletContext對象後,就可以共享能通過應用程序的所有資源訪問的信息,促進Web對象的動態註冊,共享的信息通過一個內部Map中的對象保存在ServiceContext中來實現。保存在ServletContext中的對象稱作屬性。操作屬性的方法:
ServletContext
GenericServlet
前面編寫的Servlet應用中通過實現Servlet接口來編寫Servlet,但是我們每次都必須爲Servlet中的所有方法都提供實現,還需要將ServletConfig對象保存到一個類級別的變量中,GenericServlet抽象類就是爲了爲我們省略一些模板代碼,實現了Servlet和ServletConfig,完成了一下幾個工作:
- 將init方法中的ServletConfig賦給一個類級變量,使的可以通過getServletConfig來獲取。
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
同時爲避免覆蓋init方法後在子類中必須調用super.init(servletConfig),GenericServlet還提供了一個不帶參數的init方法,當ServletConfig賦值完成就會被第帶參數的init方法調用。這樣就可以通過覆蓋不帶參數的init方法編寫初始化代碼,而ServletConfig實例依然得以保存(這難道不是適配器模式嗎?)
-
爲Servlet接口中的所有方法提供默認實現。
-
提供方法來包裝ServletConfig中的方法。
用GenericServlet實現Servlet應用
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
@WebServlet(name="SecondServlet",
urlPatterns={"/generic"},
initParams={
@WebInitParam(name="user",value="xiaobai"),
@WebInitParam(name="email",value="[email protected]")
}
)
public class SecondServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
ServletConfig config=getServletConfig();
String user=config.getInitParameter("user");
String email=config.getInitParameter("email");
response.setContentType("text/html");
PrintWriter pw=response.getWriter();
pw.write("User:"+user+"<br>email:"+email);
}
}
在編寫Servlet應用程序時,大多數都要用到HTTP,也就是說可以利用HTTP提供的特性,javax.servlet.http包含了編寫Servlet應用程序的類和接口,其中很多覆蓋了javax.servlet中的類型,我們自己在編寫應用時大多時候也是繼承的HttpServlet,以下爲其中的重要成員:
HttpServelt主要成員
從上圖看,HttpServlet繼承了GenericServlet,HttpServletRequest/Response繼承了覆蓋了ServletRequest/Response,成爲了新的Servlet請求和響應的代表。在HttpServlet中覆蓋了GenericServlet的service方法,並用新的Servlet請求和響應代表作爲參數添加了一個service方法:
//覆蓋GenereicServlet中的service
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request, response);
}
}
//新service方法簽名
protected void service(HttpServletRequest req, HttpServletResponse resp)
原始的service方法將請求和響應進行向下轉換,分別爲HttpServletRequest和HttpServletResponse,並調用新的service方法。看了下2.5版本中的實現,發現沒有加以上代碼是中的instanceof判斷,恩,看來2.5中直接向下轉型確實暴力了點,考慮容器不一定總是把請求當做HTTP請求,這樣做看起來穩妥了些。新的service方法會查尋HTTP請求的方法從而調用do{Method}來處理請求。
總之HttpServlet中有兩項特性是GenericServlet中沒有的:
-
不覆蓋service方法,而是覆蓋doGet、doPost等。
-
用HttpServletRequest\Response 替代ServletRequest\Response
HttpServletRequest,HttpServletResponse由於帶有了HTTP的特性,因此除了ServletRequest,ServletResponse中的方法之外還增加了幾個可以獲取HTTP特性信息的方法。
//獲取context的請求URI部分
java.lang.String getContextPath()
//獲取Cookie對象數組
Cookie [] getCookies()
//返回指定HTTP標頭的值
java.lang.String getHeader(java.lang.String name)
//返回發出這條請求的HTTP方法的名稱
java.lang.String getMethod()
//返回請求URL中的查詢字符串
java.lang.String getQueryString()
//獲取session對象,沒找到就新創建
HttpSession getSession()
//返回與這個請求相關的session對象,如果沒有,並且create參數爲true,創建新的session對象
//響應對象添加cookie
void addCookie(Cookie cookie)
//添加標頭
void addheader(String name,String value)
//重定向
void sendRedirect(String location)
使用web.xml配置Servlet應用
在Servlet3.x中可以使用@WebServlet來部署應用,可以不必在WEB-INF目錄下放一個web.xml配置文件,當然也可以同時使用,前者優先級更高,這是annotation爲我們帶來的好處,介於使用web.xml來配置Servlet也有其有點,就簡單介紹下,使用web.xml配置優點:
-
不用更改代碼,也就意味着不需要重新編譯
-
可包含@WebServlet中沒有的元素,如load-on-startup元素,init方法比較費時間這個就很有幫助了。
<?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_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>SimpleServlet</servlet-name>
<servlet-class>app01.SimpleServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SimpleServlet</servlet-name>
<url-pattern>/simple</url-pattern>
</servlet-mapping>
</web-app>
附
現在再來看下我們我們通常寫的Servlet,看完上文的客官,看到下面Servlet是不是感覺自己看到了更多的東西呢?反正我是看到了。講真,如果想要縷下Servlet的話,真的可以試一試下載Servlet-api的源碼看看,結合本文,或許風味更佳哦!!
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/normal")
public class NormalServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().append("Served at: ").append(request.getContextPath());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
小結
Servlet技術是Java EE技術的重要組成,Servlet容器中運行的所有Servlet,以及容器與Servlet之間的約定,都採用了javax.servlet.Servlet接口的形式。javax.servlet包也提供了實現Servlet接口的GenericServlet抽象類。這是一個比較方便的類,可以通過擴展它來創建Servlet。但是大多數的現代Servlet都在HTTP環境中處理請求,因此提供了javax.servlet.http.HttpServlet來繼承GenericServlet並且加入HTTP特性。
作者:BigfaceMonster
鏈接:https://www.jianshu.com/p/e9f31c783ff1
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。