Spring MVC-02循序渐进之解耦控制器和校验器

简介: Spring MVC-02循序渐进之解耦控制器和校验器

概述


在上篇博文 Spring MVC-01循序渐进之Model 2和MVC中,我们可以看到业务逻辑代码都写在了Servlet控制器中,这个Servlet随着应用复杂度的增加而不断增加,变得难以维护,为了避免该问题,我们应该将业务逻辑代码提取到独立的被称为controller的类中


项目结构


20180105144116668.jpg


我们在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>


运行项目,测试


20180105151139481.jpg


源码

代码已提交到github

https://github.com/yangshangwei/SpringMvcTutorialArtisan

相关文章
|
4月前
|
消息中间件 Java 开发者
【颠覆想象】Spring Boot重构未来:解耦与隔离,打造坚不可摧的微服务帝国!
【8月更文挑战第29天】本文通过构建电子商务平台的具体案例,深入探讨了如何利用 Spring Boot 实现服务间的解耦与隔离。文章详细介绍了依赖注入、模块化设计及异步通信等关键技术,并提供了具体代码示例。通过依赖注入,对象间耦合得以降低;模块化设计使各功能域独立,降低系统复杂度;异步通信则利用消息队列提升系统吞吐量与响应速度。这些方法不仅优化了系统架构,还加快了开发进程。
79 0
|
6月前
|
Java 开发者 Spring
Spring Boot 实现解耦和隔离的技术指南
【6月更文挑战第13天】Spring Boot 作为一种流行的 Java 框架,通过其强大的依赖注入和配置管理功能,使得开发者可以轻松实现模块之间的解耦和隔离
103 3
|
2月前
|
Java Spring
Spring从入门到入土(解耦的实现过程)
这篇文章详细讲解了在Spring框架中实现解耦的多种方法,包括使用接口、配置文件、BeanFactory模式以及单例模式,旨在降低代码间的依赖关系,提高程序的可维护性和扩展性。
32 0
Spring从入门到入土(解耦的实现过程)
|
1月前
|
前端开发 Java UED
SpringMVC全局异常处理+拦截器使用+参数校验
通过使用 SpringMVC 的全局异常处理、拦截器和参数校验,可以有效提升 Web 应用程序的安全性、稳定性和用户体验。这些技术的合理应用,不仅可以保证代码的健壮性,还能提高代码的可维护性,为开发高质量的 Web 应用程序提供了坚实的基础。
49 6
|
26天前
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
66 0
|
2月前
|
JSON 前端开发 Java
Spring Boot框架中的响应与分层解耦架构
在Spring Boot框架中,响应与分层解耦架构是两个核心概念,它们共同促进了应用程序的高效性、可维护性和可扩展性。
67 3
|
7月前
|
设计模式 存储 前端开发
MVC(模型-视图-控制器)是一种在Web应用程序开发中广泛使用的软件设计模式
【5月更文挑战第12天】MVC模式是Web应用开发中的常见设计模式,将逻辑、数据和界面分离,提升代码可维护性和重用性。模型处理数据逻辑,视图展示数据,控制器协调用户输入与模型视图交互。优点包括代码分离、易维护、可扩展和组件重用,促进高效灵活的开发。
71 2
|
4月前
|
Java 开发者 Spring
Spring Boot大法好:解耦、隔离、异步,让代码‘活’起来,性能飙升的秘密武器!
【8月更文挑战第29天】解耦、隔离与异步是Spring Boot中的关键设计原则,能大幅提升软件的可维护性、扩展性和性能。本文通过示例代码详细探讨了这些原则的应用:依赖注入和面向接口编程实现解耦;模块化设计与配置文件实现隔离;`@Async`注解和`CompletableFuture`实现异步处理。综合运用这些原则,可以显著提升软件质量和性能,使系统更加健壮、灵活和高效。
44 0
|
6月前
|
运维 Java 关系型数据库
Spring运维之boot项目bean属性的绑定读取与校验
Spring运维之boot项目bean属性的绑定读取与校验
60 2
|
7月前
|
存储 安全 Java
Spring Security的密码加密和校验
本文介绍了Spring Security中密码的加密和校验。首先,在`SecurityConfig`配置类中添加了两个Bean,一个是`PasswordEncoder`的无操作实例,用于明文密码校验,另一个是`UserDetailsService`,用于创建内存中的用户信息。接着,文章对比了对称加密、非对称加密和摘要加密三种加密方式,并重点讲解了BCrypt摘要加密的特性,强调其安全性高于MD5。最后,通过代码示例展示了如何使用BCryptPasswordEncoder改造权限密码加密,确保密码的安全存储和校验。
347 6