不少小夥伴對Java Web後臺開發感興趣,可是又苦於入門,一是書上和網絡上的其餘資料大可能是千篇一概,要麼就是偏知識,大量篇幅介紹Servlet相關內容,看不進去,要麼太淺,看完又以爲好像並無學到什麼。因此從你們的需求出發,內容主要包括如下四個部分:html
須要的小夥伴能夠下載FirstJavaWeb工程來對照着看。java
Servlet是運行在服務器端的程序,用於處理及響應客戶端的請求,它是個特殊的Java類,這個Java類必須繼承javax.servlet.http.HttpServlet,HttpServlet類提供了不一樣的方法用於處理客戶端的請求。 web
方法 | 描述 |
---|---|
doDelete | 用於處理DELETE請求 |
doGet | 用於處理GET請求 |
doHead | 用於處理HEAD請求 |
doOptions | 用於處理OPTIONS請求 |
doPost | 用於處理POST請求 |
doPut | 用於處理PUT請求 |
doTrace | 用於處理TRACE請求 |
getLastModified | 返回一個long整數,值爲所請求數據的最後修改時間相對於GMT時間1970年1月1號0時0分0秒的毫秒數 |
service | 用於映射請求,根據請求的HTTP方法,調用do Method |
根據HttpServlet service方法的處理邏輯,HttpServlet目前只可響應客戶端的GET,HEAD,POST,PUT,DELETE,OPTIONS,TRACE請求。spring
http請求 | 描述 |
---|---|
GET | 獲取服務器上某一資源 |
HEAD | HEAD和GET本質是同樣的,區別在於HEAD請求的響應不包含響應實體,而僅僅包含響應消息頭 |
POST | 向服務器提交數據 |
PUT | PUT和POST極爲類似,PUT一般指定了資源的存放位置,向指定資源位置上傳其最新內容 |
DELETE | 刪除服務器上某一個資源 |
OPTIONS | 獲取服務器針對特定資源所支持的HTTP請求方法,請求頭等 |
TRACE | 回顯服務器收到的請求,主要用於測試或診斷 |
PUT,DELETE,OPTIONS,TRACE請求並不常使用,OPTIONS請求做者只在處理非簡單CORS(Cross-origin resource sharing)的時候碰見過。
一般咱們的請求只有GET和POST兩種,爲了響應這兩種請求,通常須要重寫doGet和doPost兩個方法,也能夠經過重寫service方法的方式。可是注意若是你重寫的service方法沒有調用do method 方法,即便你在Servlet中又重寫了其餘do method 方法也是不會被調用的,緣由咱們看了HttpServlet類的source code就會明白。apache
public abstract class HttpServlet extends GenericServlet { public HttpServlet() { } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_get_not_supported"); if(protocol.endsWith("1.1")) resp.sendError(405, msg); else resp.sendError(400, msg); } protected long getLastModified(HttpServletRequest req) { return -1L; } protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if(DispatcherType.INCLUDE.equals(req.getDispatcherType())) { doGet(req, resp); } else { NoBodyResponse response = new NoBodyResponse(resp); doGet(req, response); response.setContentLength(); } } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_post_not_supported"); if(protocol.endsWith("1.1")) resp.sendError(405, msg); else resp.sendError(400, msg); } protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_put_not_supported"); if(protocol.endsWith("1.1")) resp.sendError(405, msg); else resp.sendError(400, msg); } protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_delete_not_supported"); if(protocol.endsWith("1.1")) resp.sendError(405, msg); else resp.sendError(400, msg); } private static Method[] getAllDeclaredMethods(Class c) { if(c.equals(javax/servlet/http/HttpServlet)) return null; Method parentMethods[] = getAllDeclaredMethods(c.getSuperclass()); Method thisMethods[] = c.getDeclaredMethods(); if(parentMethods != null && parentMethods.length > 0) { Method allMethods[] = new Method[parentMethods.length + thisMethods.length]; System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length); System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length); thisMethods = allMethods; } return thisMethods; } protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Method methods[] = getAllDeclaredMethods(getClass()); boolean ALLOW_GET = false; boolean ALLOW_HEAD = false; boolean ALLOW_POST = false; boolean ALLOW_PUT = false; boolean ALLOW_DELETE = false; boolean ALLOW_TRACE = true; boolean ALLOW_OPTIONS = true; for(int i = 0; i < methods.length; i++) { Method m = methods[i]; if(m.getName().equals("doGet")) { ALLOW_GET = true; ALLOW_HEAD = true; } if(m.getName().equals("doPost")) ALLOW_POST = true; if(m.getName().equals("doPut")) ALLOW_PUT = true; if(m.getName().equals("doDelete")) ALLOW_DELETE = true; } String allow = null; if(ALLOW_GET) allow = "GET"; if(ALLOW_HEAD) if(allow == null) allow = "HEAD"; else allow = (new StringBuilder()).append(allow).append(", HEAD").toString(); if(ALLOW_POST) if(allow == null) allow = "POST"; else allow = (new StringBuilder()).append(allow).append(", POST").toString(); if(ALLOW_PUT) if(allow == null) allow = "PUT"; else allow = (new StringBuilder()).append(allow).append(", PUT").toString(); if(ALLOW_DELETE) if(allow == null) allow = "DELETE"; else allow = (new StringBuilder()).append(allow).append(", DELETE").toString(); if(ALLOW_TRACE) if(allow == null) allow = "TRACE"; else allow = (new StringBuilder()).append(allow).append(", TRACE").toString(); if(ALLOW_OPTIONS) if(allow == null) allow = "OPTIONS"; else allow = (new StringBuilder()).append(allow).append(", OPTIONS").toString(); resp.setHeader("Allow", allow); } protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String CRLF = "\r\n"; StringBuilder buffer = (new StringBuilder("TRACE ")).append(req.getRequestURI()).append(" ").append(req.getProtocol()); String headerName; for(Enumeration reqHeaderEnum = req.getHeaderNames(); reqHeaderEnum.hasMoreElements(); buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName))) headerName = (String)reqHeaderEnum.nextElement(); buffer.append(CRLF); int responseLength = buffer.length(); resp.setContentType("message/http"); resp.setContentLength(responseLength); ServletOutputStream out = resp.getOutputStream(); out.print(buffer.toString()); out.close(); } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if(method.equals("GET")) { long lastModified = getLastModified(req); if(lastModified == -1L) { doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader("If-Modified-Since"); } catch(IllegalArgumentException iae) { ifModifiedSince = -1L; } if(ifModifiedSince < (lastModified / 1000L) * 1000L) { maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(304); } } } else if(method.equals("HEAD")) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if(method.equals("POST")) doPost(req, resp); else if(method.equals("PUT")) doPut(req, resp); else if(method.equals("DELETE")) doDelete(req, resp); else if(method.equals("OPTIONS")) doOptions(req, resp); else if(method.equals("TRACE")) { doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object errArgs[] = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } } private void maybeSetLastModified(HttpServletResponse resp, long lastModified) { if(resp.containsHeader("Last-Modified")) return; if(lastModified >= 0L) resp.setDateHeader("Last-Modified", lastModified); } public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest)req; response = (HttpServletResponse)res; } catch(ClassCastException e) { throw new ServletException("non-HTTP request or response"); } service(request, response); } private static final long serialVersionUID = 1L; private static final String METHOD_DELETE = "DELETE"; private static final String METHOD_HEAD = "HEAD"; private static final String METHOD_GET = "GET"; private static final String METHOD_OPTIONS = "OPTIONS"; private static final String METHOD_POST = "POST"; private static final String METHOD_PUT = "PUT"; private static final String METHOD_TRACE = "TRACE"; private static final String HEADER_IFMODSINCE = "If-Modified-Since"; private static final String HEADER_LASTMOD = "Last-Modified"; private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings"; private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings"); }
HttpServlet是一個不包含任何抽象方法的抽象類,繼承GenericServlet類,很巧妙的編碼,既不可以直接建立它的實例,也不強迫子類去實現任何方法,就是說咱們本身的Servlet類能夠經過繼承它,不重寫任何方法就可以直接對外提供服務。
HttpServlet類中有兩個的service方法,public void service(ServletRequest req, ServletResponse res)方法是其父類GenericServlet類的抽象方法實現,做用是接受客戶端的請求並將其傳遞給重載的service方法,protected void service(HttpServletRequest req, HttpServletResponse resp)。容器會針對每一個客戶端請求建立一個處理線程,準確來講應該是使用線程池中空閒的線程,並建立Request和Response對象傳遞給處理線程,就是說瀏覽器的一次http請求的全部信息都封裝在HttpServletRequest中,而HttpServletResponse對象表明服務器對客戶端的響應,能夠經過操做這兩個對象來交換數據。
protected void service(HttpServletRequest req, HttpServletResponse resp),是HttpServlet類定義的重載方法,訪問權限是protected,可用於子類繼承。方法首先獲取本次請求的http方法,並根據方法的類型去進入相應的if else分支處理,除了GET請求處理相對複雜一些,其餘處理很簡單,直接調用相應請求的do method 方法,當用戶的請求不是上面列出的請求方法時,會向客戶端返回501狀態碼,Method is not implemented by this servlet for this URI。在service中的GET請求邏輯部分首先調用getLastModified(HttpServletRequest req)方法獲得一個long整數lastModified,若是爲-1L則調用doGet方法,不然獲取本次請求的「If-Modified-Since」頭部得值,「If-Modified-Since」帶一個時間值表明請求數據的上次修改時間,它會與lastModified值比較,若是lastModified值比較新,容器調用maybeSetLastModified(resp, lastModified)方法,這個方法的做用是在響應頭中加一個「Last-Modified」頭,告訴瀏覽器你應該更新本地緩存了。除此來以外的任何結果,則返回304狀態碼,告訴瀏覽器從你上次訪問我以後,請求的網頁未修改,你可使用本地緩存直接加載頁面,節省帶寬和開銷。咱們看到子類若是不重寫getLastModified方法的話,這個方法將永遠返回-1L,每次GET請求都只是調用doGet方法而已。
當瀏覽器發現響應中有「Last-Modified」頭部,那麼它在下一次請求同一數據時會加上「If-Modified-Since」請求頭,值爲上一次響應的「Last-Modified」時間值。304狀態碼告訴客戶端自從上次請求後,請求的網頁未修改,本地cache的頁面是最新的。服務器返回此響應時,不會返回網頁內容,也就是說瀏覽器只在每次啓動後第一次訪問這個頁面時,才向服務器發出請求,對於後續的訪問都是直接加載本地緩存的頁面,這在提升網站性能方面特別有用。
帶你們演示一下,咱們先寫一個servlet,代碼貼上,重寫了父類的doGet方法,邏輯也很簡單,就是打印系統當前時間相對GMT1970年1月1日0點0時0分的毫秒數。設計模式
@WebServlet(urlPatterns = ("/example/one")) public class ExampleOneServlet extends HttpServlet { /** * serialVersionUID:TODO. */ private static final long serialVersionUID = 6686766899053457864L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().append("Frist Servlet " + System.currentTimeMillis()); } // @Override // protected long getLastModified(HttpServletRequest req) { // long now = System.currentTimeMillis(); // System.out.println("getLastModified: " + now); // return now; // } // @Override // protected long getLastModified(HttpServletRequest req) { // return req.getDateHeader("If-Modified-Since"); // } }
Tomcat中運行,瀏覽器查看,此時請求頭和響應頭中並無任何特殊的頭部。 api
解註釋第一個重寫的getLastModified方法並運行,這個getLastModified方法每次返回一個新的時間值,而且老是大於請求頭」If-Modified-Since」的時間值。此次咱們看到響應頭中多了「Last-Modified」字段,刷新下瀏覽器,此時請求頭中多了」If-Modified-Since」,而且響應頭中依然包含「Last-Modified」頭,響應狀態碼爲200,瀏覽器窗口中顯示的字符串的時間值也一直在隨着時間推動。 瀏覽器
註釋這個getLastModified方法,並解註釋第二個重寫的getLastModified方法,這個方法每次返回的值爲請求頭」If-Modified-Since」的值。刷新下瀏覽器,咱們看到此時的響應頭中並無」Last-Modified」頭,而且響應的狀態碼爲304。再刷新下瀏覽器,咱們看到瀏覽器窗口中顯示的字符串中的時間值沒變,證實了此時瀏覽器加載的是緩存的數據,並非服務器端傳回來的新數據。還有」If-Modified-Since」的時間值也沒有改變。 緩存
打開Eclipse,注意安裝用於Web開發的Java EE版本的Eclipse,將下載好的Apache Tomcat® [Tomcat是一個輕量級免費開源的Servlet容器,可以運行咱們的Java Web應用] 配置進來。File->New->Other。點擊Server->next。相關工具你們也能夠直接去個人百度網盤下載,Eclipse兩個版本均可以用,zip是luna,exe是最新的neon,本身安裝。tomcat
選擇相應的Tomcat版本->next,找到Tomcat安裝路徑,Finish,一個server就給配好了。
接下來咱們建立一個Java Dynamic Web Project。File->New->Dynamic Web Project,如若沒找到Dynamic Web Project菜單,則File->New->other,在Wizards輸入要搜索的項。在彈出的New Dynamic Web Project窗口輸入你的Project name,一路next,在點擊最後Finish按鈕以前勾選Generate web.xml deployment discriptor。Servlet 3.0以後支持註解的配置方式,因此生成web.xml成爲可選項,這裏勾選上,咱們一下子會演示使用註解和xml文件兩種方式配置Servlet。
看下這個project的Web工程目錄結構,就是下面這個WebContent文件夾,這個文件夾下對應着project部署到容器中時的上下文根路徑,根目錄,就是咱們在瀏覽器輸入根URL所訪問的服務器地址,其餘資源地址都是相對於這個地址的相對路徑。
再來看下這個project部署到容器中時,它的目錄結構。在這個project上右鍵->Run As->Run on Server。根據這個路徑找到eclipse中運行的tomcat的發佈文件夾%you eclipse workplace%\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps
找到咱們的project目錄,進去後發現其下內容與eclipse中project的WebContent下的內容徹底同樣,驗證了咱們以前的說法,小夥伴想問why?回到Eclipse中,project上右鍵->Properties,找到Deployment Assembly項,Deployment Assembly項爲web工程指定在發佈的時候要部署到容器中的部件。咱們看到WebContent文件夾對應的就是部署後的項目的上下文根路徑,就這麼個緣由。
咱們能夠更改這個目錄爲咱們想要的目錄,將Deployment Assembly中的WebContent Remove,新建立一個webapp目錄,並照着WebContent目錄下的文件,文件夾新建一份,以後把它配置爲Deploy Path。此時咱們的項目結構是這樣的:
我又新加了一個index.jsp,內容很簡單,顯示」hello world!」,運行一下,成功,徹底是本身掌控。
對於使用Eclipse的初學者來講,知道這個至關重要,好比當使用maven spring的時候,咱們的項目第一次運行後可能會起不起來,出現第三方庫的ClassNotFoundException,這個時候通常去檢查Deployment Assembly中是否將Maven Dependencies庫加到了WEB-INF/lib下面,很快就解決了。
回過頭看下WebContent目錄下的內容,META-INF,看名字就能明白,存放一些meta information,這個目錄下的文件應該都是build工具來生成的。WEB-INF是一個特殊的文件夾,容器會保護此文件下的內容不被瀏覽器訪問,就是說咱們直接在瀏覽器經過URL的方式是訪問不到的,因此咱們通常會將圖片文件夾,JS,CSS文件夾,以及咱們的JSP頁面放到WEB項目的根目錄下,而將配置文件放到WEB-INF下面。WEB-INF的lib文件夾存放本應用所依賴的第三方包。
web.xml文件用來配置Web應用的組件。咱們本身的Servlet要在文件中配置後才能訪問,不過Servlet 3.0以後也能夠不用配置文件而直接使用註解的方式配置servlet,兩種方式各有利弊,註解散落在個各種中,很差管理,而使用配置文件都集中在一處配置,一目瞭然,可是隨着內容的增長會略顯臃腫。<welcome-file-list/>元素配置Web應用的首頁列表。默認生成的配置信息指定該Web應用的首頁依次是index.html,index.htm,index.jsp等,當index.html不存在的時候,則由index.htm頁面來充當首頁,以此類推,當容器沒有找到<welcome-file-list/>中的頁面時,咱們會看到404 error report,不要被這種頁面嚇到,很好解決,要麼檢查你的URL路徑是否正確,要麼新建一個URL所請求的資源,這裏咱們重寫一個首頁文件就OK了。
Servlet建立
在src文件加上右鍵->New->Class->輸入Class Name->點擊Browse->搜索HttpServlet->點擊OK->點擊Finish,一個HttpServlet類就建立完成了。
使用註解的方式配置Servlet,只須要在咱們建立的類上加上註解@WebServlet(urlPatterns = ("you path"))
便可,直接運行,頁面顯示405狀態碼,HttpServlet中doGet方法的默認實現,這就OK了,你也能夠重寫doGet方法,加入本身的處理邏輯。
使用文件配置的方式,打開web.xml,加入以下配置便可:
<servlet> <servlet-name>exampleOne</servlet-name> <servlet-class>example.ExampleTwoServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>exampleOne</servlet-name> <url-pattern>/example/two</url-pattern> </servlet-mapping>
<servlet>配置Servlet基本信息,<servlet-name>的值能夠配置成任何你喜歡的名字,只要和<servlet-mapping>中的name先後對應上就行,<servlet-class>指定Servlet類的徹底限定名。<servlet-mapping>配置Servlet的URL路徑,<url-pattern>指定Servlet的URL路徑。
這一節的目的是經過Tomcat中的Examples瞭解相關類的應用。點擊Examples出現404,是由於安裝Tomcat時沒有勾選examples的選項,examples和host-manager同樣,是Tomcat本身寫的web應用而已,一樣部署在Tomcat中,咱們在%CATALINA_HOME%\webapps
下都能找到,一樣的也能夠從別的途徑得到examples代碼,放到webapps下面就能夠訪問了。
Servlets examples、JSP Examples、WebSocket Examples,咱們只過了Servlets examples,例子都很適合初學者閱讀。我把源碼中從上到下涉及到的全部類及方法總結出來,本身對照着去看代碼。
ResourceBundle(可以方便讀取.properties配置文件)
方法 | 描述 |
---|---|
ResourceBundle.getBundle(String baseName, Locale locale) | 返回具備給定基本名稱和語言環境的ResourceBundle對象 |
String getString(String key) | 今後ResourceBundle資源包中獲取給定鍵的字符串 |
HttpServletRequest(獲取請求頭和請求參數)
方法 | 描述 |
---|---|
Locale getLocale() | 獲取客戶端所使用的本地語言 |
String getMethod() | 返回http請求的方法名稱 |
String getRequestURI() | a String containing the part of the URL from the protocol name up to the query string () |
String getProtocol() | 返回協議名稱和協議版本號 |
String getMethod() | 返回http請求的方法名稱 |
String getPathInfo() | 返回請求URL中Servlet路徑以後查詢字符串以前的額外路徑信息。若是沒有額外路徑信息,返回null |
String getRemoteAddr() | 獲取發送請求的客戶端的IP地址 |
Object getAttribute(String name) | 獲取指定屬性的屬性值 |
Enumeration<String> getHeaderNames() | 獲取全部請求參數的名稱 |
String getHeader(String name) | 獲取指定請求頭的值 |
String getParameter(String name) | 獲取請求參數的值 |
Cookie[] getCookies() | 獲取本次請求攜帶地全部cookies |
HttpSession getSession(boolean create) | 返回與當前請求相關聯的HttpSession對象,create爲true時,若是當前會話無效,則建立,不然返回null |
Servlet 3.1 API文檔對String getRequestURI()方法的描述是a String containing the part of the URL from the protocol name up to the query string (),翻譯過來是URL中的協議以後到查詢字符串以前的部分,但實際上獲得是域名以後到查詢字符串的部分,理解有出入,故不敢隨便翻譯。看下它的底層實現return request.getServletContext();
,實際是就是獲取了ServletContextPath。
String getPathInfo()方法返回請求URL中Servlet路徑以後查詢字符串以前的額外路徑信息。若是沒有額外路徑信息,返回null。查詢字符串,客戶端發起的GET請求若是攜帶參數,如http://localhost:8088/examples/servlets/servlet/RequestInfoExample?param1=value1¶m2=value2
,的形式?號後面的字符串會被解析爲查詢字符串,格式通常是?param1=value1¶m2=value2。而String getPathInfo()返回servlet path和查詢字符串之間的路徑,如http://localhost:8088/examples/servlets/servlet/RequestInfoExample/user/xiaoming?param1=value1¶m2=value2
,咱們配置的servlet path是/servlets/servlet/RequestInfoExample,那麼extra path就是/user/xiaoming,不要搞混了。
HttpServletResponse(表明服務器對客戶端的響應)
方法 | 描述 |
---|---|
void setContentType(String type) | 設置發送到客戶端的響應的內容類型 |
void setCharacterEncoding(String charset) | 設置發送到客戶端的響應的字符編碼(MIME字符集),例如,設置爲UTF-8 |
PrintWriter getWriter() | 獲取頁面輸出流 |
void addCookie(Cookie cookie) | 設置cookie |
Cookie(瀏覽器端跟蹤用戶會話狀態)
方法 | 描述 |
---|---|
String getName() | 獲取cookie的名字 |
String getValue() | 獲取cookie的值 |
HttpSession(服務器端跟蹤用戶會話狀態)
方法 | 描述 |
---|---|
long getCreationTime() | 獲取session的建立時間 |
long getLastAccessedTime() | 獲取上一次訪問當前會話session的時間 |
String getId() | 獲取當前會話ID |
void setAttribute(String name, Object value) | 設置session範圍內屬性 |
Enumeration<String>getAttributeNames() | 獲取全部屬性的名字 |
Object getAttribute(String name) | 獲取指定屬性名的value |
Session和cookie區別(摘自網絡):
In Template pattern, an abstract class exposes defined way(s)/template(s) to execute its methods. Its subclasses can override the method implementation as per need but the invocation is to be in the same way as defined by an abstract class. This pattern comes under behavior pattern category.[在模板模式中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類能夠按須要重寫方法實現,但調用將以抽象類中定義的方式進行。這種類型的設計模式屬於行爲型模式。]
在Servlet中,模板方法由service()方法擔任,子類按需重寫do method 方法,不過須要注意一點,更多時候咱們會將Template Method定義爲final方法,防止子類重寫父類模板。