關於 MVC/Model 2


在 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)組織回應畫面

一個大致的流程示意如下所示:

關於 MVC/Model 2

如果使用基於 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 程式,就可以製造某種形式的攻擊了。

範例程式終究是範例程式,為了集中焦點在功能性的說明上,安全方面的考量通常是少或被忽略的,別直接放到你實際上線的程式之中,永遠不要未經驗 證或過濾處理,就將使用者的請求參數輸入直接輸出至回應的頁面之中。