跳到主要內容

JavaEE Web 基礎概念

updated:2012/08

Java用來建置Web Application所使用的技術是Servlet與JSP
Servlet是用來撰寫網頁或是需求的類別
而JSP是提高Servlet可讀性極易維護性所的技術,在執行時會轉譯成Servlet

要撰寫Java Web Application首先要準備好Java開發環境以及Tomcat
 Tomcat是做為container來支援client端的請求與Server間的溝通
另外提供的功能尚有Servlet的生命週期管理、多執行緒支援、宣告式的權限管理、JSP轉譯

開發過程使用IDE會省下不少力氣
以eclipse為例,從一開始的建立Dynamic Web Project後,依照部署規定配置檔案



Web應用程式的目錄結構要求很嚴格,必須依照正確的位置擺放
上圖的藍色表示目錄,綠色的表示檔案
App 是這支應用程式的名稱,必須放置於Tomcat的webapps下
WEB-INF底下必須放置名為 web.xml的部屬描述檔
META-INF下的MANIFEST.MF宣告程式庫的相依性
有需要再設定即可,一般不用管它
lib資料夾底下放置會被引用到web application的JAR檔
web application使用類別時,會先到classes裡尋找是否有該類別檔案
如果沒有就再到container實作中放classes檔的地方或lib目錄底下尋找

通常在部署時,比較常採用的做法之一是使用WAR類別的來源檔
完成的Web應用程式可利用IDE的協助,匯出成WAR檔
將WAR檔放置於Tomcat的webapps下
當Tomcat 啟動時會偵測到WAR檔,並自動將它解開成為應用程式的形式(如上圖)
WAR的檔名會變成應用程式的名稱

在Eclipse的配置就如同下圖
規則與上述大致相同
於Java Resource -> src 建立Java(Servlet)檔,編譯與class檔的放置由IDE處理
其他的資源檔包含JSP放置於WebContent底下


資源若放置在WEB-INF目錄下,是無法直接被客戶端存取
僅能用程式的ServletContext的getResource()、getResourceAsStream()
以及RequestDispatcher機制取得(ServletContext、RequestDispatcher後續會提及)
這種放置設定可以保護不想直接被存取的內容


部署完後就可以開始進行開發了
Servlet/JSP處理HTTP請求的過程是:
1.使用者對Web Server發出HTTP請求
2.Server收到請求後將其轉給container,並由container分析請求內容後建立相對應的物件
3.container根據請求裡的URL分配給正確的Servlet
   並為這個請求建立或配制執行緒,以及將請求物件與回應物件交予該執行緒處理
4.container呼叫Servlet的sevice() method
   根據請求的類別,service()會再呼叫doGet()或doPost() method
5.接著由所呼叫的method建置動態網頁,並將它放入回應物件裡
6.執行緒結束,container轉換回應物件為HTTP回應傳回Client端
    接著刪除請求與回應物件

Servlet的程式碼如下:
package tw.vencs;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(" /HelloPage")
public class demo extends HttpServlet{
  public void doGet(HttpServletRequest request,          //請求物件
                              HttpServletResponse response)    //回應物件
                              throws IOException{

    PrintWriter out = response.getWriter();                  //從回應物件取得PrintWriter
    java.util.Date today = new java.util.Date();
    out.println("<html> "+.....                                       //將html寫回到回應物件裡
     );
  }
}

Servlet 3.0後增加了@WebServlet 標註功能
只要Servlet上有這行標註,container就會自動讀取資訊
範例中的標註指的是這支Servlet會處理經由 /HelloPage這個虛擬路徑來的HTTP請求
@WebServlet標籤還能透過參數做更多的設定:
@WebServlet(name="Hello", urlPatterns={"/HelloPage"}, loadOnStartup=1)

一般來說,Servlet沒有被呼叫前都不會建立實例(instance,實際存在的物件)
所以第一次請求Servlet的時間比較長,原因是container要先建立Servlet實例,接著初始化完後才能處理請求
不用參數設定則會啟用預設值;@WebServlet的loadOnStartup預設值為-1
當它被設定為大於0的值時
container會在web application啟動時從數值較小的Servlet開始初始化(並沒有產生實例)

在之前的Servlet版本中
Servlet的名稱、路徑配置都是集中在一個web.xml統一管理
這個XML檔案被稱作部署描述檔(Deployment Descriptor)
雖然對單一Servlet的設定不如標註來的方便,但對整個應用程式的管理會較有效率

設定的套用順序會是 標註 -> web.xml
所以將標註當成預設值,web.xml進行管理也是可以的
寫法如下:
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">


<!--有多少個類別屬於這個Web Application 就寫幾個servlet宣告 -->
<servlet>
  <!--將servlet-class與servlet-mapping產生關聯的名稱 -->
  <servlet-name>HelloServlet</servlet-name>
  <!--完整類別名稱,不含.class副檔名 -->
  <servlet-class>tw.vencs.HelloServlet</servlet-class>
</servlet>
 
<servlet>
    ..............

<!--servlet-mapping將虛擬路徑與Servlet關聯起來 -->
<servlet-mapping>
  <!--請求執行時所使用的servlet元素 -->
  <servlet-name>HelloServlet</servlet-name> 
  <!--配置client端用來請求servlet的虛擬路徑 -->
  <url-pattern>/Hello.do</url-pattern>
</servlet-mapping>

<!--有需要用到更多類別的話可以寫更多個mapping -->
<servlet-mapping>
  .......................

</web-app>

web.xml統一管理回應請求的虛擬路徑及應對它的Servlet
能做到不將檔案的實體位置外洩以增加安全性
甚至在<url-pattern>可以使用別的副檔名來隱藏使用的技術是java這件事

web.xml能做到的事情不只這些,後續的教學會再繼續介紹,現在的宣告只要照那樣撰寫就可以了
也可以去SUN的官網找它提供的範例複製,寫好之後命名為web.xml放置在WEB-INF目錄底下

一個請求的URI可以視為由context path、Servlet path、path info組成
因為一個container上也可能有多支web application在執行
所以context path對應請求的web application路徑
Servlet path 為對應到該請求目標的Servlet的路徑
path info則是其餘不包含請求參數與上述路徑的路徑
以 http://localhost:8080/Demo/Hello.do 這個請求URI為例
context path =  /Demo
Servlet path = /Hello.do
沒有多餘的path info(= null)


提到上面議題的因素在於Servlet的虛擬路徑配置(mapping)有幾種方法:
1.嚴格對應-
同字面,只有完整的請求路徑才會對應到該Servlet

2.路徑對應-
例如 /Display/* ,URI接在context path後若有 /Display/開頭的請求都會由這支Servlet處理
 .../Display/Vehicle.view、.../Display/Car.show等路徑皆可

3.副檔名對應-
例如 *.do ,會將.../A.do、.../B.do這類結尾相同的路徑的請求交給這支Servlet

4.預設Servlet
僅包括 / ,當找不到符合其他路徑對應時,由預設Servlet處理

若請求符合的條件有重複,則照嚴格程度對應
嚴格程度1最大,4最鬆
例如符合條件2、3,則由條件2的Servlet處理

以前的版本在使用第三方框架(如Struts、Spring)時
對於相對應的Servlet也必須寫進web.xml中,造成管理上的紊亂
Servlet3.0能用JAR檔作為部分模組,並能視需求抽換以獲取更大的彈性

JAR檔是Java中常見用來封裝程式的型態
其自身的META-INF這個資料夾有許多與JAR檔相關的應用方式
當Web application的請求找不到資源時,便會到引入的JAR檔的META-INF/resources底下找
JAR檔裡可以實作Servlet、Listener等,而且也能有自己的部署描述檔
在JAR的META-INF底下建立web-fragment.xml,內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-fragment xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
   http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd">
         <name>WebFragmwnt</name>
   <servlet>
     <servlet-name>fragment</servlet-name>
     <servlet-class>tw.vencs.FragmentServlet</servlet-class>
   </servlet>
   <servlet-mapping>
     <servlet-name>fragment</servlet-name>
     <url-pattern>/fragment</url-pattern>
   </servlet-mapping>
</web-fragment>


Eclipse中建立Web Fragment Project是件簡單的事
選單 -> File -> New -> Other -> Web Fragment Project
設定過程中注意Dynamic Web Project membership的設定
可以讓產出的JAR檔自動部署到專案中,而不用手動引入
專案底下的src/META-INF底下的web-fragment.xml可以直接修改使用
網路查詢到似乎把web-fragment.xml置於Dynamic Web Project底下的META-INF也可行
不過將JAR與其部署描述檔分離對部署來說更不方便
維持專案預設即可

要確認與Dynamic Web Project相關聯的Web Fragment Project則按右鍵點選Properties
Deployment Assembly中會顯出示結果

Servlet3.0對於DD檔與標註的配置順序沒有定義,可以由我們自行設定
第一種方法是在web.xml中定義:

<web-app ...>
    <absolute-ordering>
        <name>WebFragment1</name>
        <name>WebFragment2</name>
    </absolute-ordering>
</web-app>

第二種方法是在JAR檔中設定:

WebFragmwnt1.jar============

<web-fragment ...>
         <name>WebFragmwnt1</name>
   <ordering>
             <after><name>WebFragmwnt2</name></after>
         <ordering>
    .....
</web-fragment>

WebFragmwnt2.jar============

<web-fragment ...>
         <name>WebFragmwnt2</name>
    .....
</web-fragment>

WebFragmwnt3.jar============

<web-fragment ...>
         <name>WebFragmwnt3</name>
   <ordering>
             <before><others /></before>
         <ordering>
    .....
</web-fragment>


web.xml維持原樣
載入順序依序為web.xml -> WebFragmwnt3.jar > WebFragmwnt2.jar > WebFragmwnt1.jar
但注意web-fragment.xml中的name標籤設定
假如名稱重複的話,會忽略掉名稱重複的JAR檔

web.xml中的<web-app>標籤可另外定義metadata-complete屬性
它是用來通知container是否要去搜索標註或web-fragment.xml
設定為true表示已經在web.xml檔裡完成所有的設定,就不會另外尋找定義

留言