Spring MVC -- MVC設計模式(演示4個基於MVC框架的案例)

對於簡單的Java Web項目,咱們的項目僅僅包含幾個jsp頁面,因爲項目比較小,咱們一般能夠經過連接方式進行jsp頁面間的跳轉。css

可是若是是一箇中型或者大型的項目,上面那種方式就會帶來許多維護困難,代碼複用率低等問題。所以,咱們推薦使用MVC模式。html

一 MVC概念

一、什麼是MVC

MVC的全名是Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,是一種軟件設計模式。它是用一種業務邏輯、數據與界面顯示分離的方法來組織代碼,將衆多的業務邏輯彙集到一個部件裏面,在須要改進和個性化定製界面及用戶交互的同時,不須要從新編寫業務邏輯,達到減小編碼的時間。前端

MVC開始是存在於桌面程序中的,M是指業務模型,V是指用戶界面,C則是控制器。vue

使用的MVC的目的:在於將M和V的實現代碼分離,從而使同一個程序可使用不一樣的表現形式。好比Windows系統資源管理器文件夾內容的顯示方式,下面兩張圖中左邊爲詳細信息顯示方式,右邊爲中等圖標顯示方式,文件的內容並無改變,改變的是顯示的方式。無論用戶使用何種類型的顯示方式,文件的內容並無改變,達到M和V分離的目的。java

在網頁當中:react

  • V:即View視圖是指用戶看到並與之交互的界面。好比由html元素組成的網頁界面,或者軟件的客戶端界面。MVC的好處之一在於它能爲應用程序處理不少不一樣的視圖。在視圖中其實沒有真正的處理髮生,它只是做爲一種輸出數據並容許用戶操縱的方式;
  • M:即model模型是指模型表示業務規則。在MVC的三個部件中,模型擁有最多的處理任務。被模型返回的數據是中立的,模型與數據格式無關,這樣一個模型能爲多個視圖提供數據,因爲應用於模型的代碼只需寫一次就能夠被多個視圖重用,因此減小了代碼的重複性;
  • C:即controller控制器是指控制器接受用戶的輸入並調用模型和視圖去完成用戶的需求,控制器自己不輸出任何東西和作任何處理。它只是接收請求並決定調用哪一個模型構件去處理請求,而後再肯定用哪一個視圖來顯示返回的數據;

下圖說明了三者之間的調用關係:程序員

用戶首先在界面中進行人機交互,而後請求發送到控制器,控制器根據請求類型和請求的指令發送到相應的模型,模型能夠與數據庫進行交互,進行增刪改查操做,完成以後,根據業務的邏輯選擇相應的視圖進行顯示,此時用戶得到這次交互的反饋信息,用戶能夠進行下一步交互,如此循環。angularjs

常見的服務器端MVC框架有:Struts、Spring MVC、ASP.NET MVC、Zend Framework、JSF;常見前端MVC框架:vue、angularjs、react、backbone;由MVC演化出了另一些模式如:MVP、MVVM。web

注意:咱們應該避免用戶經過瀏覽器直接訪問jsp頁面。數據庫

二、MVC舉例一(jsp+servlet+javabean)

最典型的MVC就是jsp+servlet+javabean模式:

  • Serlvet做爲控制器,用來接收用戶提交的請求,而後獲取請求中的數據,將之轉換爲業務模型須要的數據模型,而後調用業務模型相應的業務方法進行更新(這一塊也就是Model層所作的事情),同時根據業務執行結果來選擇要返回的視圖;
  • JavaBean做爲模型,既能夠做爲數據模型來封裝業務數據(對應實體類),又能夠做爲業務邏輯模型來包含應用的業務操做(對應Action類)。其中,數據模型用來存儲或傳遞業務數據,而業務邏輯模型接收到控制器傳過來的模型更新請求後,執行特定的業務邏輯處理,而後返回相應的執行結果;實踐中會採用一個實體類來持有模型狀態,並將業務邏輯放到一個Action類中。
  • JSP做爲表現層,負責提供頁面爲用戶展現數據,提供相應的表單(Form)來用於用戶的請求,並在適當的時候(點擊按鈕)向控制器發出請求來請求模型進行更新;

每一個控制器中能夠定義多個請求URL,每一個用戶請求都發送給控制器,請求中的URL標識出對應的Action。Action表明了Web應用能夠執行的一個操做。一個提供了Action的Java對象稱爲Action對象。一個Action類型能夠支持多個Action(在Spring MVC以及Struts2中),或一個Action(在struts1中)。

注意:Struts一、Spring MVC和JavaServer Fces使用Servlet做爲控制器,而Struts2使用Filter做爲控制器。

三、MVC舉例二(Struts2框架)

Struts2框架:Struts2是基於MVC的輕量級的web應用框架。Struts2的應用範圍是Web應用,注重將Web應用領域的平常工做和常見問題抽象化,提供一個平臺幫助快速的完成Web應用開發。基於Struts2開發的Web應用天然就能實現MVC,Struts2着力於在MVC的各個部分爲開發提供相應幫助。

下面經過代碼來簡單解釋一下(這裏只是簡單使用):

Login.html(位於WebContent下)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <form id="form1" name="form1" action="/action/Login.action" method="post">
        登陸<br>
        用戶名:<input name="username" type="text"><br>
        密碼:<input name="password" type="password"><br>
        <input type="submit" value="登陸">
    </form>
</body>
</html>

LoginAction.Java(位於包com.dc365.s2下)

    if(username.equals("1") && password.equals("1")) {
            return "Success";
    }
    return "Error";

struts.xml(位於src下)

<struts>
    <package name="default" namespcase="/action" extends="struts-default">
        <action name="Login" class="com.dc365.s2.LoginAction">
            <result name="Success">Success.jsp</result>
            <result name="Error">Error.jsp</result>
        </action>
    </package>
</struts>

注意:除了上面代碼,還須要在web.xml裏面配置前端過濾器FilterDispatcher。

用戶首先在Login.html中輸入用戶名和密碼,點擊登錄,用戶請求(請求路徑爲/action/Login.action)首先到達前端控制器FilterDispatcher,FilterDispatcher根據用戶請求的URL和配置在struts.xml找到對應的Login,而後根據對應的class的路徑進入相應的login.Java,在這裏判斷以後,返回success或error,而後根據struts.xml中的result值,指向相應的jsp頁面。

  • 控制器——filterdispatcher:從上面這張圖來看,用戶請求首先到達前端控制器FilterDispatcher。FilterDispatcher負責根據用戶提交的URL和struts.xml中的配置,來選擇合適的動做(Action),讓這個Action來處理用戶的請求。FilterDispatcher實際上是一個過濾器(Filter,servlet規範中的一種web組件),它是Struts2核心包裏已經作好的類,不須要咱們去開發,只是要在項目的web.xml中配置一下便可。FilterDispatcher體現了J2EE核心設計模式中的前端控制器模式。
  • 動做——Action:在用戶請求通過FilterDispatcher以後,被分發到了合適的動做Action對象。Action負責把用戶請求中的參數組裝成合適的數據模型,並調用相應的業務邏輯進行真正的功能處理,獲取下一個視圖展現所須要的數據。Struts2的Action,相比於別的web框架的動做處理,它實現了與Servlet API的解耦,使得Action裏面不須要再直接去引用和使用HttpServletRequest與HttpServletResponse等接口。於是使得Action的單元測試更加簡單,並且強大的類型轉換也使得咱們少作了不少重複的工做。
  • 視圖——Result:視圖結果用來把動做中獲取到的數據展示給用戶。在Struts2中有多種優秀的結果展現方式,常規的jsp,模板freemarker、velocity,還有各類其它專業的展現方式,如圖表jfreechart、報表JasperReports、將XML轉化爲HTML的XSLT等等。並且各類視圖結果在同一個工程裏面能夠混合出現。

四、MVC優勢

  • 耦合性低:視圖層和業務層分離,這樣就容許更改視圖層代碼而不用從新編譯模型和控制器代碼,一樣,一個應用的業務流程或者業務規則的改變只須要改動MVC的模型層便可。由於模型與控制器和視圖相分離,因此很容易改變應用程序的數據層和業務規則;
  • 重用性高:MVC模式容許使用各類不一樣樣式的視圖來訪問同一個服務器端的代碼,由於多個視圖能共享一個模型,它包括任何WEB(HTTP)瀏覽器或者無線瀏覽器(wap),好比,用戶能夠經過電腦也可經過手機來訂購某樣產品,雖然訂購的方式不同,但處理訂購產品的方式是同樣的。因爲模型返回的數據沒有進行格式化,因此一樣的構件能被不一樣的界面使用;
  • 部署快,生命週期成本低:MVC使開發和維護用戶接口的技術含量下降。使用MVC模式使開發時間獲得至關大的縮減,它使程序員(Java開發人員)集中精力於業務邏輯,界面程序員(HTML和JSP開發人員)集中精力於表現形式上;
  • 可維護性高:分離視圖層和業務邏輯層也使得WEB應用更易於維護和修改;

五、MVC缺點

  • 徹底理解MVC比較複雜:因爲MVC模式提出的時間不長,加上同窗們的實踐經驗不足,因此徹底理解並掌握MVC不是一個很容易的過程;
  • 調試困難:由於模型和視圖要嚴格的分離,這樣也給調試應用程序帶來了必定的困難,每一個構件在使用以前都須要通過完全的測試;
  • 不適合小型,中等規模的應用程序:在一箇中小型的應用程序中,強制性的使用MVC進行開發,每每會花費大量時間,而且不能體現MVC的優點,同時會使開發變得繁瑣;
  • 增長系統結構和實現的複雜性:對於簡單的界面,嚴格遵循MVC,使模型、視圖與控制器分離,會增長結構的複雜性,並可能產生過多的更新操做,下降運行效率;
  • 視圖與控制器間的過於緊密的鏈接而且下降了視圖對模型數據的訪問:視圖與控制器是相互分離,但倒是聯繫緊密的部件,視圖沒有控制器的存在,其應用是頗有限的,反之亦然,這樣就妨礙了他們的獨立重用;依據模型操做接口的不一樣,視圖可能須要屢次調用才能得到足夠的顯示數據。對未變化數據的沒必要要的頻繁訪問,也將損害操做性能;

六、具體案例

接下來咱們將會演示基於MVC框架的4個不一樣的示例:

  • 第一個採用Servlet做爲控制器;
  • 第二個採用Filter做爲控制器;
  • 第三個引入校驗器組件來校驗用戶輸入數據的合法性;
  • 第四個採用了一個自制的依賴注入器。在實際的應用中,咱們應該使用Spring。

二 MVC案例(Serlvet做爲控制器)

建立一個名爲appdesign1的Dynamic Web Project項目,Servlet版本選擇3.0,其功能設定爲輸入一個產品信息。具體爲:

  • 用戶填寫產品表單並提交;
  • 保存產品並展現一個完成頁面,顯示已經保存的產品信息;

示例應用支持以下兩個action(每一個action對應一個URL):

  • 展現」添加產品「表單,其對應的URL包含字符串input-product;
  • 保存產品並返回完成界面,對應的URL包含字符串save-product;

示例應用由以下組件組成:

  •  一個Product類,做爲product的領域對象;
  • 一個ProductForm類,封裝了HTML表單的輸入項;
  • 一個ControllerServlet,本示例應用的控制器;
  • 一個SaveProdtcuAction類;
  • 兩個jsp頁面(ProductForm.jsp和ProductDetails.jsp)做爲視圖;
  • 一個CSS文件,定義了兩個jsp頁面的顯示風格。

示例應用目錄結構以下:

注意:因爲咱們採用的是Servlet3.0版本,web.xml能夠不須要,具體能夠參考博客Servlet2.5版本和Servlet3.0版本

項目右鍵屬性、部署路徑設置以下:

一、Product類

Product類是一個封裝了產品信息的JavaBean。Product類包含三個屬性:name,description和price:

package appdesign1.model;

import java.io.Serializable;
import java.math.BigDecimal;

public class Product implements Serializable {

    private static final long serialVersionUID = 748392348L;
    private String name;
    private String description;
    private BigDecimal price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}

Product類實現了java.io.Serializable接口,其實例能夠安全地將數據保存到HttpSession中。根據Serializable的要求,Product類實現了一個serialVersionUID 屬性。

二、ProductForm表單類

表單類與HTML表單相對應,是後者在服務器的表明。ProductForm類看上去同Product類類似,這就引出一個問題:ProductForm類是否有存在的必要:

package appdesign1.form;

public class ProductForm {
    private String name;
    private String description;
    private String price;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getPrice() {
        return price;
    }
    public void setPrice(String price) {
        this.price = price;
    }
}

實際上,經過表單對象能夠將ServletRequest中的表單信息傳遞給其它組件,好比校驗器Validator(後面會介紹,主要用於檢查表單輸入數據是否合法)。若是不使用表單對象,則應將ServletRequest傳遞給其它組件,然而ServletRequest是一個Servlet層的對象,是不該當暴露給應用的其它層。

另外一個緣由是,當數據校驗失敗時,表單對象將用於保存和顯示用戶在原始表單上的輸入。

注意:大部分狀況下,一個表單類不須要事先Serializable接口,由於表單對象不多保存在HttpSession中。

三、ControllerServlet類

ContrlooerServlet類繼承自javax.servlet.http.HttpServlet類。其doGet()和doPost()方法最終調用process()方法,該方法是整個Servlet控制器的核心。

可能有人好奇,爲什麼Servlet控制器命名爲ControllerServlet,實際上,這裏聽從了一個約定:全部Servlet的類名稱都帶有Servlet後綴。

package appdesign1.controller;
import java.io.IOException;

import javax.servlet.RequestDispatcher;
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 appdesign1.action.SaveProductAction;
import appdesign1.form.ProductForm;
import appdesign1.model.Product;
import java.math.BigDecimal;
//Servlet3.0使用註解指定訪問Servlet的URL
@WebServlet(name = "ControllerServlet", urlPatterns = {
        "/input-product", "/save-product" })
public class ControllerServlet extends HttpServlet {

    private static final long serialVersionUID = 1579L;

    @Override
    public void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws IOException, ServletException {
        process(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws IOException, ServletException {
        process(request, response);
    }

    private void process(HttpServletRequest request,
            HttpServletResponse response)
            throws IOException, ServletException {

        String uri = request.getRequestURI();
        /*
         * uri is in this form: /contextName/resourceName,
         * for example: /appdesign1/input-product.
         * However, in the event of a default context, the
         * context name is empty, and uri has this form
         * /resourceName, e.g.: /input-product
         */
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        
        // execute an action  根據不一樣的uri執行不一樣的action
        String dispatchUrl = null;
        if ("input-product".equals(action)) {
            // no action class, just forward
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if ("save-product".equals(action)) {
            // create form 建立一個表單對象、保存表單信息
            ProductForm productForm = new ProductForm();
            // populate action properties
            productForm.setName(request.getParameter("name"));
            productForm.setDescription(
                    request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));

            // create model 建立一個Model類
            Product product = new Product();
            product.setName(productForm.getName());
            product.setDescription(productForm.getDescription());
            try {
                product.setPrice(new BigDecimal(productForm.getPrice()));
            } catch (NumberFormatException e) {
            }
            // execute action method     保存表單      
            SaveProductAction saveProductAction =
                    new SaveProductAction();
            saveProductAction.save(product);

            // store model in a scope variable for the view    
            request.setAttribute("product", product);
            dispatchUrl = "/jsp/ProductDetails.jsp";
        }

        //請求轉發
        if (dispatchUrl != null) {
            RequestDispatcher rd =
                    request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        }
    }
}

ControllerServlet的process()方法處理全部輸入請求。首先是獲取URI和action名稱:

        String uri = request.getRequestURI();
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);

在本示例中,action值只會是input-product或sava-product。

接着,當action值爲sava-product,process()方法執行以下步驟:

  • 建立並更根據請求參數建立一個ProductForm表單對象。save-product操做涉及3個屬性:name,description、price。而後建立一個product對象,並經過表單對象設置相應屬性;
  • 執行鍼對product對象的業務邏輯,保存表單;
  • 轉發請求到視圖(jsp頁面),顯示輸入的表單信息。

process()方法中判斷action的if代碼塊以下:

  if ("input-product".equals(action)) {
            // no action class, just forward
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if ("save-product".equals(action)) {
            ....
    }            

對於input-product,無需任何操做;而針對save-product,則建立一個ProductForm對象和Product對象,並將前者的屬性值複製到後者。

再次,process()方法實例化SavaProductAction類,並調用其save()方法:

 // create form 建立一個表單對象、保存表單信息
            ProductForm productForm = new ProductForm();
            // populate action properties
            productForm.setName(request.getParameter("name"));
            productForm.setDescription(
                    request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));

            // create model 建立一個Model類
            Product product = new Product();
            product.setName(productForm.getName());
            product.setDescription(productForm.getDescription());
            try {
                product.setPrice(new BigDecimal(productForm.getPrice()));
            } catch (NumberFormatException e) {
            }
            // execute action method     保存表單      
            SaveProductAction saveProductAction =
                    new SaveProductAction();
            saveProductAction.save(product);

而後,將Product對象放入HttpServletRequest對象中,以便對應的視圖能夠訪問到:

            // store model in a scope variable for the view    
            request.setAttribute("product", product);
            dispatchUrl = "/jsp/ProductDetails.jsp";

最後,process()方法轉到視圖,若是action是input-product,則轉到ProductForm.jsp頁面,不然轉到ProductDetails.jsp頁面:

        //請求轉發
        if (dispatchUrl != null) {
            RequestDispatcher rd =
                    request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        }

四、Action類

這個應用這有一個action類,負責將一個product對象持久化,例如數據庫。這個action類名爲SaveProductAction:

package appdesign1.action;

import appdesign1.model.Product;

public class SaveProductAction {
    public void save(Product product) {
        // insert Product to the database
    }
}

在這個示例中,SaveProductAction類的save()方法是一個空實現。

五、視圖

示例應用包含兩個jsp頁面。第一個頁面ProductForm.jsp對應input-product操做,第二個頁面ProductDetails.jsp對應sava-product操做。

ProductForm.jsp:

<!DOCTYPE html>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<form method="post" action="save-product">
    <h1>Add Product 
        <span>Please use this form to enter product details</span>
    </h1>
    <label>
        <span>Product Name :</span>
        <input id="name" type="text" name="name" 
            placeholder="The complete product name"/>
    </label>
    <label>
        <span>Description :</span>
        <input id="description" type="text" name="description" 
            placeholder="Product description"/>
    </label>
    <label>
        <span>Price :</span>
        <input id="price" name="price" type="number" step="any"
            placeholder="Product price in #.## format"/>
    </label> 
    <label>
        <span>&nbsp;</span> 
        <input type="submit"/> 
    </label> 
</form>
</body>
</html>

注意:不要用HTML table標籤來佈局表單,使用CSS。

ProductDetails.jsp:

<!DOCTYPE html>
<html>
<head>
<title>Save Product</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<div id="global">
    <h4>The product has been saved.</h4>
    <p>
        <h5>Details:</h5>
        Product Name: ${product.name}<br/>
        Description: ${product.description}<br/>
        Price: $${product.price}
    </p>
</div>
</body>
</html>

ProductForm頁面包含了一個HTML表單。ProductDetails頁面經過EL表達式語言訪問HttpServletRequest所包含的product對象。

此外,該實例存在一個問題,即用戶能夠直接經過瀏覽器訪問這兩個jsp頁面,咱們能夠經過如下方式避免這種直接訪問:

  • 將jsp頁面都放在WEB-INF目錄下。WEB-INF目錄下的任何文件和子目錄都受保護、沒法經過瀏覽器直接訪問,但控制器仍然能夠轉發請求到這些頁面;
  • 利用一個servlet filter過濾jsp頁面;
  • 在部署描述符中爲jsp壓面增長安全限制。這種方式相對簡單,無需編寫filter代碼;

main.css:

form {
    margin-left:auto;
    margin-right:auto;
    max-width: 450px;
    background: palegreen;
    padding: 25px 15px 25px 10px;
    border:1px solid #dedede;
    font: 12px Arial;
}
h1 {
    padding: 20px;
    display: block;
    border-bottom:1px solid grey;
    margin: -20px 0px 20px 0px;
    color: mediumpurple;
}
h1>span {
    display: block;
    font-size: 13px;
}
label {
    display: block;
}
label>span {
    float: left;
    width: 20%;
    text-align: right;
    margin: 14px;
    color: mediumpurple;
    font-weight:bold;
}
input[type="text"], input[type="number"] {
    border: 1px solid #dedede;
    height: 30px;
    width: 70%;
    font-size: 12px;
    border-radius: 3px;
    margin: 5px;
}
input[type="submit"] {
    background: mediumseagreen;
    font-weight: bold;
    border: none;
    padding: 8px 20px 8px 20px;
    color: black;
    border-radius: 5px;
    cursor: pointer;
    margin-left:4px;
}
input[type="submit"]:hover {
    background: red;
    color: yellow;
}

六、測試應用

將項目部署到tomcat服務器,而後啓動服務器,假設示例應用運行在本機的8000端口上,則能夠經過以下URL訪問應用:

http://localhost:8008/appdesign1/input-product

完成表單輸入後,表單提交到以下服務器URL上:

http://localhost:8008/appdesign1/save-product

三 MVC案例(Filter做爲控制器)

雖然Servlet是MVC框架中最多見的控制器,可是過濾器也能夠充當控制器。Struts2就是使用過濾器做爲控制器,是由於該過濾波器也可用於提供靜態頁面。

Filter是在Servlet 2.3以後增長的新功能,當須要限制用戶訪問某些資源或者在處理請求時提早處理某些資源的時候,就可使用過濾器完成。
過濾器是以一種組件的形式綁定到WEB應用程序當中的,與其餘的WEB應用程序組件不一樣的是,過濾器是採用了「鏈」的方式進行處理的。
在Servlet中,若是要定義一個過濾器,則直接讓一個類實現javax.servlet.Filter接口便可,此接口定義了三個操做方法:
  • public void init(FilterConfig filterConfig) throws ServletException
  • public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException
  • public void destroy();
FilterChain接口的主要做用是將用戶的請求向下傳遞給其餘的過濾器或者是Servlet:
  • public void doFilter(ServletRequest request,ServletResponse response) throws IOException,ServletException
在FilterChain接口中依然定義了一個一樣的doFilter()方法,這是由於在一個過濾器後面可能存在着另一個過濾器,也多是請求的最終目標(Servlet),這樣就經過FilterChain造成了一個「過濾鏈」的操做,所謂的過濾鏈就相似於生活中玩的擊鼓傳花遊戲 。

注意:過濾器沒有做爲歡迎頁面(即僅僅在瀏覽器地址欄中輸入域名)的權限,僅輸入域名時不會調用過濾器分派器。

下面咱們採用一個名爲FilterDispactcher的過濾器替代appdesign1項目中的Servlet控制器,項目命名爲appdesign2,目錄結構以下:

package appdesign2.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

import appdesign2.action.SaveProductAction;
import appdesign2.form.ProductForm;
import appdesign2.model.Product;
import java.math.BigDecimal;

//Servlet3.0新增了註解的特性,指定過濾器的訪問URL
@WebFilter(filterName = "DispatcherFilter",
        urlPatterns = { "/*" })
public class DispatcherFilter implements Filter {

    //過濾器初始化
    @Override
    public void init(FilterConfig filterConfig)
            throws ServletException {
    }

    //過濾器銷燬
    @Override
    public void destroy() {
         System.out.println("** 過濾器銷燬。");
    }

    //執行過濾操做
    @Override
    public void doFilter(ServletRequest request,
            ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        System.out.println("** 執行doFilter()方法以前。");
        
        HttpServletRequest req = (HttpServletRequest) request;
        String uri = req.getRequestURI();
        /*
         * uri is in this form: /contextName/resourceName, for
         * example /appdesign2/input-product. However, in the
         * case of a default context, the context name is empty,
         * and uri has this form /resourceName, e.g.:
         * /input-product
         */
        // action processing
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        String dispatchUrl = null;
        if ("input-product".equals(action)) {
            // do nothing
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if ("save-product".equals(action)) {
            // create form
            ProductForm productForm = new ProductForm();
            // populate action properties
            productForm.setName(request.getParameter("name"));
            productForm.setDescription(
                    request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));
            
            // create model
            Product product = new Product();
            product.setName(productForm.getName());
            product.setDescription(product.getDescription());
            try {
                product.setPrice(new BigDecimal(productForm.getPrice()));
            } catch (NumberFormatException e) {
            }
            // execute action method
            SaveProductAction saveProductAction = 
                    new SaveProductAction();
            saveProductAction.save(product);
            
            // store model in a scope variable for the view
            request.setAttribute("product", product);
            dispatchUrl = "/jsp/ProductDetails.jsp";
        }
        // forward to a view
        if (dispatchUrl != null) {
            RequestDispatcher rd = request
                    .getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        } else {
            // let static contents pass
            filterChain.doFilter(request, response);
        }
        System.out.println("** 執行doFilter()方法以後。");
    }
}

doFilter()方法的內容同appdesign1中的process()方法。

因爲過濾器的過濾目標是包括靜態內容在內的全部網址,所以,若沒有相應的action,則須要調用filterChain.doFilter();

else {
            // let static contents pass
            filterChain.doFilter(request, response);
        }

 要測試應用,將項目配置到tomcat服務器,啓動服務器,並在瀏覽器輸入以下URL:

http://localhost:8008/appdesign2/input-product

四 表單輸入校驗器

在Web應用執行action時,很重要的一個步驟就是進行輸入校驗。檢驗的內容能夠是簡單的,如檢查一個輸入是否爲空,也能夠是複雜的,如檢驗信用卡號。實際上,由於校驗工做如此重要,Java社區專門發佈了JSR 303 Bean Validation以及JSR 349 Bean Validation 1.1版本,將Java世界的輸入校驗進行標準化。現代的MVC框架一般同時支持編程式和聲明式兩種校驗方式。在編程式中,須要經過編碼進行用戶輸入校驗;而在聲明式中,則須要提供包含校驗規則的XML文檔或者屬性文件。

注意:即便您可使用HTML5或JavaScript執行客戶端輸入校驗,也不要依賴它,由於精明的用戶能夠輕鬆地繞過它。始終執行服務器端輸入驗證!

本節的新應用(appdesign3)擴展自appdesign1,可是多了一個ProductValidator類:

 

一、ProductValidator類

package appdesign3.validator;
import java.util.ArrayList;
import java.util.List;
import appdesign3.form.ProductForm;

//對錶單進行輸入校驗
public class ProductValidator {
    
    public List<String> validate(ProductForm productForm) {
        List<String> errors = new ArrayList<>();
        //商品名不能爲空
        String name = productForm.getName();
        if (name == null || name.trim().isEmpty()) {
            errors.add("Product must have a name");
        }
        //商品價格不能爲空、也不能是非法數字
        String price = productForm.getPrice();
        if (price == null || price.trim().isEmpty()) {
            errors.add("Product must have a price");
        } else {
            try {
                Float.parseFloat(price);
            } catch (NumberFormatException e) {
                errors.add("Invalid price value");
            }
        }
        return errors;
    }
}

注意:ProductValidator類中有一個操做ProductForm對象的validate()方法,確保產品的名字非空,其價格是一個合理的數字。validate()方法返回一個包含錯誤信息的字符串列表,若返回一個空列表,則表示輸入合法。

如今須要讓控制器使用這個校驗器了,ControllerServlet代碼以下:

package appdesign3.controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.RequestDispatcher;
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 appdesign3.action.SaveProductAction;
import appdesign3.form.ProductForm;
import appdesign3.model.Product;
import appdesign3.validator.ProductValidator;
import java.math.BigDecimal;

//Servlet3.0使用註解指定訪問Servlet的URL
@WebServlet(name = "ControllerServlet", urlPatterns = { 
        "/input-product", "/save-product" })
public class ControllerServlet extends HttpServlet {
    
    private static final long serialVersionUID = 98279L;

    @Override
    public void doGet(HttpServletRequest request, 
            HttpServletResponse response)
            throws IOException, ServletException {
        process(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request, 
            HttpServletResponse response)
            throws IOException, ServletException {
        process(request, response);
    }

    private void process(HttpServletRequest request,
            HttpServletResponse response) 
            throws IOException, ServletException {

        String uri = request.getRequestURI();
        /*
         * uri is in this form: /contextName/resourceName, 
         * for example: /appdesign1/input-product. 
         * However, in the case of a default context, the 
         * context name is empty, and uri has this form
         * /resourceName, e.g.: /input-product
         */
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        
        // execute an action  根據不一樣的uri執行不一樣的action
        String dispatchUrl = null;
        if ("input-product".equals(action)) {
            // no action class, there is nothing to be done
            dispatchUrl = "/jsp/ProductForm.jsp";
        } else if ("save-product".equals(action)) {
            // instantiate action class 建立一個表單對象、保存表單信息
            ProductForm productForm = new ProductForm();
            // populate action properties
            productForm.setName(
                    request.getParameter("name"));
            productForm.setDescription(
                    request.getParameter("description"));
            productForm.setPrice(request.getParameter("price"));
            
            // validate ProductForm 表單輸入校驗
            ProductValidator productValidator = new ProductValidator(); List<String> errors = productValidator.validate(productForm); if (errors.isEmpty()) {   //表單輸入正確 // create Product from ProductForm  建立一個Model類
                Product product = new Product();
                product.setName(productForm.getName());
                product.setDescription(
                        productForm.getDescription());
                product.setPrice(new BigDecimal(productForm.getPrice()));
                
                // no validation error, execute action method 保存表單   
                SaveProductAction saveProductAction = new 
                        SaveProductAction();
                saveProductAction.save(product);
                
                // store action in a scope variable for the view
                request.setAttribute("product", product);
                dispatchUrl = "/jsp/ProductDetails.jsp";
            } else {         //表單輸入有誤,從新加載該頁面
                request.setAttribute("errors", errors); request.setAttribute("form", productForm); dispatchUrl = "/jsp/ProductForm.jsp"; }
        }

        // forward to a view   請求轉發
        if (dispatchUrl != null) {
            RequestDispatcher rd = 
                    request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        }
    }
}

新版的ControllerServlet類添加了初始化ProductValidator類,並調用了validate()方法的代碼:

 // validate ProductForm 表單輸入校驗
            ProductValidator productValidator = new
                    ProductValidator();
            List<String> errors = 
                    productValidator.validate(productForm);

validate()方法接受一個ProductForm參數,它分裝了輸入到HTML表單的產品信息。若是不用ProductForm,則應將ServletRequest傳遞給校驗器。

若是校驗成功,validate()方法返回一個空列表,在這種狀況下,將建立一個產品並傳遞給SaveProductAction,而後,控制器將Product對象存儲在ServletRequest中,並轉發到ProductDetails.jsp頁面,顯示產品的詳細信息。若是校驗失敗,控制器將錯誤列表和ProductForm對象存儲在ServletRequest中,並返回到ProductForm.jsp中。

 if (errors.isEmpty()) {   //表單輸入正確
                // create Product from ProductForm  建立一個Model類
                Product product = new Product();
                product.setName(productForm.getName());
                product.setDescription(
                        productForm.getDescription());
                product.setPrice(new BigDecimal(productForm.getPrice()));
                
                // no validation error, execute action method 保存表單   
                SaveProductAction saveProductAction = new 
                        SaveProductAction();
                saveProductAction.save(product);
                
                // store action in a scope variable for the view
                request.setAttribute("product", product);
                dispatchUrl = "/jsp/ProductDetails.jsp";
            } else {         //表單輸入有誤,從新加載該頁面
                request.setAttribute("errors", errors);
                request.setAttribute("form", productForm);
                dispatchUrl = "/jsp/ProductForm.jsp";
            }
        }

二、ProductForm.jsp

如今,須要修改appdesign3應用的ProductForm.jsp頁面,使其能夠顯示錯誤信息以及錯誤的輸入:

<!DOCTYPE html>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<form method="post" action="save-product">
    <h1>Add Product 
        <span>Please use this form to enter product details</span>
    </h1> ${empty requestScope.errors? "" : "<p id='errors'>" += "Error(s)!" += "<ul>"} <!--${requestScope.errors.stream().map( x -> "--><li>"+=x+="</li><!--").toList()}--> ${empty requestScope.errors? "" : "</ul></p>"} <label>
        <span>Product Name :</span>
        <input id="name" type="text" name="name" 
            placeholder="The complete product name" value="${form.name}"/>
    </label>
    <label>
        <span>Description :</span>
        <input id="description" type="text" name="description" 
            placeholder="Product description" value="${form.description}"/>
    </label>
    <label>
        <span>Price :</span>
        <input id="price" name="price" type="number" step="any"
            placeholder="Product price in #.## format" value="${form.price}"/>
    </label> 
    <label>
        <span>&nbsp;</span> 
        <input type="submit"/> 
    </label> 
</form>
</body>
</html>

三、測試應用

將項目配置到tomcat服務器,啓動服務器,並在瀏覽器輸入以下URL:

http://localhost:8008/appdesign3/input-product

若產品表單提交了無效數據,頁面將顯示錯誤信息,以下:

五 自制的依賴注入

什麼是依賴注入技術?若是不瞭解的話,能夠參考博客:Spring MVC -- Spring框架入門(IoC和DI)

示例appdesign4使用了一個自制的依賴注入器。在實際的應用中,咱們應該使用Spring。該示例用來生成pdf。它有兩個action:

  • form:沒有action類,只是將請求轉發到用來輸入一些文本的表單;
  • pdf:生成pdf文件並使用PDFAction類,PDFAction類依賴於PDFService類;

該應用的目錄結構以下:

一、PDFAction類和PDFService

PDFAction類:

package action;

import service.PDFService;

public class PDFAction {
    private PDFService pdfService;
    public void setPDFService(PDFService pdfService) {
        this.pdfService = pdfService;
    }
    public void createPDF(String path, String input) {
        pdfService.createPDF(path, input);
    }
}

PDFService類:

package service;

import util.PDFUtil;

public class PDFService {
    public void createPDF(String path, String input) {
        PDFUtil.createDocument(path, input);
    }
}

PDFService使用了PDFUtil類,PDFUtil最終採用了apache pdfbox庫來建立pdf文檔,若是對建立pdf的具體代碼有興趣,能夠進一步查看PDFUtil類。

package util;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;

public class PDFUtil {
    public static void createDocument(String path, String input) {
        PDDocument doc = null;
        try {
            doc = new PDDocument();
            PDFont font = PDType1Font.HELVETICA;
            PDPage page = new PDPage();
            doc.addPage(page);
            float fontSize = 12.0f;

            PDRectangle pageSize = page.getMediaBox();
            float centeredXPosition = (pageSize.getWidth() - fontSize
                    / 1000f) / 2f;
            float stringWidth = font.getStringWidth(input);
 
            PDPageContentStream contentStream = new PDPageContentStream(doc, page);
            contentStream.setFont(font, fontSize);
            contentStream.beginText();
            contentStream.moveTextPositionByAmount(centeredXPosition, 600);
            contentStream.drawString(input);
            contentStream.endText();
            contentStream.close();
            doc.save(path);
            doc.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
View Code

這裏的關鍵在於,PDFAction須要一個PDFService來完成它的工做,換句話說,PDFAction依賴於PDFService。沒有依賴注入,你必須在PDFAction類中實例化PDFService類,這將使PDFAction更不可測試。除此以外,若是須要更改PDFService的實現,必須從新編譯PDFAction。

使用依賴注入,每一個組件都有注入它的依賴項,這使得測試每一個組件更容易。對於在依賴注入環境中的類,必須使其支持注入。一種方法是爲每一個依賴關係建立一個set方法。例如,PDFAction類有一個setPDFService方法,能夠調用它來傳遞PDFService。注入也能夠經過構造方法或者類屬性進行。

一旦全部的類都支持注入,則能夠選擇一個依賴注入框架並將其導入項目。好比Spring框架、Google Guice、Weld和PicoContainer是一些好的選擇。

二、DependencyInjector類

appdesign4應用使用DependencyInjector類來代替依賴注入框架(在實際應用中,咱們應該使用一個合適的框架)。這個類專爲appdesign4應用設計,利用Java的反射機制來實現(不懂的能夠參考博客:Java基礎 -- 深刻理解Java類型信息(Class對象)與反射機制),能夠容易的實例化。一旦實例化,必須調用其start()方法來執行初始哈,使用後,應調其shutdown()方法來釋放資源。在此示例中,start()和shutdown()都沒有實現。

package util;

import action.PDFAction;
import service.PDFService;

public class DependencyInjector {
 
    public void start() {
        // initialization code
    }
 
    public void shutDown() {
        // clean-up code
    }
 
    /*
     * Returns an instance of type. type is of type Class
     * and not String because it's easy to misspell a class name
     */
    public Object getObject(Class type) {
        if (type == PDFService.class) {
            return new PDFService();
        } else if (type == PDFAction.class) {
            PDFService pdfService = (PDFService) 
                    getObject(PDFService.class);
            PDFAction action = new PDFAction();
            action.setPDFService(pdfService);
            return action;
        }
        return null;
    }
}

要從DependencyInjector獲取對象,須要調用其getObject()方法,並傳遞目標類對應的Class對象,DependencyInjector支持兩種類型,即PDFAction和PDFService。例如,要獲取PDFAction實例,你能夠經過傳遞PDFAction.class來調用getObject():

        PDFAction pdfAction = (PDFAction) dependencyInjector
                    .getObject(PDFAction.class);

DependencyInjector(和全部依賴注入框架)的優雅之處在於它返回的對象注入了依賴。若是返回的對象所依賴的對象也有依賴,則所依賴的對象也會注入其自身的依賴。例如,從DependencyInjector獲取的PDFAction已包含PDFService。無需在PDFAction類中本身建立PDFService。

三、ControllerServlet

appdesign4應用的Servlet控制器以下所示。請注意:在其init()方法中實例化DependencyInjector,並在其destroy()方法中調用DependencyInjector的shutdown()方法。Servlet再也不建立它自身的依賴項,相反,它從DependencyInjector獲取這些依賴。

package servlet;

import action.PDFAction;
import java.io.IOException;
import javax.servlet.ReadListener;
import javax.servlet.RequestDispatcher;
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 javax.servlet.http.HttpSession;
import util.DependencyInjector;

//Servlet3.0使用註解指定訪問Servlet的URL
@WebServlet(name = "ControllerServlet", urlPatterns = {
    "/form", "/pdf"})
public class ControllerServlet extends HttpServlet {
private static final long serialVersionUID = 6679L;
    private DependencyInjector dependencyInjector;
 
    @Override
    public void init() {
        //實例化DependencyInjector
        dependencyInjector = new DependencyInjector();
        dependencyInjector.start();
    }
 
    @Override
    public void destroy() {
        //關閉DependencyInjector實例
        dependencyInjector.shutDown();
    }
    protected void process(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        ReadListener r = null;
        String uri = request.getRequestURI();
        /*
         * uri is in this form: /contextName/resourceName,
         * for example: /app10a/product_input.
         * However, in the case of a default context, the
         * context name is empty, and uri has this form
         * /resourceName, e.g.: /product_input
         */
        int lastIndex = uri.lastIndexOf("/");
        String action = uri.substring(lastIndex + 1);
        
        
        if ("form".equals(action)) {
            String dispatchUrl = "/jsp/Form.jsp";
            RequestDispatcher rd = 
                    request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        } else if ("pdf".equals(action)) {
            //建立pdf文檔
            HttpSession session = request.getSession(true);
            String sessionId = session.getId();
            //利用dependencyInjector建立PDFAction對象
            PDFAction pdfAction = (PDFAction) dependencyInjector
                    .getObject(PDFAction.class);
            String text = request.getParameter("text");
            //設置pdf在磁盤上文件路徑E:\tomcat\wtpwebapps\appdesign4\result\sessionId.pdf
            String path = request.getServletContext()
                    .getRealPath("/result/") + sessionId + ".pdf";
            //System.out.println(path);
            //生成pdf文件,保存在path路徑下
            pdfAction.createPDF(path, text);
            
            // redirect to the new pdf
            StringBuilder redirect = new 
                    StringBuilder();
            redirect.append(request.getScheme() + "://");       // http://
            redirect.append("localhost");                       // http://localhost
            int port = request.getLocalPort();
            if (port != 80) {
                redirect.append(":" + port);                   // http://localhost:8008
            }
            String contextPath = request.getContextPath();     // /appdesign4
            if (!"/".equals(contextPath)) {
                redirect.append(contextPath);                  // http://localhost:8008/appdesign4
            }
            redirect.append("/result/" + sessionId + ".pdf"); 
            //System.out.println(redirect.toString());  
            response.sendRedirect(redirect.toString());
        }
    }

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

}

Servlet控制器支持兩種URL模式:

  • form:對於表單模式,Servlet控制器將請求轉發到表單/jsp/Form.jsp;
  • pdf:對於pdf模式,Servlet控制器使用DependencyInjector建立PDFAction對象,並調用其createDocument()方法生成pdf文檔,保存在當前項目的result文件夾下。此方法有兩個參數:文件保存在磁盤上的路徑和文本輸入。全部PDF存儲在應用項目目錄下的result文件夾中,用戶的會話標識符用作文件名,而文本輸入做爲pdf文件的內容;最後,重定向到生成的pdf文件,如下是建立重定向URL並將瀏覽器重定向到新URL的代碼:
     // redirect to the new pdf
                StringBuilder redirect = new 
                        StringBuilder();
                redirect.append(request.getScheme() + "://");       // http://
                redirect.append("localhost");                       // http://localhost
                int port = request.getLocalPort();
                if (port != 80) {
                    redirect.append(":" + port);                   // http://localhost:8008
                }
                String contextPath = request.getContextPath();     // /appdesign4
                if (!"/".equals(contextPath)) {
                    redirect.append(contextPath);                  // http://localhost:8008/appdesign4
                }
                redirect.append("/result/" + sessionId + ".pdf"); 
                //System.out.println(redirect.toString());  
                response.sendRedirect(redirect.toString());

四、PDFActionTest和PdfBoxTest

該應用提供了兩個測試類PDFActionTest和PdfBoxTest,因爲依賴注入器,appdesign4中的每一個組件均可以獨立測試,好比能夠運行PDFActionTest類來測試類的createDocument()方法。

PDFActionTest類:

package test;

import action.PDFAction;
import util.DependencyInjector;

public class PDFActionTest {
    public static void main(String[] args) {
        //建立DependencyInjector對象
        DependencyInjector dependencyInjector = new DependencyInjector();
        dependencyInjector.start();
        //利用DependencyInjector建立PDFAction對象
        PDFAction pdfAction = (PDFAction) dependencyInjector.getObject(
                PDFAction.class);
        //生成pdf文檔
        pdfAction.createPDF("E:/tomcat/wtpwebapps/appdesign4/result/1.pdf", 
                "Testing PDFAction....");
        dependencyInjector.shutDown();
    }
}

 輸出以下:

PdfBoxTest類:

package test;
import util.PDFUtil;

public class PdfBoxTest {
    public static void main(String[] args) {
        PDFUtil.createDocument("E:/tomcat/wtpwebapps/appdesign4/result/2.pdf", 
                "Tod late");
    }
}

輸出以下:

五、視圖

Form.jsp:

<!DOCTYPE html>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<form method="post" action="pdf">
    <h1>Create PDF
        <span>Please use this form to enter the text</span>
    </h1>
    <label>
        <span>Text :</span>
        <input type="text" name="text" 
            placeholder="Text for PDF"/>
    </label>
    <label>
        <span>&nbsp;</span> 
        <input type="submit"/> 
    </label> 
</form>
</body>
</html>

main.css:

form {
    margin-left:auto;
    margin-right:auto;
    max-width: 450px;
    background: palegreen;
    padding: 25px 15px 25px 10px;
    border:1px solid #dedede;
    font: 12px Arial;
}
h1 {
    padding: 20px;
    display: block;
    border-bottom:1px solid grey;
    margin: -20px 0px 20px 0px;
    color: mediumpurple;
}
h1>span {
    display: block;
    font-size: 13px;
}
label {
    display: block;
}
label>span {
    float: left;
    width: 20%;
    text-align: right;
    margin: 14px;
    color: mediumpurple;
    font-weight:bold;
}
input[type="text"], input[type="number"] {
    border: 1px solid #dedede;
    height: 30px;
    width: 70%;
    font-size: 12px;
    border-radius: 3px;
    margin: 5px;
}
input[type="submit"] {
    background: mediumseagreen;
    font-weight: bold;
    border: none;
    padding: 8px 20px 8px 20px;
    color: black;
    border-radius: 5px;
    cursor: pointer;
    margin-left:4px;
}
input[type="submit"]:hover {
    background: red;
    color: yellow;
}
View Code

六、應用測試

將項目配置到tomcat服務器,啓動服務器,並在瀏覽器輸入以下URL來測試應用:

http://localhost:8008/appdesign4/form

應用將展現一個表單:

若是在文本字段中輸入一些內容並按提交按鈕,服務器將建立一個pdf文件併發送重定向到瀏覽器:

請注意:重定向網址將採用此格式:

http://localhost:8008/appdesign4/result/sessionId.pdf

參考文章

[1]MVC簡介(部分轉載)

[2]Spring MVC 學習總結(一)——MVC概要與環境配置(IDea與Eclipse示例)(推薦)

[3]Spring MVC 學習總結(三)——請求處理方法Action詳解

[4]Spring MVC學習指南

[5]java基礎篇---Servlet過濾器

相關文章
相關標籤/搜索