经典怀旧 —— 通过一个代码案例讲解 Spring MVC 的请求流程

目录

一. 前言

二. Spring MVC 简介

2.1. Spring MVC 的诞生

2.2. 什么是 MVC

2.3. 什么是 Spring MVC

三. Spring MVC 的请求流程

3.1. 核心架构的具体流程步骤

3.2. 流程补充

3.2.1. Filter(ServletFilter)

3.2.2. LocaleResolver

3.2.3. ThemeResolver

3.2.4. 文件的上传请求

四. Spring MVC 代码示例

4.1. Maven 包引入

4.2. 业务代码的编写

4.3. webapp 下的 web.xml

4.4. JSP 视图

4.5. 部署测试


一. 前言

    前面几篇文章我们介绍了 Spring 框架和 Spring 框架中最为重要的两个技术点:IoC 和 AOP。那我们如何更好的构建上层的应用呢(比如 Web 应用),这便是 Spring MVC。Spring MVC 是Spring 在 Spring Container Core 和 AOP 等技术基础上,遵循上述 Web MVC 的规范推出的 Web开发框架,目的是为了简化 Java 栈的 Web 开发。 本文主要介绍 Spring MVC 主要的流程和基础案例的编写和运行。

    虽然目前主流都是 Spring Boot,但是越是简单好用的技术往往封装隐藏的东西更多,对于程序员的成长不见得是一件好事。同是,不管是 Spring MVC 还是 Spring Boot,原理上的很多东西都是想通的,技术会过时,但思想恒经典。

二. Spring MVC 简介

2.1. Spring MVC 的诞生

    在《通过一个 Spring 的 HelloWorld 引入 Spring 要点》中通过几个 Demo 应用了Core Container 中包。

Demo 中涉及到的 Core Container 包使用如下:

那么问题来了,我们如何更好的构建上层的应用呢?比如 Web 应用?

针对构建上层的 Web 应用,Spring MVC 诞生了,它也是 Spring 技术栈中非常重要的一个框架。

    所以,为了更好的帮助你串联整个知识体系,我列出了几个问题,通过如下几个问题帮你深入浅出的构建对 Spring MVC 的认知:

  • Java 技术栈的 Web 应用是如何发展的?
  • 什么是 MVC,什么是 Spring MVC?
  • Spring MVC 主要的请求流程是什么样的?
  • Spring MVC 中还有哪些组件?
  • 如何编写一个简单的 Spring MVC 程序呢?

2.2. 什么是 MVC

    MVC 英文是 Model View Controller,是模型(Model)- 视图(View)- 控制器(Controller)的缩写,一种软件设计规范。本质上也是一种解耦

    用一种业务逻辑、数据、界面显示分离的方法,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC 被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

  • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
  • View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。
  • Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

2.3. 什么是 Spring MVC

    简单而言,Spring MVC 是 Spring 在 Spring Container Core 和 AOP 等技术基础上,遵循上述Web MVC 的规范推出的 Web 开发框架,目的是为了简化 Java 栈的 Web 开发。

    套上专业词汇,Spring MVC 是一种基于 Java 的实现了 Web MVC 设计模式的请求驱动类型的轻量级 Web 框架,即使用了 MVC 架构模式的思想,将 Web 层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring MVC 也是要简化我们日常 Web 开发的。

相关特性如下:

  • 让我们能非常简单的设计出干净简洁的 Web 层,从而进行开发;
  • 天生与 Spring 框架集成(如 IoC 容器、AOP 等);
  • 提供强大的约定大于配置的契约式编程支持;
  • 能简单的进行 Web 层的单元测试;
  • 支持灵活的 URL 到页面控制器的映射;
  • 非常容易与其他视图技术集成,如 Velocity、FreeMarker 等等,因为模型数据不放在特定的 API 里,而是放在一个 Model 里(Map 数据结构实现,因此很容易被其他框架使用);
  • 非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的 API;
  • 提供一套强大的 JSP 标签库,简化 JSP 开发;
  • 支持灵活的本地化、主题等解析;
  • 更加简单的异常处理;
  • 对静态资源的支持;
  • 支持 RESTful 风格。关于 RESTful 可参见《RESTful API 接口设计指南》

三. Spring MVC 的请求流程

    Spring MVC 框架也是一个基于请求驱动的 Web 框架,并且也使用了前端控制器模式来进行设计,再根据请求映射规则分发给相应的页面控制器(动作/处理器)进行处理。

3.1. 核心架构的具体流程步骤

首先让我们整体看一下 Spring MVC 处理请求的流程:

核心架构的具体流程步骤如下:

  1. 首先用户发送请求 ==> DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
  2. DispatcherServlet ==> HandlerMapping, HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器对象),通过这种策略模式,很容易添加新的映射策略;
  3. DispatcherServlet ==> HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
  4. HandlerAdapter ==> 处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个 ModelAndView 对象(包含模型数据、逻辑视图名);
  5. ModelAndView 的逻辑视图名 ==> ViewResolver,ViewResolver 将把逻辑视图名解析为具体的 View,通过这种策略模式,很容易更换其他视图技术;
  6. View ==> 渲染,View 会根据传进来的 Model 模型数据进行渲染,此处的 Model 实际是一个Map 数据结构,因此很容易支持其他视图技术;
  7. 返回控制权给 DispatcherServlet,由 DispatcherServlet 返回响应给用户,到此一个流程结束。

3.2. 流程补充

上述流程只是核心流程,这里我们再补充一些其它组件。

3.2.1. Filter(ServletFilter)

    进入 Servlet 前可以有 preFilter,Servlet 处理之后还可以有 postFilter。

3.2.2. LocaleResolver

    在视图解析/渲染时,还需要考虑国际化(Local),显然这里需要有 LocaleResolver。

3.2.3. ThemeResolver

    Spring MVC 中还设计了 ThemeSource 接口和 ThemeResolver,包含一些静态资源的集合(样式及图片等),用来控制应用的视觉风格。

3.2.4. 文件的上传请求

    对于常规请求上述流程是合理的,但是如果是文件的上传请求,那么就不太一样了,所以这里便出现了 MultipartResolver。

四. Spring MVC 代码示例

本例子主要文件和结构如下:

4.1. Maven 包引入

    主要引入 spring-webmvc 包(spring-webmvc 包中已经包含了 Spring Core Container 相关的包),以及 servlet 和 jstl(JSP 中使用 jstl)的包。

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.lm.it</groupId>
        <artifactId>lm-spring-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>07-lm-spring-demo-springmvc</artifactId>
    <name>07-lm-spring-demo-springmvc</name>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring.version>5.3.9</spring.version>
        <servlet.version>4.0.1</servlet.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
    </dependencies>

</project>

4.2. 业务代码的编写

User 实体类:

package com.lm.it.springframework.entity;

/**
 * User
 */
public class User {
    /**
     * user's name.
     */
    private String name;

    /**
     * user's age.
     */
    private int age;

    /**
     * init.
     *
     * @param name name
     * @param age  age
     */
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Dao:

package com.lm.it.springframework.dao;

import com.lm.it.springframework.entity.User;
import org.springframework.stereotype.Repository;

import java.util.Collections;
import java.util.List;

@Repository
public class UserDaoImpl {
    /**
     * mocked to find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return Collections.singletonList(new User("流华追梦", 18));
    }
}

Service:

package com.lm.it.springframework.service;

import com.lm.it.springframework.dao.UserDaoImpl;
import com.lm.it.springframework.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl {
    /**
     * user dao impl.
     */
    @Autowired
    private UserDaoImpl userDao;

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return this.userDao.findUserList();
    }
}

Controller:

package com.lm.it.springframework.controller;

import com.lm.it.springframework.service.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

@Controller
public class UserController {
    @Autowired
    private UserServiceImpl userService;

    /**
     * find user list.
     *
     * @param request  request
     * @param response response
     * @return model and view
     */
    @RequestMapping("/user")
    public ModelAndView list(HttpServletRequest request, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("dateTime", new Date());
        modelAndView.addObject("userList", userService.findUserList());
        modelAndView.setViewName("userList"); // views目录下userList.jsp
        return modelAndView;
    }
}

4.3. webapp 下的 web.xml

webapp 下的 web.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <display-name>SpringFramework - SpringMVC Demo</display-name>

    <servlet>
        <servlet-name>springmvc-demo</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc-demo</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

springmvc.xml:

web.xml 中我们配置初始化参数 contextConfigLocation,路径是 classpath:springmvc.xml。

<init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
</init-param>

在 src/main/resources 目录下创建 springmvc.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 扫描注解 -->
    <context:component-scan base-package="com.lm.it.springframework"/>

    <!-- 静态资源处理 -->
    <mvc:default-servlet-handler/>

    <!-- 开启注解 -->
    <mvc:annotation-driven/>

    <!-- 视图解析器 -->
    <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

4.4. JSP 视图

创建 userList.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>User List</title>

    <!-- Bootstrap -->
    <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">

</head>
<body>
    <div class="container">
        <c:if test="${!empty userList}">
            <table class="table table-bordered table-striped">
                <tr>
                    <th>Name</th>
                    <th>Age</th>
                </tr>
                <c:forEach items="${userList}" var="user">
                    <tr>
                        <td>${user.name}</td>
                        <td>${user.age}</td>
                    </tr>
                </c:forEach>
            </table>
        </c:if>
    </div>
</body>
</html>

4.5. 部署测试

我们通过 IDEA 的 Tomcat 插件来进行测试。

Tomcat 下载地址:https://downloads.apache.org/tomcat/

1. 配置 Run Congfiuration:

2. 添加 Tomcat Server - Local

3. 将我们下载的 Tomcat 和 Tomcat Server - Local 关联

4. 在 Deployment 中添加我们的项目

5. 运行和管理 Tomcat Sever(注意 context 路径)

6. 运行后访问我们的 Web 程序页面(注意 context 路径)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流华追梦

你的鼓励将是我创作最大的动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值