顯示具有 Servlet 標籤的文章。 顯示所有文章
顯示具有 Servlet 標籤的文章。 顯示所有文章

2022年11月23日 星期三

配合 Fetch 使用 MediaSource 播放 FMP4 - 影片輸出使用 jsp 或 servlet

 html 裡用 <video> 播放 MP4 影片檔時,
需要完全的讀取完影片檔才能播放,如果影片本身較長檔案大小較大時,
就會需要等待較長的時間等影片讀取完後才能開始播放,
因為 <video> 需要讀完影片檔中含有影片資訊的 moov (Movie Box) 區塊才會開始播放影片。

我們可以使用 Bento4 工具來查看影片中的檔案結構,
使用 Bento4 的 mp4dump 命令執行以下指令:

D:\Bento4\bin\mp4dump xxx_video.mp4

就可以在命令列視窗中看到影片的結構,

通常的 MP4 除了一開始的含有影片版本協議等資訊的 ftyp (File Type Box) 區塊,
接著的會是一個含有影片資訊的 moov (Movie Box) 區塊和一個含有實際影片數據的 mdat (Media Data Box) 區塊 (其他區塊可先忽略),像這樣:

ftyp + mdat + moov

mdat 和 moov 的擺放位置順序可以換,如果 moov 在 mdat 之後的話,
<video> 會需要在讀取完 moov 後才會開始播放影片。

如果想要提前 <video> 的影片開始播放時間的話,有幾種解決方法:

  1. 第一個方法是把 moov 放到 mdat 之前,
    讓 <video> 先讀取到 moov 的影片資訊,
    這樣 <video> 就可以不用等到影片完全載入完就開始播放影片,
    檔案結構會像這樣:

    ftype + moov + mdat

    使用 ffmpeg 工具執行以下指令就可以將影片的 moov 搬到 mdat 前面,可參考"ffmpeg 常用命令"這篇文章:
    D:\ffmpeg\bin\ffmpeg -i "normal_video.mp4" -movflags faststart -acodec copy -vcodec copy "moov_moved_video.mp4"
    第二個方法是把 MP4 轉成 FMP4 (fragmented mp4),
  2. FMP4 跟一般的 MP4 不同,
    除了有 moov 以外,還有 moof (movie fragment) 。
    一般的 MP4 檔案結構為一個 moov 和一個 mdat,
    而 FMP4 的檔案結構把影片分割成多個片段 (fragment),
    每個片段由一組 moof 和一個 mdat 組成,
    moof 包含了後面跟著的 mdat 的資訊,
    所以 <video> 可以在讀取影片時,先對讀到的片段進行播放,再一邊繼續讀取剩餘的片段,
    檔案結構會像這樣:

    ftype + moof + mdat + moof + mdat + moof + mdat + ........

    使用 Bento4 工具的 mp4fragment 命令可以方便地將 MP4 影片轉成 FMP4,
    執行以下指令即可:
    D:\Bento4\bin\mp4fragment "normal_video.mp4" "fragmented_video.mp4"
    也可使用 ffmpeg 工具來進行 FMP4 的轉檔,以下是兩種我在網路上找到的指令,
    參數有點不同,不過親自實測都是可以成功轉檔的,
    但我使用轉出來的檔案實作邊下載邊播放時有點問題,
    有時會出現
    Uncaught (in promise) DOMException: Failed to execute 'appendBuffer' on 'SourceBuffer': The HTMLMediaElement.error attribute is not null.
    的錯誤訊息,所以我還是會先以用 Bento4 轉檔的檔案來做實作:
    D:\ffmpeg\bin\ffmpeg -i "normal_video.mp4" -strict -2 -movflags frag_keyframe+empty_moov "fragmented_video.mp4"
    或
    D:\ffmpeg\bin\ffmpeg -i "normal_video.mp4" -movflags empty_moov+default_base_moof+frag_keyframe "fragmented_video.mp4"

成功把影片轉成 FMP4 後,就可以配合 Javascript 的 Fetch 功能實作邊下載影片檔邊播放的功能,在這裡我要展示一下使用 Javscript 的 Fetch 來邊讀取邊播放來自 servlet (或 jsp 或 nodeJs 等任何你喜歡的 server 程式碼)  的FMP4影片二進位流 (不支援非 FMP4 的影片):

首先是專案檔案結構,其中紅框部份是主要需要的檔案,此專案是一個 Java EE Web Application 專案,使用 Tomcat server 運行:




專案中放了三個測試的影片檔如下:

  1. normal_video.mp4:
    普通的影片檔,格式如:ftyp + mdat + moov
  2. moov_moved_video.mp4:
    把 moov 放到 mdat 之前的影片檔,格式如:ftype + moov + mdat
  3. fragmented_video.mp4:
    轉檔成 FMP4 的影片檔,格式如:ftype + moof + mdat + moof + mdat + moof + mdat + ........

首先是把影片檔用二進位輸出的 servlet, GetVideoBinary.java
/testWeb/src/test/GetVideoBinary.java:
package test;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLConnection;

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("/GetVideoBinary")
public class GetVideoBinary extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public GetVideoBinary() {
        super();
    }

	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String videoType = request.getParameter("videoType");
		if (videoType == null) {
			videoType = "";
		}
		
		String videoSourceDir = "/source";
		String videoSourceName = "normal_video.mp4";
		switch(videoType) {
			case "moov_moved_video":
				videoSourceName = "moov_moved_video.mp4";
				break;
			case "fragmented_video":
				videoSourceName = "fragmented_video.mp4";
				break;
		}
		String videoSourcePath = videoSourceDir + "/" + videoSourceName;
		
		String contentType = URLConnection.guessContentTypeFromName(videoSourceName);
		if (contentType == null) {
			contentType = "application/octet-stream";
		}
		response.setContentType(contentType);
		
		try (BufferedInputStream inputStream = new BufferedInputStream(this.getClass().getResourceAsStream(videoSourcePath));
			 OutputStream outputStream = response.getOutputStream();
			){
			byte[] buffer = new byte[1024];
			int numRead = 0;
			while ((numRead = inputStream.read(buffer)) != -1) {
				outputStream.write(buffer, 0, numRead);
			}
		}
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		service(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

在 GetVideoBinary.java 中,接受了一個 "videoType" 參數,來決定要讀取哪一個影片檔做輸出。

再來是主要的測試頁, index.html
/testWeb/WebContent/readableStreamTest/index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Readable Stream Test</title>

<script src="js/fmp4Player.js"></script>
<script>
	readAndPlayFMP4("fragmented_video");
</script>

</head>

<body>
	<div>
		normal_video: 
		<video muted controls autoplay playsInline style="width: 500px;" id="normal_video" src="/GetVideoBinary?videoType=normal_video"></video>
	</div>
	<div>
		moov_moved_video: 
		<video muted controls autoplay playsInline style="width: 500px;" id="moov_moved_video" src="/GetVideoBinary?videoType=moov_moved_video"></video>
	</div>
	<div>
		fragmented_video:
		<video muted controls autoplay playsInline style="width: 500px;" id="fragmented_video"></video>
	</div>
</body>
</html> 

index.html 內容非常簡單,頁面中有三個 <video>, 分別用來播放上述的三個 MP4 影片,
normal_video.mp4 和 moov_moved_video.mp4 是直接設定影片 url 在 <video> 的 src 屬性上,
而 fragmented_video 則是呼叫了 "js/fmp4Player.js" 中的 readAndPlayFMP4 函式來載入影片。

最後我們來看一下 "js/fmp4Player.js",
/readableStreamTest/js/fmp4Player.js:

function readAndPlayFMP4(videoPlayerId){
	//使用 fetch 讀取影片二進位流
	fetch('/GetVideoBinary',
		{
			method : 'POST',
			headers : {
				'Content-Type': 'application/x-www-form-urlencoded'
			},
			body: "videoType=" + videoPlayerId
		}).then(function(response) {
			var videoPlayer = document.getElementById(videoPlayerId);
	
			var mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
	
			if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
				var mediaSource = new MediaSource();
				videoPlayer.src = URL.createObjectURL(mediaSource);
				mediaSource.addEventListener('sourceopen', sourceOpen);
			} else {
				console.error('Unsupported MIME type or codec: ', mimeCodec);
			}
	
			function sourceOpen(_) {
				//console.log(this.readyState); // open
				var mediaSource = this;
				var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
				
				var dataSetReductionStatus = []; // 用來顯示目前 dataset 被拆分的狀態以利觀察 Record current data reduction status for debug
				
				var reader = response.body.getReader();
				readNextDataset();
				
				function readNextDataset(){
					// 遞迴地讀取影片的二進位流資料 Read video data recursively
					return reader.read().then(function(response){
						if (response.done) {
							//讀取完畢,已沒有可讀取的影片二進位流資料,告訴 mediaSource 所有影片資料已讀取完畢
							mediaSource.endOfStream(); //tell media source that it reaches the end of stream, all data from steam has been send.
							console.log("All video data handling complete");
							return Promise.resolve();
						}else{
							// response.value is Uint8Array trype
							dataSetReductionStatus.unshift(response.value.byteLength);
							//把這一波讀到的影片資料 (response.value) 交由 handleData() 處理
							return handleData(response.value).then(function(){
								// 這波讀取的影片資料均已處理完畢,開始讀取下一波影片資料
								console.log("A set of data (total: " + response.value.byteLength + ") handling complete");
								readNextDataset();
							});
						}
					});
				}
				
				function handleData(data){
					return new Promise(function(resolve, reject){
						// 在 sourceBuffer.appendBuffer() 之後,必須等到 updateend 事件發生後才能再進行下一次的 sourceBuffer.appendBuffer()
						// Need to wait for "updateend" event to do next sourceBuffer.appendBuffer()
						sourceBuffer.addEventListener('updateend', sourceBufferUpdateendListener);
						function sourceBufferUpdateendListener(_) {
							console.log("updateend");
							dataSetReductionStatus.shift();
							sourceBuffer.removeEventListener('updateend', sourceBufferUpdateendListener);
							resolve();
						}
						
						try {
							// 印出 dataSetReductionStatus 觀察影片資料拆分的狀態
							console.log("dataSetReductionStatus: <-- " + dataSetReductionStatus + " <--");
							console.log("Try to append: " + data.byteLength);						
							sourceBuffer.appendBuffer(data); // sourceBuffer.appendBuffer() 有可能得到 "QuotaExceededError" 例外 May catch 'QuotaExceededError' exception
							console.log(data.byteLength + " data was appended.");
					    } catch (e) {
					    	sourceBuffer.removeEventListener('updateend', sourceBufferUpdateendListener);
					        if (e.name !== 'QuotaExceededError') {
					        	throw e;
				        	}
					        console.log("QuotaExceededError!");
				        	// 嘗試進行拆分影片資料分別處理,例如此例把影片拆分成總大小(byte) 80% 和 20% 大小的兩個部份 Split data and handle each reduced part of data, for example: reduction: 80%.
							var reduction = data.byteLength * 0.8;
							
							if (data.slice(0, reduction).byteLength == 0){
								// 如果拆分後 80% 那部份大小為 0 的話,就不拆分,還是處理 100% 的影片資料,但是等一段時間例如 1 秒後再處理
								// 以遞迴方式處理資料
								console.log("Try to append " + data.byteLength + " after waiting for 1 second.");
								setTimeout(function(){
					        		handleData(data).then(resolve);
								}, 1000);
							}else{
								dataSetReductionStatus.shift();
								dataSetReductionStatus.unshift(data.slice(reduction).byteLength);
								dataSetReductionStatus.unshift(data.slice(0, reduction).byteLength);
								
								// 各別以遞迴方式處理拆分後的資料
								// Handle first part of reduced data
								console.log("Split data");
								handleData(data.slice(0, reduction))
								.then(function(){
									// Handle second part of reduced data
									return handleData(data.slice(reduction));
								})
								.then(resolve);	
							}
					    }
					});
				}
			};		
	});
}

相關的說明已寫在程式碼的註解當中,這邊再做一些說明:

  1. 程式使用了 Javascript 的 fetch() 來進行影片資料的讀取,跟 XMLHttpRequest 不同,
    XMLHttpRequest 只能在讀取完全部資料後進行資料處理,
    但 fetch() 可以使用在 callback 函式中得到的 response 用 response.body.getReader() 來得到
    ReadableStream Reader,可以多次地使用 ReadableStream Reader 的 read() 來讀取部份資料,不用一次全部讀完。
  2. 當進行 sourceBuffer.appendBuffer() 把一段影片資料放到 sourceBuffer 時,
    可能會因為 Buffer 已滿而碰到 QuotaExceededError,
    這時程式會進行資料拆分,例如以 byte 為單位,先處理前 80% 的資料,再處理後 20% 的資料,
    而如果處理各拆分的資料還是碰到 QuotaExceededError 的話,就再繼續遞迴地進行拆分處理,
    最後如果要被處理的資料只有 1 byte,則不再進行拆分,當碰到 QuotaExceededError 時,
    則等待 1 秒鐘讓 Buffer 有空間時再遞迴地進行處理。
  3. 程式使用了 Promise 來處理非同步的影片處理,
    handleData() 會回應一個 Promise 物件,
    當它被 resolve() 時代表一段由 readNextDataset() 讀取到的影片資料被處理完畢,
    因為在處理影片時,有可能會碰到 QuotaExceededError 而需要進行影片資料的拆分,
    還有在 sourceBuffer.appendBuffer() 時需要等待 updateend 事件發生後才行進行下一次的 sourceBuffer.appendBuffer(),
    這些都是非同步的行為,所以使用 Promise 可以較好的處理流程。
  4. 程式中為了方便地觀察每一次由 readNextDataset() 讀取到的影片資料,
    在 sourceBuffer.appendBuffer() 時,資料是被拆分成何種狀態,
    所以建立了一個名為 dataSetReductionStatus 的 Arry 物件來觀察,
    例如一開始要處理的資料是 1024 byte 時,
    dataSetReductionStatus = [1024]
    拆分成 819 byte 和 205 byte 時,則是
    dataSetReductionStatus = [819, 205]。


最後下面是程式執行的樣子,
normal_video.mp4 會等影片全部讀取完才會開始播放,
moov_moved_video.mp4 會在讀到 moov 時就會開始播放,
fragmented_video.mp4 會在影片檔一邊下載一邊一段段的播放各個 fragment (mdat),
連覽器的 console 則是會列印出一些 debug 訊息。



原始碼分享:

  1. readableStreamTest.zip (內含範例使用的三個 MP4 影片檔)

資原分享:

  1. Bento4-SDK-1-6-0-639.x86_64-microsoft-win32.zip
  2. ffmpeg.zip
  3. 在這邊可以免費下載容量較大片長較長的影片做測試用:
    https://www.learningcontainer.com/mp4-sample-video-files-download/
    我範例使用的影片是:
    https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/Sample-Video-File-For-Testing.mp4

參考資料:

  1. 5分钟入门MP4文件格式
  2. MediaSource错误:Failed to execute"endOfStream" on "MediaSource": The MediaSource‘s readyState is not “open”.
  3. Exceeding the buffering quota
  4. 4.22 mov, mp4, ismv
  5. JavaScript Promise 全介紹

2016年9月27日 星期二

使用Java製作GIF動畫 - AnimatedGifEncoder

今天要來介紹一個對於用Java生成動態GIF特別好用的類別, AnimatedGifEncoder,
由Kevin Weiner所撰寫,

下面以一個簡單的例子來演示如何使用。

需求如下:

  1. 寫一個servlet,其接到request後可輸出一個GIF動畫。
  2. 在一個htm內用<img>的src接接看測試

實作的檔案結構如下:

各檔內容如下(AnimatedGifEncoder.java就不介紹了):

  1. index.html
    <!DOCTYPE html>
    <html>
        <head>
            <title>Animated Gif Test</title>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <style>
                /* 為img加上背景色,測試透明GIF動畫 */
                img {
                    background-color: black;
                }
            </style>
        </head>
        <body>
            <div>Animated Gif Test</div>
            <!-- 測試GIF動畫 -->
            <img src="AnimatedGif.do" />
        </body>
    </html>
  2. AnimatedGif.java
    /*
     * To change this license header, choose License Headers in Project Properties.
     * To change this template file, choose Tools | Templates
     * and open the template in the editor.
     */
    package servlet;
    
    import java.awt.Color;
    import java.awt.Graphics2D;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.OutputStream;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import tools.AnimatedGifEncoder;
    
    @WebServlet(name = "AnimatedGif", urlPatterns = {"/AnimatedGif.do"})
    public class AnimatedGif extends HttpServlet {
    
        /**
         * Processes requests for both HTTP <code>GET</code> and <code>POST</code>
         * methods.
         *
         * @param request servlet request
         * @param response servlet response
         * @throws ServletException if a servlet-specific error occurs
         * @throws IOException if an I/O error occurs
         */
        protected void processRequest(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            OutputStream o = response.getOutputStream();
            response.setContentType("image/gif"); //設定-- header -- ContentType
            
            Color transparentColor = Color.WHITE;
            
            AnimatedGifEncoder encoder = new AnimatedGifEncoder();
            encoder.setTransparent(transparentColor); //設定透明色
            encoder.setDelay(100);  //設定frame之間的間隔 (毫秒)
            encoder.setRepeat(0);  //設定重覆迴圈次數(不設就是不重覆), 0:無限, 1:重覆一次...
            encoder.start(o);      //綁定OutputStream  
            
            //產生畫布
            BufferedImage generatedImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
            //產生繪圖工具
            Graphics2D graphics2D = generatedImage.createGraphics();
            for (int i = 0; i < 10 ; i++){
                //畫底色
                graphics2D.setColor(transparentColor);
                graphics2D.fillRect(0, 0, generatedImage.getWidth(), generatedImage.getHeight());
                //畫圓圈
                graphics2D.setColor(Color.RED);
                graphics2D.drawOval(i*3, 25, 50, 50);
                //加到GIF動畫frame中
                encoder.addFrame(generatedImage);
            }
            graphics2D.dispose(); //釋放資源
     encoder.finish();  //GIF動畫結束
            //串流輸出
            o.flush();
            o.close();
        }
    
        // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
        /**
         * Handles the HTTP <code>GET</code> method.
         *
         * @param request servlet request
         * @param response servlet response
         * @throws ServletException if a servlet-specific error occurs
         * @throws IOException if an I/O error occurs
         */
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            processRequest(request, response);
        }
    
        /**
         * Handles the HTTP <code>POST</code> method.
         *
         * @param request servlet request
         * @param response servlet response
         * @throws ServletException if a servlet-specific error occurs
         * @throws IOException if an I/O error occurs
         */
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            processRequest(request, response);
        }
    
        /**
         * Returns a short description of the servlet.
         *
         * @return a String containing servlet description
         */
        @Override
        public String getServletInfo() {
            return "Short description";
        }// </editor-fold>
    
    }
    
    
    
成果展示:

原始碼下載:
AnimatedGifEncoderTest.7z

參考資料:

  1. Java – How To Overlay One Image Over Another Using Graphics2D [Tutorial]
  2. 在Java應用程序中創建圖像的方法和技巧
  3. Is there a way to create one Gif image from multiple images in Java? [closed]
  4. 在Java中使用AnimatedGifEncoder生成GIF動畫
  5. [Android] AnimatedGifEncoder
  6. java将gif动态图片分开展示源代码简单示例

2016年8月20日 星期六

使用Ajax上傳檔案 - (Javascript, JQuery or AngularJS) + FormData

在以前我們要在網頁中傳送檔案資料時,通常會需要設計一個html Form,並設定Form的encype=multipart/form-data和準備一個input type="file",在User按下submit按鈕後,跟據Form
所設定的action="URL",將整個頁面request移動到action所指定的地方,例如Servlet,
等處理完後在將User導至其他網頁頁面。

但有沒有可能不要讓User被導到其他頁面,留在原頁面就完成檔案資訊的傳送呢?
答案是可以的,這邊就要利用新的javascript類別FormData和Ajax的技術來達到Ajax傳
送檔案(也可順便傳遞其他input資料)資料。


在這邊我們要利用Netbeans、Servlet3.0、Tomcat 8來實做我們的範例,
實現了三個版本的檔案上傳:Javascript、JQuery、AngularJS
首頁是專案檔案結構,如下圖:

所用的版本JQuery為v2.0.3、AngularJS為v1.5.8,
主要的重要檔案有:

  1. fileUploadAjaxExample.html
    給User上傳檔案的html網頁。
  2. FileUploadAjax.js
    處理檔案資料上傳的Javascript File.
    包含Javascript、JQuery、AngularJS三個版本。
  3. FileUploadAction.java
    用來接收檔案資料的servlet,這裡設定URL patter為/fileUpload.do
  4. web.xml
    設定檔,其中必須在要接收檔案的servlet中設定的tag,
    tag裡可以有以下的子tag設定:
    <location>         檔案存放位置 (使用Part.write(fileName)可以在寫入檔案,但如果fileName
                               為絕對路徑則以絕對路徑為準)
    <max-file-size>  最大檔案size
    <max-request-size>   最大request size  (例如POST的request size)
    <file-size-threshold>
       超過file-size-threshold的檔案request將會以臨時暫存的方式存到硬                                       碟中,預設為0
接下來我以來各別看下每個實作檔案的內容:

2015年7月12日 星期日

JSP/Servlet中web.xml裡的<welcome-file>與Spring MVC的<mvc:resources>

在使用Spring框架時,如果在web.xml裡將所有的請求都交給Spring來處理的話,例如下面這樣:
<servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
<servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
</servlet-mapping>

此時一些靜態資源可能就會因為沒有相應的Url Mapping或Controller來處理而無法回應靜態資源(如html, jpg, css, js等)的請求,這時我們可以有兩種方法來解決:
  1. 在Spring設定檔中,例如dispatcher-servlet.xml,加入如下內容,作用為再交由Spring的url mapping等處理之前,如果是靜態資源就交還給Web應用服務器管理:

  2. <mvc:default-servlet-handler />
    

  3.  在Spring設定檔中,加入以下內容,作用為對於符合"mapping"的靜態資源邏輯路徑(**表示任意子路徑),告知Web應用服務器去"location"的地方查找。例如對於以下設定,邏輯路徑為/resource/img/picture.jpg就會去找/img/picture.jpg:

  4. <mvc:resources location="/" mapping="/resource/**"/>
    
這裡要注意的地方是,如果我們有在web.xml裡設定歡迎頁面,例如像下面這樣:

<welcome-file-list>
        <welcome-file>index.html</welcome-file>
</welcome-file-list>

然後請求Web應用程式的根目錄時,例如 http://XXX.XXX.XXX.XXX/projectName ,根目錄的url請求,即 "/" ,會被Spring所接收處理,假設Spring設定檔中設定如下:

<mvc:resources location="/" mapping="/**"/>

根目錄 "/" 將會與mapping內容符合,並以location的值 "/" 返回給Web應用服務器,Web應用服務器發現為根目錄("/")的請求時,就會根據web.xml裡的歡迎頁面設定查找index.html。

也就是說,如果我們現在把index.html放在 "/html/index.html" 的路徑中,並且想要以http://XXX.XXX.XXX.XXX/projectName 的請求找到index.html的話,以下的設定方法是錯的:

2015年6月30日 星期二

簡易Hibernate入門(NetBeans+Servlet+MySQL)

Hibernate是一個基於資料庫物件化的一個框架(framework),它將資料庫裡的表格(Table)中的每一行紀錄實做成一個Java Bean型式的類別,而其中的屬性對應到了紀錄中的每欄位(Column),且擁有各自的Getter與Setter方法。

利用Hibernate我們可以以物件化的方式操控Database及以物件的方式得到查詢結果。在這裡我們要實作一個簡單的使用了Hibernate的Servlet,利用NetBeans的功能,可以使我們容易地實作Hibernate的許多細節。

一、在這邊我們使用MySQL作為我們的資料庫。首先先在MySQL中建立一個名為guest_database的資料庫,其中建立一個名為guest_list的Table,有三個欄位,分別是id、name、age、birth_date,tyep分別是varchar、varchar、int、date。我們先加入幾條紀錄,如下圖所示:

2015年6月27日 星期六

Ajax、JQuery與Servlet的簡單範例

在這邊要紀錄的是一個簡單的Ajax、JQuery、Servlet聯合應用的簡單範例。
此例client端利用JQuery的Ajax語法來將表單中的參數傳給以Servlet實作的Server端,經由Server端處理過後,以JSON的格式回傳給client端, client端再將JSON格式的回傳值解析後做相應的動作,此例的動作為兩個:跳出視窗顯示回傳值、在html裡的filedset中顯示回傳值。

  1. 首先是作為client端的html內容,index.html:
  2. <!DOCTYPE html>
    
    <html>
    
        <head>
    
            <title>AjaxTest</title>
    
            <meta charset="UTF-8">
    
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
            <!-- 加載Ajax -->
    
            <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
    
            <!-- Ajax的測試Script -->
    
             <script>
    
                 //在網頁加載後,對id=doAjaxBtn的Button設定click的function
    
                $(document).ready(function(){
    
                   $("#doAjaxBtn").click(function(){
    
                       $.ajax({
    
                            type:"POST",                    //指定http參數傳輸格式為POST
    
                            url: "doAjaxServlet.do",        //請求目標的url,可在url內加上GET參數,如 www.xxxx.com?xx=yy&xxx=yyy
    
                            data: $("#formId").serialize(), //要傳給目標的data為id=formId的Form其序列化(serialize)為的值,之
    
                                                            //內含有name的物件value
    
                            dataType: "json",               //目標url處理完後回傳的值之type,此列為一個JSON Object
    
                            //Ajax成功後執行的function,response為回傳的值
    
                            //此範列回傳的JSON Object的內容格式如右所示: {userName:XXX,uswerInterest:[y1,y2,y3,...]}
    
                            success : function(response){
    
                                //在id=ajaxResponse的fieldset中顯示Ajax的回傳值
    
                                $("#ajaxResponse").html("您的大名:"+response.userName+"</br>");
    
                                $("#ajaxResponse").append("您的興趣:</br>");
    
                                var userInterestString = "";
    
                                for(var i = 0 ; i<response.userInterest.length ; i++){
    
                                    $("#ajaxResponse").append(response.userInterest[i]+"</br>");
    
                                    userInterestString += "\n"+response.userInterest[i];
    
                                }
    
                                //用彈出視窗顯示Ajax的回傳值
    
                                alert("UserName:"+response.userName+"\nInterest: "+userInterestString);
    
                            },
    
                            //Ajax失敗後要執行的function,此例為印出錯誤訊息
    
                            error:function(xhr, ajaxOptions, thrownError){
    
                                alert(xhr.status+"\n"+thrownError);
                            }
    
                        });
    
                   });
    
                });  
    
            </script>
    
        </head>
    
        <body>
    
            <div>AjaxTest</div>
    
            <form id="formId">
    
                大名:<input type="text" name="userName">
    
                <input type="checkbox" name="userInterest" value="看書" />看書
    
                <input type="checkbox" name="userInterest" value="遊戲" />遊戲
    
                <input type="checkbox" name="userInterest" value="電影" />電影
    
                <input type="button" id="doAjaxBtn" value="啟動Ajax" />           
    
            </form>
    
            <div id="anotherSection">
    
                <!-- 用來顯示Ajax回傳值的fieldset -->
    
             <fieldset>
    
                 <legend>Response from jQuery Ajax Request</legend>
    
                 <div id="ajaxResponse"></div>
    
             </fieldset>
    
            </div> 
    
        </body>  
    
    </html>
  3. 再來是Server端的Servlet,doAjaxServlet.java:

Struts簡單入門 - 簡易身份認證(使用NetBeans)

Struts是一個基於MVC(Model-View-Control)架構的Java Web框架,今天要以一個簡單的身份認證例子來初步地了解Struts的MVC概念精神。

下圖為一個Struts的簡單原理流程圖,其中在web.xml中會設定什麼樣的url要交由ActionServlet來轉發給處理邏輯(例如 *.do),JSP File相當於View的可視化介面,當JSP File要把表單(Form)中的參數傳給商業邏輯處理時,ActionServlet會去struts-config.xml中尋找對應Form裡指定目標url的Action(相當於Control)和相應要物件實例化Form的Bean類別,ActionForm(相當於Model)。之後在Action中處理商業邏輯,並指定一個轉送目標url,這時就會轉送到struts-config.xml中對應轉送標的JSP或Action等。

我們的範例專案檔案配置如下:

  1. login.jsp                  : 身份認證的輸入畫面。
  2. loginSuccess.jsp     : 認證成功的畫面。
  3. struts-config.xml    : Struts的配置設定。
  4. LoginCheckAction : 處理身份認證商業邏輯的Action。
  5. LoginFormBean     : 用來實例物件Form的Bean類別。



以下為程式碼,相關的解釋都在註解裡: