對於簡單的Java Web項目,咱們的項目僅僅包含幾個jsp頁面,因爲項目比較小,咱們一般能夠經過連接方式進行jsp頁面間的跳轉。css
可是若是是一箇中型或者大型的項目,上面那種方式就會帶來許多維護困難,代碼複用率低等問題。所以,咱們推薦使用MVC模式。html
MVC的全名是Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,是一種軟件設計模式。它是用一種業務邏輯、數據與界面顯示分離的方法來組織代碼,將衆多的業務邏輯彙集到一個部件裏面,在須要改進和個性化定製界面及用戶交互的同時,不須要從新編寫業務邏輯,達到減小編碼的時間。前端
MVC開始是存在於桌面程序中的,M是指業務模型,V是指用戶界面,C則是控制器。vue
使用的MVC的目的:在於將M和V的實現代碼分離,從而使同一個程序可使用不一樣的表現形式。好比Windows系統資源管理器文件夾內容的顯示方式,下面兩張圖中左邊爲詳細信息顯示方式,右邊爲中等圖標顯示方式,文件的內容並無改變,改變的是顯示的方式。無論用戶使用何種類型的顯示方式,文件的內容並無改變,達到M和V分離的目的。java
在網頁當中:react
下圖說明了三者之間的調用關係:程序員
用戶首先在界面中進行人機交互,而後請求發送到控制器,控制器根據請求類型和請求的指令發送到相應的模型,模型能夠與數據庫進行交互,進行增刪改查操做,完成以後,根據業務的邏輯選擇相應的視圖進行顯示,此時用戶得到這次交互的反饋信息,用戶能夠進行下一步交互,如此循環。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模式:
每一個控制器中能夠定義多個請求URL,每一個用戶請求都發送給控制器,請求中的URL標識出對應的Action。Action表明了Web應用能夠執行的一個操做。一個提供了Action的Java對象稱爲Action對象。一個Action類型能夠支持多個Action(在Spring MVC以及Struts2中),或一個Action(在struts1中)。
注意:Struts一、Spring MVC和JavaServer Fces使用Servlet做爲控制器,而Struts2使用Filter做爲控制器。
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頁面。
接下來咱們將會演示基於MVC框架的4個不一樣的示例:
建立一個名爲appdesign1的Dynamic Web Project項目,Servlet版本選擇3.0,其功能設定爲輸入一個產品信息。具體爲:
示例應用支持以下兩個action(每一個action對應一個URL):
示例應用由以下組件組成:
示例應用目錄結構以下:
注意:因爲咱們採用的是Servlet3.0版本,web.xml能夠不須要,具體能夠參考博客Servlet2.5版本和Servlet3.0版本。
項目右鍵屬性、部署路徑設置以下:
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 屬性。
表單類與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中。
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()方法執行以下步驟:
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類,負責將一個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> </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頁面,咱們能夠經過如下方式避免這種直接訪問:
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
雖然Servlet是MVC框架中最多見的控制器,可是過濾器也能夠充當控制器。Struts2就是使用過濾器做爲控制器,是由於該過濾波器也可用於提供靜態頁面。
public void init(FilterConfig filterConfig) throws ServletException
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException
public void destroy();
public void doFilter(ServletRequest request,ServletResponse response) throws IOException,ServletException
注意:過濾器沒有做爲歡迎頁面(即僅僅在瀏覽器地址欄中輸入域名)的權限,僅輸入域名時不會調用過濾器分派器。
下面咱們採用一個名爲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類:
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"; } }
如今,須要修改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> </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:
該應用的目錄結構以下:
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(); } } }
這裏的關鍵在於,PDFAction須要一個PDFService來完成它的工做,換句話說,PDFAction依賴於PDFService。沒有依賴注入,你必須在PDFAction類中實例化PDFService類,這將使PDFAction更不可測試。除此以外,若是須要更改PDFService的實現,必須從新編譯PDFAction。
使用依賴注入,每一個組件都有注入它的依賴項,這使得測試每一個組件更容易。對於在依賴注入環境中的類,必須使其支持注入。一種方法是爲每一個依賴關係建立一個set方法。例如,PDFAction類有一個setPDFService方法,能夠調用它來傳遞PDFService。注入也能夠經過構造方法或者類屬性進行。
一旦全部的類都支持注入,則能夠選擇一個依賴注入框架並將其導入項目。好比Spring框架、Google Guice、Weld和PicoContainer是一些好的選擇。
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。
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模式:
// 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,因爲依賴注入器,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> </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; }
將項目配置到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學習指南