Spring MVC-01循序渐进之Model 2和MVC

举报
小工匠 发表于 2021/09/11 00:15:45 2021/09/11
【摘要】 概述模型1和模型2模型2架构图模型2之Servlet控制器 Product类ProductForm类ControllerServlet类视图测试应用 Maven配置文件源码 ...

概述

Java Web开发中有两种设计模式

  • 模型1:页面中心,适合小应用的开发

  • 模型2:基于MVC模式,是Java Web的推荐框架

本篇博文我们将通过3个不同的示例来介绍模型2和MVC模式

  • 第一个示例为基本的模型2应用,使用Servlet控制

  • 第二个示例引入了控制器

  • 第三个示例则引入了验证控件来检验用户的输入


模型1和模型2

还记得初次学习JSP,通常通过链接的方式进行页面之间的跳转,非常直接,但是如果一个JSP页面修改了名称,在大中型项目中会带来很大的维护问题,因此在实际的运用中并不推荐使用模型1。

模型2基于 模型—视图—控制器(MVC)模式,该模式是Smalltalk-80用户交互的核心概念。

一个实现了MVC模式的应用包含模型、视图和控制器3个模块。

  • 视图负责应用的展示

  • 模型封装了应用的数据和业务逻辑

  • 控制器负责接收用户的输入,改变模型以及调整视图的显示

模型2中,Servlet或者filter都可以充当控制器。 在Spring MVC 和 Struts1中送Servlet作为控制器,而Struts2中则使用一个Filter作为控制器。

大部分视图都采用JSP作为应用的视图,当然也有其他技术。

而模型则采用POJO(Plain Old Java Object), 是一个普通对象。

实践中会采用一个JavaBean来持有模型状态,并将业务逻辑放到一个Action类中。一个JavaBean必须拥有一个无参的构造函数,通过get/set来访问参数,同时支持持久化。


模型2架构图

我们来看下模型2应用的架构图

这里写图片描述

我们来分析一下:

  • 每个HTTP请求都发给控制器,请求中的URI标识出对应的action。 action代表了应用可以执行的一个操作。

  • 一个Action的Java对象称为Action对象,一个Action可以支持多个action(在Spring MVC以及Struts2中)或者一个action(Structs1 中)。

举个简单的例子: 添加产品,需要两个action

  1. 显示“添加产品”表单,以便用户输入信息
  2. 将表单信息保存到数据库中

如上所述,我们需要通过URI的方式告诉控制器来执行相应的action, 比如通过

http://domain/appName/product_input
  
 
  • 1

来显示“添加产品”表单

通过如下URI

http://domain/appName/product_save
  
 
  • 1

来保存产品。

控制器会解析URI并调用对应的Action,然后将模型对象放到视图可以访问的区域(以便服务端数据可以展示在浏览器上),最后,控制器利用RequestDispatcher跳转到视图(JSP页面),用表达式语言以及定制标签来显示数据。

注意:调用RequestDispatcher.forward方法并不会停止执行剩余的代码,因此,若forward方法不是最后一行代码,则应该显式的返回


模型2之Servlet控制器

为了便于对模型二有个直观的了解,我们展示一个简单的应用。实际中模型二非常复杂。

我们的demo如下所示

http://localhost:8080/chapter02a/product_input.action

这里写图片描述

demo支持如下的两个action

  • 展示“添加产品”表单。该action发送如上图中输入表单到浏览器,其对应的URI应包含字符串product_input

  • 保存产品并返回如下图所示的完成页面,对应的URI必须包含字符串product_save

http://localhost:8080/chapter02a/product_save.action

这里写图片描述


工程结构:

这里写图片描述

  • 一个Product类,作为product的领域对象
  • 一个ProductForm类,封装了HTML表单的输入项
  • 一个ControllerServlet类,控制器
  • 两个JSP页面作为View(都在WEB-INF下确保无法直接访问到,必须通过Servlet来跳转)
  • 一个CSS文件,定义了页面的显示风格

Product类

Product实例是一个封装了产品信息的JavaBean.
包含3个属性,name、description、price

package com.artisan.learnmvc.model;

import java.io.Serializable;
public class Product implements Serializable {
    private static final long serialVersionUID = 748392348L;
    private String name;
    private String description;
    private float 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 float getPrice() {
        return price;
    }
    public void setPrice(float price) {
        this.price = price;
    }
}
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Product类实现了Serializable接口,其实例可以安全的将数据保存到HTTPSession中。


ProductForm类

ProductForm表单类与HTML表单相映射,是后者在服务端的代表。

ProductForm用来保存HttPServletRequest类中传递过来的值,因为HttPServletRequest只能是字符串,所以ProductForm类中所有的属性都是String 。注意Product中的price是float,ProductForm是String类型

ProductForm类看上去和Product类相同,那么有没有存在的必要呢?

实际上,表单对象会传到ServletRequest给其他组件,类似Validator,而ServletRequest是一个Servlet层的对象不应该暴露给应用的其它层。

另外一个原因是:当数据校验失败时,表单对象将用于保存和展示在原始表单上的输入。

注意:大部分情况,一个表单类不需要实现Serializable接口,因为表单对象很少保存在HttPSession中

package com.artisan.learnmvc.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;
    }
}
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

ControllerServlet类

ControllerServlet类继承自javax.servlet.http.HttpServlet. 其中doGet和doPOST方法都最终调用自定义的process方法,该方法是整个Servlet控制器的核心。

按照约定,所有的Servlet类名称都带有servlet后缀。

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.form.ProductForm;
import com.artisan.learnmvc.model.Product;

/**
 * 
 * @author Mr.Yang
 * @Desc ControllerServlet类继承自javax.servlet.http.HttpServlet. 
 *       其中doGet和doPOST方法都最终调用自定义的process方法,该方法是整个Servlet控制器的核心。
 *
 */
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: /chapter02a/product_input. 
         * 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);
        // execute an action
        if (action.equals("product_input.action")) {
            // no action class, there is nothing to be done
        } else if (action.equals("product_save.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(productForm.getDescription());
            try {
                product.setPrice(Float.parseFloat(
                        productForm.getPrice()));
            } catch (NumberFormatException e) {
            }

            // code to save product

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

        // forward to a view
        String dispatchUrl = null;
        if (action.equals("product_input.action")) {
            dispatchUrl = "/WEB-INF/jsp/ProductForm.jsp";
        } else if (action.equals("product_save.action")) {
            dispatchUrl = "/WEB-INF/jsp/ProductDetails.jsp";
        }
        if (dispatchUrl != null) {
            RequestDispatcher rd =
                    request.getRequestDispatcher(dispatchUrl);
            rd.forward(request, response);
        }
    }
}
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

如果是Servlet3.0 ,可以使用注解的方式,比如

@WebServlet(name="ControllerServlet",urlPatterns = {"/product_input","/product_save"})
public class ControllerServlet extends HttpServlet {
.....
}
  
 
  • 1
  • 2
  • 3
  • 4

我们来分析下ControllerServlet的process方法处理所有的输入请求。

1. 首先是获取请求的URI和action名称

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

2. 紧接着,process方法执行如下步骤

  • 创建并根据请求参数构造一个表单对象。product_save操作涉及3个属性 name description 和price,然后创建一个领域对象,并通过表单对象设置相应属性。

  • 执行针对领域对象的业务逻辑,包括持久化到数据库中

  • 转发请求到视图

详见代码部分。

web.xml中配置Servlet

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0"> 

     <servlet>
        <servlet-name>ControllerServlet</servlet-name>
        <servlet-class>com.artisan.learnmvc.servlet.ControllerServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ControllerServlet</servlet-name>
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>

</web-app>
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

视图

两个视图

  • ProductForm.jsp 对应product_input的操作
  • ProductDetails.jsp对应product_save的操作,通过EL表达式语言访问HTTPServletRequest中的product对象。

都采用了位于css目录下的main.css中的CSS样式进行控制

<!DOCTYPE HTML>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>

<div id="global">
<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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
<!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>
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

测试应用

我们使用tomcat的8080端口,运行服务,访问

http://localhost:8080/chapter02a/product_input.action

这里写图片描述

完成输入后,表单提交到服务器URL上

http://localhost:8080/chapter02a/product_save.action

这里写图片描述


Maven配置文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.artisan</groupId>
    <artifactId>chapter02a</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>chapter02a Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
            <!-- provided 依赖只有在当JDK 或者一个容器已提供该依赖之后才使用。 
                例如, 如果你开发了一个web 应用,你可能在编译 classpath 
                中需要可用的Servlet API 来编译一个servlet,但是你不会想要在打包好的WAR 中包含这个Servlet API;
                 这个Servlet API JAR 由你的应用服务器或者servlet 容器提供。
                  已提供范围的依赖在编译classpath (不是运行时)可用。它们不是传递性的,也不会被打包。 -->
        </dependency>
    </dependencies>
    <build>
        <finalName>chapter02a</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

源码

代码已提交到github

https://github.com/yangshangwei/SpringMvcTutorialArtisan

文章来源: artisan.blog.csdn.net,作者:小小工匠,版权归原作者所有,如需转载,请联系作者。

原文链接:artisan.blog.csdn.net/article/details/78867677

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。