1. 程式人生 > 其它 >Servlet 與 JSTL/EL

Servlet 與 JSTL/EL

Servlet 與 JSTL/EL

前面介紹的JSP基礎語法(在頁面中嵌入Java小指令碼和表示式),雖然可以基本實現動態網站的開發,但卻使得介面層程式碼的維護變得複雜,HTML程式碼和Java程式碼混編在頁面中,頁面美工和Java程式設計師也難以實現分工。

下面介紹的Servlet和JSTL/EL可以實現介面層Java程式碼和HTML程式碼的完美分離,讓我們的Web應用程式更容易維護。

1 Servlet

前面我們討論過一個JSP頁面的執行過程如下圖所示。JSP頁面雖然包含了Java指令碼,但畢竟還不是一個Java類,要把一個JSP頁面作為一個Java程式被執行並最終輸出HTML結果,伺服器還須要把JSP頁面轉換成一個實際的Java類。Servlet就是JSP頁面最終被解釋成的Java類,換句話,JSP頁面實質上就是一個Servlet類。

1.1 Servlet生命週期

​ Servlet是JSP規範中的一個非常重要介面,實現了該介面的類,就可以部署在伺服器(Tomcat)中,用來處理使用者傳送過來的請求。

​ Serlvet介面的主要成員如下所示:

​ 方法 描述

​ void init(ServletConfig config) 初始化一個Servlet

​ void service() 用於處理(響應)一個請求

​ void destory() 用於銷燬一個Servlet

​ 當客戶傳送一個請求到伺服器時,伺服器會根據請求的URL挑選一個合適的Servlet類去處理該請求;如果選中的Servlet類還沒被例項化,則例項化出一個Servlet物件,依次執行該物件的init()方法和service()方法,service()方法會根據請求內容生成HTML響應,並由伺服器發回給客戶;如果選中的Servlet類已經被例項化,則越過例項化和init()階段,直接呼叫該Servlet物件的service()方法去生成HTML響應;Servlet物件的destory()方法會在Web應用程式停止(或伺服器停止)時被呼叫,用於銷燬Serlvet物件。每個Servlet類在伺服器中都是單例的,也就說通常URL相同的所有請求會由同一個Servlet物件處理。

​ 每個Servlet會經歷例項化(new,僅在第一次請求時)、初始化(init,僅在第一次請求時)、服務(service,在每次請求時)和銷燬(destory,僅在應用停止時)四個階段,這個過程常常被稱為“Servlet生命週期”。

1.2 HttpServlet

Serlvet介面實在過於底層,直接實現它是難以實現HTTP請求處理的,實際程式設計中,我們往往採用繼承HttpServlet類的方式去編寫HTTP請求處理。HttpServlet類同樣派生自Servlet介面,但其內部已經實現了大量的Servlet API基礎構件,通過使用這些基礎構件,我們就可以輕鬆完成請求處理並返回HTML響應。

在HttpServlet中,原來Servlet介面中用於處理請求的service方法已經被解構成doGet、doPost等若干個子方法,每個方法處理一種方式的HTTP的請求(如HTTP的get請求和post請求);而且在doXxx方法中,我們可以通過引數(HttpServletRequest和HttpSerlvetResponse)獲取Servlet API,這樣我們只需要把程式設計的關注點放在業務上就可以了。

使用HttpServlet來處理請求,須要兩個步驟:其一,建立一個HttpServlet類編寫doGet或doPost處理程式碼;其二,在web.xml中配置該HttpServlet類,讓它處理某種URL的請求。

下面的示例使用一個Servlet來輸出HTML頁面。

	public class MyFirstServlet extends HttpServlet {
		public void doGet(HttpServletRequest request, HttpServletResponse response)
				throws ServletException, IOException {
			response.setContentType("text/html");
			PrintWriter out = response.getWriter();
			out.println("<HTML>");
			out.println("  <HEAD><TITLE>A Servlet</TITLE></HEAD>");
			out.println("  <BODY>");
			out.print("    This is ");
			out.print(this.getClass());
			out.println(", using the GET method");
			out.println("  </BODY>");
			out.println("</HTML>");
			out.flush();
			out.close();
		}
	}

​ 要使得客戶端可以請求該Servlet,須要在web.xml對其進行配置,如下所示:

	<servlet>
    	<servlet-name>MyFirstServlet</servlet-name>
    	<servlet-class>com.demo.action.MyFirstServlet</servlet-class>
  	</servlet>
	<servlet-mapping>
    	<servlet-name>MyFirstServlet</servlet-name>
    	<url-pattern>/servlet/MyFirstServlet</url-pattern>
  	</servlet-mapping>

​ 當用戶請求網站的“/servlet/MyFirstServlet”URL時,伺服器就會使用“com.demo.action.MyFirstServlet”類來對其響應。

1.3 Servlet的實際應用

Servlet和JSP同樣可以處理使用者的請求,但它們所側重的地方不一樣,JSP是一個頁面檔案,更適合用於組織HTML的輸出;而Servlet則是一個Java類,更適合用來執行業務(呼叫業務層程式碼,準備資料)處理和控制頁面的轉向。

回顧之前的一個示例,我們在完成JSP登入驗證並控制頁面轉向時使用過一個loginController.jsp頁面,用於呼叫業務層實現使用者驗證並根據驗證結果實現跳轉。該頁面只是一個邏輯頁面,不存在HTML程式碼,如果使用JSP來實現,會有種奇怪的感覺。

	<%@ page language="java" pageEncoding="UTF-8"%>
	<%
		request.setCharacterEncoding("UTF-8");	//解決Post引數中文亂碼問題
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		if("sam".equals(username) && "123".equals(password)){
			response.sendRedirect("login_success.jsp");
		}else{
			request.setAttribute("error", "使用者名稱或密碼有誤。");
			request.getRequestDispatcher("login.jsp").forward(request, response);
		}
	%>

這種專門用來中轉的JSP頁面就很適合用Servlet來實現。

其實,在每個JSP頁面中,與顯示無關(非HTML輸出控制)的Java指令碼程式碼,都應該編寫在Serlvet中。

2 JSTL和EL

通過Servlet技術,我們已經可以把JSP中與顯示無關的Java邏輯程式碼分離到Servlet了,但是與顯示相關的一些Java指令碼(如for迴圈的HTML輸出控制、在頁面中注入的Java表示式)還無法替代。為此SUN公司推出了JSTL和EL,用來實現頁面的輸出控制和表示式的注入。

JSTL叫做JSP標準標籤庫,提供了大量的標籤來控制JSP頁面的輸出,可以替代頁面中用於控制輸出的Java指令碼(<% ...; %>);而EL是一種表示式語言,用於實現動態的Java表示式求解,可以替代頁面中的表示式(<%=… %>)。

2.1 EL的使用

EL的實質是一個字串,其語法非常簡單,基本語法如下所示:

${ 範圍.物件.屬性 } 或 ${ 範圍.物件[屬性] }

EL可以替代Java表示式,獲取幾乎所有頁面上下文範圍中的資料,控制EL表示式的取值範圍的隱式物件如下所示:

​ 範圍物件 型別 說明

pageContext javax.servlet.ServletContext 表示此JSP的PageContext

pageScope java.util.Map 取Page範圍的屬性名所對應的值

requestScope java.util.Map 取Request範圍的屬性名所對應的值

sessionScope java.util.Map 取Session範圍的屬性名稱所對應的值

applicationScope java.util.Map 取Application範圍的屬性名所對應的值

param java.util.Map 同request.getParameter()獲取請求引數

paramValues java.util.Map 同request.getParameterValues()

header java.util.Map 同request.getHeader()

headerValues java.util.Map 同request.getHeaders()

cookie java.util.Map 同request.getCookies()

initParam java.util.Map 同ServletContext.getInitParameter()

下面頁面示例程式碼通過EL獲取名為username的cookie中的值:

<h1>歡迎光臨,${cookie.username.value}。</h1>

如果不使用EL表示式,獲取同樣的cookie值須要繁複的Java指令碼程式碼:

	<%
		String username = null;
		Cookie[] cookies = request.getCookies();
		if(cookies != null){
			for(Cookie c : cookies){
				if(c.getName().equals("username")){
					username = c.getValue();
				}
			} 
		}
	%>
	<% if (username!=null) { %>
		<h1>歡迎光臨,<%=username%>。</h1>
	<% } %>

使用EL訪問資料對比使用Java表示式有兩個很大的優勢:一是無需擔心物件為空值時丟擲NullPointerException,EL遇到物件為空時會忽略該表示式不作任何顯示;二是無需型別轉換,EL表示式中的變數都是弱型別的。

另外,EL表示式中的一些範圍物件是可以省略的。下面示例通過EL獲取Session中儲存的user物件中的username屬性值 :(注Session範圍是可省略範圍物件宣告的)

<h1>歡迎光臨,${sessionScope.use.userrname }。</h1>

如果使用Java指令碼和表示式,則須要做繁複的型別轉換和非空判斷:

	<%
		if(session.getAttribute("user") != null) {
			User user = (User)session.getAttribute("user");
	%>
		<h1>歡迎光臨,<%=user.getUsername()%>。</h1>
	<%  }  %>

EL表示式除了可以獲取資料之外還可以實現一些運算,完成一個複雜的表示式的求解。

下面示例在EL中使用三元運算子“?:”輸出登入使用者的使用者名稱,若使用者沒有登入(session的user為空)則顯示“您尚未登入”。

<h1>歡迎光臨,${ user!=null? user.username : "您尚未登入" }。</h1>

2.2 JSTL的使用

JSTL(JSP Standard Tag Library,JSP標準標籤庫)是SUN公司為JSP技術推出的一套完善標籤庫,用於控制JSP頁面中的HTML輸出。

JSTL分為core(核心)、fmt(格式)、fn(函式)、sql、xml五套自標籤。

使用JSTL,要在頁面開始的地方用<%@ taglib uri="標籤庫uri" prefix="字首簡稱" %>宣告所引用的標籤庫。

 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

​ 下面將為大家講解常用的幾個JSTL標籤。

2.2.1 core標籤

 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

(1)<c:out>

<c:out> 標籤用於在JSP中顯示資料,它有如下屬性:

​ 屬 性 描 述 是否必須 預設值

​ value 輸出的資訊,可以是EL表示式或常量 是 無

​ default value為空時顯示資訊 否 無

​ escapeXml 為true則避開特殊的xml字符集 否 true

下面示例使用<c:out>和EL輸出使用者名稱,當EL索取的值為空時顯示“遊客”:

您的使用者名稱是<c:out value="${user.username}" default="遊客" />

(2)<c:set>

<c:set>標籤用於在JSP中設定儲存資料,它有如下屬性:

​ 屬 性 描 述 是否必須 預設值

​ value 要儲存的資訊,可以是EL表示式或常量 否

​ target 要修改屬性的變數名,一般為javabean物件 否 無

​ property 需要修改的javabean屬性 否 無

​ var 需要儲存資訊的變數 否 無

​ scope 儲存資訊的變數的範圍 否 page

​ 注意:如果指定了target屬性, 那麼property屬性也必須指定。

​ 下面示例將EL獲取的值設定到Session範圍中儲存,session的鍵為test2:

<c:set value="${test.testinfo}" var="test2" scope="session" />

(3)<c:if>…</c:if>

​ <c:if>標籤使用者JSP頁面中的分支流程控制,有如下屬性:

​ 屬 性 描 述 是否必須 預設值

​ test 需要評價的條件,相當於if (...){}語句中的條件 是 無

​ var 要求儲存條件結果的變數名 否 無

​ scope 儲存條件結果的變數範圍 否 page

(4)<c:forEach>…</c:forEach>

<c:forEach>標籤用於迴圈遍歷集合元素,它有以下屬性:

​ 屬 性 描 述 是否必須 預設值

​ items 進行迴圈的專案 否 無

​ begin 開始條件 否 0

​ end 結束條件 否 最後一項

​ step 步長 否 1

​ var 代表當前專案的變數名 否 無

​ varStatus 顯示迴圈狀態(含有屬性index) 否 無

值得一提的是,其中varStatus包括以下屬性:

​ 特性 描述

​ current 當前這次迭代的(集合中的)項

​ index 當前這次迭代從 0 開始的迭代索引

​ count 當前這次迭代從 1 開始的迭代計數

​ first 用來表明當前這輪迭代是否為第一次迭代的標誌

​ last 用來表明當前這輪迭代是否為最後一次迭代的標誌

​ begin begin 屬性值

​ end end 屬性值

​ step step 屬性值

下面示例通過<c:forEach><c:if>標籤,實現電影資訊的顯示,並且實現表格斑馬條紋的樣式效果。

	<table>
		<tr><th>分類編號</th><th>分類名稱</th></tr>
		<c:forEach var="c" items="${categories}" varStatus="vs">
		<c:if test="${vs.index%2==0}">
			<tr style="background-color:yellow;"><td>${c.id}</td><td>${c.name}</td>
		</c:if>
		<c:if test="${vs.index%2!=0}">
			<tr><td>${c.id}</td><td>${c.name}</td>
		</c:if>
		</c:forEach>
	</table>

2.2.2 fmt標籤

 <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>

日期格式化標籤:<fmt:formatDate>

屬性名 說明 EL 型別 必須 預設值
value 將要格式化的日期物件。 Java.util.Date
type 顯示的部分(日期 date、時間 time或者兩者 both)。 String date
partten 格式化的樣式。 String