概述
在上篇博文 Spring MVC-01循序渐进之Model 2和MVC中,我们可以看到业务逻辑代码都写在了Servlet控制器中,这个Servlet随着应用复杂度的增加而不断增加,变得难以维护,为了避免该问题,我们应该将业务逻辑代码提取到独立的被称为controller的类中
项目结构
我们在controller包下,增加了一个自定义的Controller接口和两个controller实现类用于执行对应的action ,该接口只有handleRequest方法。
实现类通过该方法访问到当前请求的HttpServletRequest和HttpServletResponse
示例
Controller接口
package com.artisan.learnmvc.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public interface Controller { String handleRequest(HttpServletRequest request ,HttpServletResponse response); }
InputProductController
package com.artisan.learnmvc.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class InputProductController implements Controller { @Override public String handleRequest(HttpServletRequest request, HttpServletResponse response) { System.out.println("find page"); return "/WEB-INF/jsp/ProductForm.jsp"; }
该类直接返回ProductForm.jsp的路径。
SaveProductController
package com.artisan.learnmvc.controller; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.artisan.learnmvc.form.ProductForm; import com.artisan.learnmvc.model.Product; import com.artisan.learnmvc.validator.ProductValidator; public class SaveProductController implements Controller { @Override public String handleRequest(HttpServletRequest request, HttpServletResponse response) { 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 Product product = new Product(); product.setName(productForm.getName()); product.setDescription(productForm.getDescription()); product.setPrice(Float.parseFloat(productForm.getPrice())); // no validation error, execute action method // insert code to save product to the database // store product in a scope variable for the view request.setAttribute("product", product); return "/WEB-INF/jsp/ProductDetails.jsp"; } else { // store errors and form in a scope variable for the view request.setAttribute("errors", errors); request.setAttribute("form", productForm); return "/WEB-INF/jsp/ProductForm.jsp"; } } }
SaveProductController类则会读取请求参数构造一个ProductForm对象,之后用ProductForm对象来构造一个Product对象,并返回SaveProductController.jsp的路径。
将业务逻辑迁移到controller类中的好处很明显:Controller Servlet变得更加的专注。作用更加像一个dispatcher,而非一个controller,因此我们将其改名为DispatcherServlet.
DispatcherServlet类检查每个URI,创建对应的controller,并调用其handleRequest方法
package com.artisan.learnmvc.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.artisan.learnmvc.controller.InputProductController; import com.artisan.learnmvc.controller.SaveProductController; public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = -5454977373262337215L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp); } private void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String uri = request.getRequestURI(); /* * uri is in this form: /contextName/resourceName, for example: * /chapter02b/product_input.action However, in the event 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); System.out.println("action:" + action); String dispatchUrl = null; // execute an action if (action.equals("product_input.action")) { InputProductController inputProductController = new InputProductController(); dispatchUrl = inputProductController.handleRequest(request, response); } else if (action.equals("product_save.action")) { SaveProductController saveProductController = new SaveProductController(); dispatchUrl = saveProductController.handleRequest(request, response); } System.out.println("dispatchUrl:" + dispatchUrl); // 页面跳转 if (dispatchUrl != null) { RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl); rd.forward(request, response); } } }
校验器
我们这里仅仅说前台校验,不涉及后台校验,这里只是简单的演示下
package com.artisan.learnmvc.validator; import java.util.ArrayList; import java.util.List; import com.artisan.learnmvc.form.ProductForm; public class ProductValidator { public List<String> validate(ProductForm form){ List<String> errors = new ArrayList<String>(); String name = form.getName(); if(name == null || name.trim().isEmpty()){ System.out.println("name must input"); errors.add("Product must have a name"); } String price = form.getPrice(); if (price == null || price.trim().isEmpty()) { System.out.println("price must input"); errors.add("Product must have a price"); }else { try { Float.parseFloat(price); } catch (NumberFormatException e) { System.out.println("price must be right format"); errors.add("Invalid price"); e.printStackTrace(); } } return errors; } }
ProductValidator类中有一个操作ProductForm对象的validate方法,确保产品的名字非空,价格是一个合理的数字。
validate方法返回一个包含错误信息的字符串列表,若返回一个空列表,表示输入合法。
应用中需要用到产品校验的地方是保存产品时,即SaveProductController类。现在为SaveProductController类引入ProductValidator类,调用validate方法
// validate ProductForm ProductValidator productValidator = new ProductValidator(); List<String> errors = productValidator.validate(productForm);
接下来我们修改ProductForm.jsp页面,使其可以显示错误信息
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE HTML> <html> <head> <title>Add Product Form</title> <style type="text/css">@import url(css/main.css);</style> </head> <body> <div id="global"> <c:if test="${requestScope.errors != null}"> <p id="errors"> Error(s)! <ul> <c:forEach var="error" items="${requestScope.errors}"> <li>${error}</li> </c:forEach> </ul> </p> </c:if> <form action="product_save.action" method="post"> <fieldset> <legend>Add a product</legend> <p> <label for="name">Product Name: </label> <input type="text" id="name" name="name" tabindex="1"> </p> <p> <label for="description">Description: </label> <input type="text" id="description" name="description" tabindex="2"> </p> <p> <label for="price">Price: </label> <input type="text" id="price" name="price" tabindex="3"> </p> <p id="buttons"> <input id="reset" type="reset" tabindex="4"> <input id="submit" type="submit" tabindex="5" value="Add Product"> </p> </fieldset> </form> </div> </body> </html>
运行项目,测试
源码
代码已提交到github