2010年7月26日 星期一

JSP & Servlet JSP基礎概念

在JavaEE裡最常來作業面呈現的還是JSP了
Servlet雖然也可以靠著 writer 物件來作出頁面呈現
但程式碼的部分不比JSP美觀及直觀
接下來就作些JSP的簡單介紹

JSP檔案的部署位置在Web Application的根目錄底下即可
在讀取過程中,JSP會先被container轉譯成Servlet檔(.java),接著再被編譯成類別檔(.class)
所以在執行時還是與Servlet一樣

首先來個簡單的JSP範例:
test.jsp==============

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8" import="tw.vencs.storage"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>For Test</title>
</head>
<body>
<%!
  storage sto = new storage();
  int temp = 5;
%>
<%
  out.println("We get " + sto.wordGetter());  //out是JSP的隱含物件
%>
<hr />
<p>Now we will show a number: <%=temp %> </p>
</body>
</html>


storage.java============

package tw.vencs;

public class storage {
  private String[] innerWord = {"one", "two", "three"};
 
  public String wordGetter(){
  int temp = (int) (Math.random()*3);
  return innerWord[temp];
  }
}

我們在範例中的頁面裡放入了Scriptlet來作出動態效果
Scriptlet指的就是簡單的Java程式碼
正如範例所示,Java的程式碼必須放在 <% %> 裡
也因為是Java程式碼,所以結尾要加上 ; 

<%@ page....  這行程式碼代表指令
<%後有個@符號,讓它跟放入一般Java程式碼的Scriptlet不一樣
指令作用就像是header一樣,能在轉譯期間將特殊指示傳給container
指令共有三種: page、include、taglib
雖然只有三種,但卻可以搭配屬性而作到很多的效果
page 指令用來定義頁面的特性,總共有13種屬性
include 指令定義在轉譯期要被增加到當前頁面的文字或程式碼,用來建立可重複使用的區塊
taglib 定義供JSP使用的標籤函式庫,這個東西會在以後的筆記提到

page的屬性有:
import -
匯入套件用的指令

isTreadSafe -
定義轉譯出的Servlet是否實作STM介面。STM是失敗的東西,這裡用預設的 true 就好

contentType -
為JSP回應定義MIME Type

isELIgnored -
定義在JSP轉譯時,是否要忽略EL表達式。後續筆記才會開始討論EL

isErrorPage -
定義此頁面是否為另一個JSP頁面的錯誤頁面,預設值是false
若設定成 true ,此頁面就能存取exception 隱含物件(參照到被引發的例外或錯誤)

errorPage -
定義一個當錯誤或例外發生時會被送往的URL;與 isErrorPage 設定的網址搭配

language -
定義目前使用的語言,目前只接受唯一的預設值 java
這個屬性的設計是為了增加JSP的擴充性

extends -
定義在JSP轉Servlet時要繼承的父類別,一般不需要也不會用到這個選項

session -
定義頁面是否有 session 隱含物件,預設值 true

buffer -
定義 out 隱含物件如何使用buffering

autoFlush -
定義緩衝區的資料是否自動被送出,預設值 true

info -
轉譯後的Servlet可以用 getServletInfo method 來取得此項資訊

pageEncoding
為JSP定義字元編碼,預設值是 "ISO-8859-1"


運算式是另外一種表現方式,程式碼中的 <%=temp %> 就是這樣的例子
<% 後面接 = 就是運算式的寫法
運算式裡不用加 ; 作結尾,它提供了我們作簡短的輸出
它被container轉譯後,會被當成一般Scriptlet裡的 out.print() 的參數輸出
也因為是 out.print() 的參數,若運算式裡的程式碼無法輸出結果的話,會回傳錯誤
boolean值會被輸出成為顯示 true 或 false 的字串
物件類別需要有 toString() method才得以被輸出

<%! 則是宣告用的程式碼,它會在轉譯出來的Servlet裡宣告成員
主要用在靜態及實例變數或方法的宣告,甚至連內部類別也可以
在這裡宣告的變數才會是全域變數,否則都會變成區域變數
這跟JSP轉Servlet時的編譯方式有關
container會將在一般Scriptlet裡的程式碼都放置在一個叫做 _jspService() 方法裡
每次進行服務時都會呼叫這個method,當然裡面的變數也只有區域變數的效力

JSP的註解寫法是 <%-- 註解  --%>,很類似HTML的註解寫法( <!-- 註解 --> )
JSP註解在頁面轉譯過程中就會被拿掉,不像HTML註解會顯示在 Client 端
所以想隱藏註解於JSP頁面的話選擇JSP註解寫法即可

隱含物件在轉譯的過程中會對映到Servlet/JSP API裡的某些東西,如:
out -> JspWriter      //不屬於PrintWriter的類別階層,但擁有與其相似的大多數方法
request -> HttpServletRequest
response -> HttpServletResponse
session -> HttpSession
application -> ServletContext
config -> ServletConfig
exception -> Throwable    //只有在error page才會看到的隱含物件
pageContext -> pageContext  //封裝了其他隱含物件,能夠利用它的參考取得其他隱含物件
page -> Object
能讓我們藉此使用Servlet的一些特性
隱含物件裡和指令裡都可以看到page的字眼,注意兩者完全不相干

被轉譯出來的Servlet會實作HttpJspPage介面
實作的三個主要 method 是 jspInit()、jspDestroy()、_jspService()
_jspService() 為container自動處理的方法,關係到頁面的轉譯,是不能被覆寫的方法
另外兩個方法則可以覆寫來進行處理
jspInit() 會被Servlet的 init() 呼叫,jspDestroy() 則會被Servlet的 destroy() 呼叫
所以接下來先示範用 jspInit() 在JSP中進行Servlet的初始化工作:
web.xml=========

<web-app ....>
....
    <servlet>
    <servlet-name>initTest</servlet-name>
    <jsp-file>/test.jsp</jsp-file>
    <init-param>
      <param-name>val</param-name>
      <param-value>1</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>initTest</servlet-name>
    <url-pattern>/test.jsp</url-pattern>
  </servlet-mapping>
........
</web-app>

與一般Servlet作組態的差別在於,必須增加<jsp-file> tag
接著透過宣告覆寫 jspInit() :
test.jsp============

<%!
public void jspInit(){
    ServletConfig scf = getServletConfig();
    String getAns = scf.getInitParameter("val");
    ServletContext scx = getServletContext();
    scx.setAttribute("Answer", getAns);
}
%>

因為是以Servlet的 init() 所呼叫的,所以到此方法執行時有ServletConfig和ServletContext可用
範例中取出初始參數後,將它放入設定成應用程式作用領域的屬性

與範例不大相同的是,設定及取得屬性一般使用JSP的4個隱含物件來做
1.Application
   Application.setAttribute() = Servlet裡的 getServletContext().setAttribute()

2.Request
   request.setAttribute()  ,Servlet中也是一樣的語法

3.Session
   session.setAttribute() = Servlet裡的 request.getSession().setAttribute()

4.Page
    pageContext.setAttribute(),pageContext是JSP裡獨特的物件

利用指向PageContext的參考可以取得任何作用域的屬性
設定Page作用域的屬性:
<% pageContext.setAttribute("val", 1); %>

取得Page作用域的屬性:
<%= pageContext.getAttribute("val") %>

使用pageContext設定Session作用域的屬性:
<% pageContext.setAttribute("val", 1, PageContext.SESSION_SCOPE); %>

使用pageContext取得Session作用域的屬性:
<%= pageContext.getAttribute("val", PageContext.SESSION_SCOPE) %>
(等同於 <%= session.getAttribute("val") %> )

如同 pageContext 用在session的範例那般,可以設定的作用領域有:
APPLICATION_SCOPE、PAGE_SCOPE、REQUEST_SCOPE、SESSION_SCOPE
如果不知道屬性所在的作用域,也可以利用 pageContext來尋找
語法是 <%= pageContext.findAttribute("val") %>
搜尋範圍會先檢查Page作用域,接著按照 Request -> Session -> Application
如果作用域裡的屬性名稱有相同者,會回傳先找到的那個

最後提到編譯的實際情況
頁面的轉譯及編譯只會發生一次,之後執行就與一般Servlet無異
然而實際執行時,最好先將JSP事先轉譯及編譯過
第一次實質的請求進來時再做,會耗費客戶端太多的時間
可以參考container廠商所提供的預先編譯的方法

沒有留言:

張貼留言