###### tags: `Servlet&JSP` # MVC & URL ## MVC 在 MVC 中,將 Web 應用程式劃分為模型、畫面與控制器: * M模型(Model)的職責 * 保存應用程式狀態 * 執行應用程式商務邏輯(Business logic) * 只要跟資料庫有關 * 細分成Dao、Bean、Servers 等等 * V畫面(View)的職責 * 提取模型狀態 * 執行呈現邏輯(Presentation logic)組織回應畫面 * C控制器(Controller)的職責 * 接受請求 * 驗證請求 * 判斷要轉發請求給哪個模型 * 判斷要轉發請求給哪個畫面 大致的流程示意如下所示: ![](https://i.imgur.com/lqsiuek.png) 例子:依使用者發送的名稱來提取個別訊息顯示。 ``` 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 程式碼干擾,複雜的情境時MCV各司其責,使得程式更易於維護。 **注意** : 以上只是範例別直接放到你實際上線的程式之中,這個簡單的程式中,由於 JSP 頁面直接將使用者的請求參數輸出,如果使用者懷著惡意輸入了一些不該輸入的東西,例如 JavaScript 程式,就可以製造某種形式的攻擊了。 --- ## URL模式 ### 一個請求 URI 實際上是由三個部份所組成 ``` requestURI = contextPath + servletPath + pathInfo ``` 可以使用 HttpServletRequest 的 getRequestURI() 來取得。 其中 contextPath 是環境路徑(Context path),是容器用來決定該挑選哪個 Web 應用程式的依據(一個容器上可能部署多個Web應用程式),在 Servlet 4.0 之前,環境路徑的設定並沒有標準規範,而是依伺服器實作而有所不同。例如在 Tomcat 9 中,可以設定 context.xml 的 <Context path="xxxx"> 來決定,在 Servlet 4.0 中,可以於 web.xml 中使用 <default-context-path> 來設定預設路徑,然而,容器實作廠商可以覆蓋這個設定。 你可使用 HttpServletRequest 的 getContextPath() 來取得。如果應用程式環境路徑與 Web 伺服器環境根路徑相同,則應用程式環境路徑為空字串,如果不是,則應用程式環境路徑以 / 開頭,不包括 / 結尾。 一旦決定是哪個 Web 應用程式來處理請求,接下來就進行 Servlet 的挑選,Servlet 必須設定 URL 模式(URL pattern),可以設定的格式優先序如下: ### 1.Exact match 明確の對應關係(嚴格匹配) 字串完全吻合且不含 * 號。 例如若 URL 模式設定為/HelloWorld.do。 servletPath為/HelloWorld.do。 ### 2.Path mapping 前置路徑の對應關係 / 開頭但 /* 結尾的 URL 模式。 例如若設定 URL 模式為 /guest/*,則請求 URI 扣去環境路徑的部份若為/guest/test.view 、/guest/home.view 等以 /guest/ 作為開頭的,都會交由該Servlet處理。 servletPath為/guest。 PathInfo為/*查到的東東。(只有Path mapping有PathInfo!) ### 3.Extension mapping 延伸檔名の對應關係 以 *. 開頭的 URL 模式。 例如若 URL 模式設定為 *.view,則所有以 .view 結尾的請求,都會交由該 Servlet 處理。 servletPath為/guest/test.view。 ### 4.Context root 預設の對應關係 僅包括 / 的 URL 模式。 當找不到適合的 URL 模式對應時,就才會使用預設 Servlet。 ![](https://i.imgur.com/SEAvSsb.png) 在最上面的 requestURI 中,servletPath 的部份是指 Servlet 路徑(Servlet path),不包括路徑資訊(Path info)與請求參數(Request parameter)。Servlet 路徑直接對應至URL模式資訊,可使用 HttpServletRequest 的 getServletPath() 來取得,Servlet 路徑基本上是以 / 開頭,但 /* 與空字串的URL模式比對而來的請求除外,在 /* 與空字串的情況下,Servlet 路徑是 ""。 例如若某個請求是根據 /hello.do 對應至 Servlet,則 Servlet 路徑就是 "/hello.do",如果是透過 /servlet/* 對應至 Servlet,則 Servlet 路徑就是 "/servlet",但如果是透過 /* 或空字串,則 Servlet 路徑就是 ""。 requestURI 中 pathInfo 是指路徑資訊(Path info),路徑資訊不包括請求參數,指的是不包括 Context Path 與 Servlet Path 部份的額外路徑資訊。 可使用 HttpServletRequest 的 getPathInfo() 來取得。如果沒有額外路徑資訊,則為 null(延伸對應、預設 Servlet、嚴格匹配的情況下),如果有額外路徑資訊,則是個以 "/" 為開頭的字串。 好累剩下的翻這個後面: https://openhome.cc/Gossip/ServletJSP/URLPattern.html REST 全名 REpresentational State Transfer 直接翻譯:代表性的狀態轉移