在 Web 應用程式的領域中,包括了網頁的呈現與程式碼的行為,網頁的呈現可交由網頁美術人員執行,程式碼的撰寫則由程式設計人員負責,各司其職,為了不在網頁中添 加不必要的程式碼來干擾網頁美術人員的設計,也為了不在程式碼中安插麻煩的字串來輸出 HTML 等內容,Web應用程式世界常使用的架構是 MVC/Model 2。
MVC 指的是 Model/View/Controller, 也就是將 Web 應用程式的組成劃分為模型、畫面與控制器三個角色,最原始的 MVC 定義是指桌面應用程式上的架構,這邊不予探討,Web 應用程式借鏡桌面應用程式 MVC 架構,取其 Model/View/Controller 的職責劃分,並修改流程為適用於 HTTP 請求/回應特性,基本上你也可以稱這個修改後的架構為 MVC,或者是 Model 2(是的!還有一個架構稱之為 Model 1,之後會看到),或併稱為 MVC/Model 2。
許多 Web 開發框架或快速開發框架(像是 Play、Grails 等),都是基於 MVC/Model 2,在前端工程(Front-end Engineering)興盛之後,雖然有許多新的模式名詞出現,然而基本上都是從 MVC/Model 2 變化而來,認識 MVC/Model 2,是認識這些框架或者新模式的基礎。
在 MVC/Model 2 中,將 Web 應用程式劃分為模型、畫面與控制器:
- 控制器(Controller)的職責
- 接受請求
- 驗證請求
- 判斷要轉發請求給哪個模型
- 判斷要轉發請求給哪個畫面
- 模型(Model)的職責
- 保存應用程式狀態
- 執行應用程式商務邏輯(Business logic)
- 畫面(View)的職責
- 提取模型狀態
- 執行呈現邏輯(Presentation logic)組織回應畫面
一個大致的流程示意如下所示:
如果使用基於 Java 技術的 Servlet/JSP 來實現以上的架構,控制器通常是由 Servlet 來擔任,模型則是個 Java 物件,而畫面則由 JSP 來實現。
舉個例子來說,如果你想要依使用者發送的名稱來提取個別訊息顯示,以下是個簡單的 MVC/Model 2 實現:
package cc.openhome;
import java.io.IOException;
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("/hello")
public class HelloServlet extends HttpServlet {
private Hello hello = new Hello();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getParameter("user");
String message = hello.doHello(name);
request.setAttribute("message", message);
request.getRequestDispatcher("hello.jsp").forward(request, response);
}
}
其中 Hello
的職責劃分是模型,根據使用者的 user
請求參數不同,會取得不同的訊息給 message
參考:
package cc.openhome;
import java.util.*;
public class Hello {
private Map<String, String> messages;
public Hello() {
messages = new HashMap<>();
messages.put("caterpillar", "Hello");
messages.put("Justin", "Welcome");
messages.put("momor", "Hi");
}
public String doHello(String user) {
return String.format("%s, %s!", messages.get(user), user);
}
}
依上,如果 user
請求參數是 caterpillar
就會取得 "Hello"
字串,如果是 Justin
,就會取得 "Welcome"
字串,在取得訊息之後,先前擔任控制器的 Servlet 會轉發給畫面:
<!DOCTYPE html>
<html>
<head>
<title>${param.user}</title>
</head>
<body>
<h1>${message}</h1>
</body>
</html>
畫面會取得使用者的訊息,並使用 HTML 來顯示,所以,如果你的請求是:
http://localhost:8080/ServletDemo/hello?user=caterpillar
則你會得到以下的 HTML:
<!DOCTYPE html>
<html>
<head>
<title>caterpillar</title>
</head>
<body>
<h1>Hello, caterpillar!</h1>
</body>
</html>
上面的範例有一些還沒有介紹的特性,不過重點在於,經由適當的職責劃分,控制器與模型不用處理 HTML 的輸出,而 JSP 也不會被 Java 程式 碼干擾,當然,切割呈現邏輯與商務邏輯並非僅是 MVC/Model 2 的作用,在更複雜的情境時,控制器、模型、畫面會各司其責,將會使得程式更易於維護、更不會因為需求變更而作出過大的變動。
最後是一點安全提示,在這個簡單的程式中,由於 JSP 頁面直接將使用者的請求參數輸出,如果使用者懷著惡意輸入了一些不該輸入的東西,例如 JavaScript 程式,就可以製造某種形式的攻擊了。
範例程式終究是範例程式,為了集中焦點在功能性的說明上,安全方面的考量通常是少或被忽略的,別直接放到你實際上線的程式之中,永遠不要未經驗 證或過濾處理,就將使用者的請求參數輸入直接輸出至回應的頁面之中。