Spring MVC 全解析:从核心原理到 SSM 整合实战 (附完整源码)

文章目录

    • 写在前面
    • [1. 回顾MVC架构](#1. 回顾MVC架构)
      • [1.1 什么是MVC](#1.1 什么是MVC)
      • [1.2 Model1 与 Model2 时代](#1.2 Model1 与 Model2 时代)
        • [Model1 时代](#Model1 时代)
        • [Model2 时代](#Model2 时代)
      • [1.3 回顾Servlet](#1.3 回顾Servlet)
      • [1.4 架构演进:MVVM 扫盲](#1.4 架构演进:MVVM 扫盲)
    • [2. 什么是Spring MVC](#2. 什么是Spring MVC)
      • [2.1 概述](#2.1 概述)
      • [2.2 中心控制器:DispatcherServlet](#2.2 中心控制器:DispatcherServlet)
      • [2.3 Spring MVC 执行原理](#2.3 Spring MVC 执行原理)
    • [3. 第一个 Spring MVC 程序:HelloSpring](#3. 第一个 Spring MVC 程序:HelloSpring)
      • [3.1 配置版实现](#3.1 配置版实现)
      • [3.2 注解版实现](#3.2 注解版实现)
      • [3.3 小结](#3.3 小结)
    • [4. Controller 与 RESTful 风格](#4. Controller 与 RESTful 风格)
      • [4.1 `Controller` 接口实现](#4.1 Controller 接口实现)
      • [4.2 `@Controller` 注解](#4.2 @Controller 注解)
      • [4.3 `@RequestMapping` 注解](#4.3 @RequestMapping 注解)
      • [4.4 RestFul 风格详解](#4.4 RestFul 风格详解)
      • [4.5 程序员的锦囊妙计:小黄鸭调试法](#4.5 程序员的锦囊妙计:小黄鸭调试法)
    • [5. 数据处理与页面跳转](#5. 数据处理与页面跳转)
      • [5.1 ModelAndView](#5.1 ModelAndView)
      • [5.2 原生 Servlet API](#5.2 原生 Servlet API)
      • [5.3 Spring MVC 的转发与重定向](#5.3 Spring MVC 的转发与重定向)
      • [5.4 处理前端提交数据](#5.4 处理前端提交数据)
      • [5.5 数据显示到前端](#5.5 数据显示到前端)
      • [5.6 乱码问题解决方案](#5.6 乱码问题解决方案)
    • [6. JSON 交互处理](#6. JSON 交互处理)
      • [6.1 什么是 JSON](#6.1 什么是 JSON)
      • [6.2 Jackson 框架使用](#6.2 Jackson 框架使用)
      • [6.3 FastJson 框架简介](#6.3 FastJson 框架简介)
    • [7. 整合 SSM 框架实战](#7. 整合 SSM 框架实战)
      • [7.1 基本环境搭建](#7.1 基本环境搭建)
      • [7.2 MyBatis 层配置](#7.2 MyBatis 层配置)
      • [7.3 Spring 业务层配置](#7.3 Spring 业务层配置)
      • [7.4 Spring MVC 表现层配置](#7.4 Spring MVC 表现层配置)
      • [7.5 案例功能实现](#7.5 案例功能实现)
    • [8. Ajax 技术](#8. Ajax 技术)
    • [9. 拦截器 (Interceptor)](#9. 拦截器 (Interceptor))
      • [9.1 概述与区别](#9.1 概述与区别)
      • [9.2 自定义拦截器](#9.2 自定义拦截器)
      • [9.3 实战:用户登录验证](#9.3 实战:用户登录验证)
    • [10. 文件上传和下载](#10. 文件上传和下载)
      • [10.1 准备工作](#10.1 准备工作)
      • [10.2 文件上传](#10.2 文件上传)
        • [报错:Cannot resolve method 'getServletContext' in 'HttpServletRequest'](#报错:Cannot resolve method ‘getServletContext’ in ‘HttpServletRequest’)
      • 3.文件下载
    • 参考

写在前面

你好,我是 ZzzFatFish。

这份笔记记录了从 Web 开发最基础的 MVC 架构模式,到 Spring MVC 核心原理与实战的全过程。内容涵盖了 Servlet 回顾、Spring MVC 的执行流程、注解式开发、RESTful 风格、JSON 数据交互、SSM 框架整合、AJAX 异步通信、拦截器应用以及文件上传下载等关键技术点。

这不仅是一份按部就班的学习记录,更是一份包含了大量实战代码、常见问题排查和个人心得(比如有趣的小黄鸭调试法)的实践总结。

无论你是正在入门 Spring MVC 的新手,还是希望系统性回顾知识点的开发者,都希望这份详尽的笔记能为你提供有价值的参考和帮助。

💡 配套源码已上传至 Gitee

为了方便大家学习和实践,本文涉及的所有代码和配置都已整理并上传至我的 Gitee 仓库。建议大家 clone 下来,边看文章边动手敲一遍,效果更佳!

仓库地址:https://gitee.com/zzzfatfish/springmvc-test


1. 回顾MVC架构

1.1 什么是MVC

MVC 是一种经典的软件架构模式,其核心思想在于解耦,即将应用程序的不同职责分离,使代码结构更清晰、更易于维护。

MVC = Model (模型) + View (视图) + Controller (控制器)

  • 模型 (Model):负责应用程序的数据和业务逻辑。它既包含用于存储数据的实体对象(JavaBean),也包含处理具体业务的服务类。
  • 视图 (View):负责数据的展示,即用户界面。它向用户展示模型中的数据,并将用户的操作(如点击按钮、填写表单)传递给控制器。
  • 控制器 (Controller):作为模型和视图之间的协调者。它接收来自视图的请求,决定调用哪个模型进行处理,然后将处理结果更新到视图上。

MVC 的主要作用是降低视图与业务逻辑之间的耦合度。它并非一种具体的设计模式,而是一种宏观的架构思想。

在 Java Web 中,基于 Servlet 的 MVC 模式实现如下:

  • 模型:由 JavaBean(实体模型)和普通的 Service/DAO 类(业务模型)组成。
  • 视图:通常由 JSP 页面承担,负责数据显示和用户交互。
  • 控制器:由 Servlet 承担,接收 HTTP 请求,并进行分发和协调。

其经典流程如下图所示:

最典型的原生 MVC 就是 JSP + Servlet + JavaBean 的模式。

1.2 Model1 与 Model2 时代

Model1 时代

在 Web 开发早期,普遍采用 Model1 架构。它主要分为视图层和模型层,JSP 页面同时承担了视图(View)和控制器(Controller)的双重职责。

  • 优点:架构简单,适合小型项目快速开发。
  • 缺点:JSP 职责过重,视图逻辑与控制逻辑混杂,导致代码重用性低,难以维护和扩展。
Model2 时代

为了解决 Model1 的弊端,Model2 架构应运而生。它引入了独立的控制器(Servlet),实现了彻底的 MVC 分离。

职责分析:

  • Controller (控制器):获取表单数据,调用业务逻辑,并决定转向哪个页面。
  • Model (模型):处理业务逻辑,保存数据状态。
  • View (视图):纯粹负责页面显示。

Model2 模式提高了代码的复用率和项目的扩展性,大大降低了维护成本,是现代 Web 框架的基础。

1.3 回顾Servlet

为了更好地理解 Spring MVC,我们先用原生的 Servlet 来实现一个简单的 MVC 程序。

新建一个Maven工程当做父工程!pom依赖!

xml 复制代码
<!-- 导入依赖 -->
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>
  1. 建立一个Moudle:springmvc-01-servlet , 添加Web app的支持!

在 idea 中没有找到 add framework support (添加框架支持) 选项-CSDN博客

1. 项目搭建与依赖

引入 servlet-apijsp-api 依赖。

xml 复制代码
<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
    </dependency>
</dependencies>

2. 编写 Servlet (Controller)

创建一个 HelloServlet 类,用于接收请求、处理逻辑并转发到视图。

java 复制代码
package com.github.subei.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 获取前端参数
        String method = req.getParameter("method");
        if ("add".equals(method)) {
            req.getSession().setAttribute("msg", "执行了 add 方法");
        }
        if ("delete".equals(method)) {
            req.getSession().setAttribute("msg", "执行了 delete 方法");
        }
        
        // 2. 调用业务逻辑层 (此处省略)
        
        // 3. 视图转发
        req.getRequestDispatcher("/WEB-INF/jsp/Hello.jsp").forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

3. 编写 JSP (View)

WEB-INF/jsp/ 目录下创建 jsp 用于显示结果。

Hello.jsp

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Servlet Result</title>
</head>
<body>
    <h3>${msg}</h3>
</body>
</html>

Form.jsp

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<form action="/hello" method="post">
    <input type="text" name="method">
    <input type="submit">
</form>

</body>
</html>

4. 注册 Servlet (web.xml)

web.xml 中配置 Servlet 的映射关系。

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_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.github.subei.servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

</web-app>

5. 启动测试

配置 Tomcat 并启动,访问 http://localhost:8080/your_project/hello?method=add,即可看到 Servlet 处理后的结果。

现代 MVC 框架的核心任务就是简化上述流程,包括:

  1. 将 URL 映射到 Java 类或方法。
  2. 自动封装用户提交的数据。
  3. 解耦业务处理与视图渲染。
  4. 将响应数据渲染到 JSP/HTML 等视图上。

1.4 架构演进:MVVM 扫盲

在 MVC 的基础上,前端领域又演化出了 MVVM(Model-View-ViewModel)模式,它本质上是 MVC 的改进版。

在传统 MVC 中,Controller 的职责过于繁重,既要处理业务逻辑,又要负责数据转换和视图更新,导致 Controller 变得臃肿不堪。

MVVM 引入了 ViewModel 这一关键角色,它充当了视图(View)和模型(Model)之间的"适配器"和"粘合剂"。

  • View 层:包含视图(UIView)和控制器(UIViewController),职责更纯粹,只负责展示和交互。
  • ViewModel 层:持有 Model,并暴露属性与 View 中的 UI 元素一一对应。它处理所有与 UI 相关的逻辑,极大地为 Controller 减负。
  • Model 层:依然是纯粹的数据模型。
  • Binder :MVVM 的灵魂。它在 View 和 ViewModel 之间建立了双向数据绑定,当 ViewModel 的数据变化时,View 会自动更新;反之,当用户的操作导致 View 变化时,ViewModel 的数据也会同步改变。

Vue.js、Angular 等现代前端框架就是 MVVM 模式的典型实现。

2. 什么是Spring MVC

2.1 概述

Spring MVC 是 Spring Framework 的一部分,是一个基于 Java 实现 MVC 思想的轻量级 Web 框架。

Spring MVC 的核心特点:

  1. 轻量级:核心库小,易于学习和使用。
  2. 高效:基于请求-响应模式的 MVC 框架。
  3. 无缝集成:与 Spring IoC/AOP 完美结合,享受 Spring 生态的全部优势。
  4. 约定优于配置:提供大量默认配置,简化开发。
  5. 功能强大:支持 RESTful、数据验证、格式化、国际化、拦截器等高级功能。
  6. 简洁灵活:支持基于注解的开发,代码非常简洁。

最重要的一点:社区庞大,生态成熟,使用者和使用公司众多。

2.2 中心控制器:DispatcherServlet

Spring MVC 框架是以请求为驱动 ,围绕一个中心 Servlet ------DispatcherServlet 来设计和工作的。DispatcherServlet 继承自 HttpServlet,是整个流程的入口和调度中心,负责接收所有请求并将其分发给不同的处理器(Controller)。

所有请求都会先经过这个前置控制器,再由它来协调各个组件完成后续工作。

当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。

2.3 Spring MVC 执行原理

下图展示了 Spring MVC 处理一个请求的完整流程。实线表示由框架自动完成,虚线表示需要开发者编写的部分。

执行流程简要分析:

  1. 用户发送请求,DispatcherServlet(前置控制器)接收请求。
  2. DispatcherServlet 调用 HandlerMapping(处理器映射器),根据请求 URL 查找对应的 Handler(即 Controller 方法)。
  3. HandlerMapping 返回一个 HandlerExecutionChain(执行链,包含 Handler 和拦截器)给 DispatcherServlet
  4. DispatcherServlet 调用 HandlerAdapter(处理器适配器)。
  5. HandlerAdapter 按照特定规则去执行 Handler(调用 Controller 方法)。
  6. Controller 方法执行业务逻辑,并返回一个 ModelAndView 对象(包含模型数据和视图名)。
  7. HandlerAdapterModelAndView 返回给 DispatcherServlet
  8. DispatcherServlet 调用 ViewResolver(视图解析器),将逻辑视图名解析为具体的视图对象(如 JSP)。
  9. ViewResolver 返回具体的 View 对象给 DispatcherServlet
  10. DispatcherServletView 进行渲染(将模型数据填充到视图中)。
  11. DispatcherServlet 将最终渲染好的视图响应给用户。

3. 第一个 Spring MVC 程序:HelloSpring

3.1 配置版实现

这是早期 Spring MVC 的开发方式,通过实现 Controller 接口和 XML 配置来工作。

1. 项目搭建

创建一个 Maven Web 模块,并确保已引入 spring-webmvc 依赖。

springmvc-02-hellomvc

2. 配置 web.xml

注册 DispatcherServlet,并指定 Spring MVC 配置文件的位置。

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_4_0.xsd"
        version="4.0">
    <!-- 1. 注册 DispatcherServlet -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 关联 Spring MVC 配置文件 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!-- 启动时加载 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- 2. 映射所有请求到 DispatcherServlet -->
    <!-- / 匹配所有请求 (不包括 .jsp) -->
    <!-- /* 匹配所有请求 (包括 .jsp,不推荐) -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

3. 编写 springmvc-servlet.xml

编写SpringMVC 的 配置文件!名称:springmvc-servlet.xml : [servletname]-servlet.xml

配置 Spring MVC 的三大核心组件:处理器映射器、处理器适配器、视图解析器。

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 1. 处理器映射器: 按 bean 的 name (URL) 查找 Handler -->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

    <!-- 2. 处理器适配器: 执行实现了 Controller 接口的 Handler -->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

    <!-- 3. 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 视图前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!-- 视图后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 4. 注册 Handler (Controller) -->
    <!-- bean 的 name "/hello" 就是请求的 URL -->
    <bean name="/hello" class="com.github.subei.controller.HelloController"/>
</beans>

4. 编写 Controller

创建一个实现 Controller 接口的类。

编写要操作业务Controller ,要么实现Controller接口,要么增加注解;需要返回一个ModelAndView,装数据,封视图;

java 复制代码
package com.github.subei.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 创建模型和视图对象
        ModelAndView mv = new ModelAndView();

        // 封装数据 (Model)
        mv.addObject("msg", "Hello, SpringMVC! (Config Version)");
        // 设置视图名 (View)
        mv.setViewName("hello"); // 对应 /WEB-INF/jsp/hello.jsp
        
        return mv;
    }
}

5. 编写 JSP 视图 (hello.jsp)

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>HelloSpringMVC</title>
</head>
<body>
    <h3>${msg}</h3>
</body>
</html>

6. 启动测试

配置 Tomcat,启动后访问 http://localhost:8080/your_project/hello 即可看到结果。

常见问题与排错

  • java: 程序包org.springframework.web.servlet不存在 :通常是 Maven 依赖问题或 IDEA 配置问题。确保 spring-webmvc 依赖已正确下载,并检查项目的 Artifacts 配置,确保依赖包已添加到 WEB-INF/lib 目录下。
  • 404 Not Found :原因很多,常见于 URL 拼写错误、Controller 未注册为 Bean、web.xml 配置错误、Tomcat 发布路径配置问题等。
  • 笔者心得:因为 IDEA 2020.1 版本不稳定导致 404 问题排查了半个月,更换到 2020.2 版本后问题迎刃而解!工具链的稳定性至关重要。

3.2 注解版实现

注解是现代 Spring MVC 开发的主流方式,它极大地简化了配置。

1. web.xml 配置 (与配置版相同)

同样需要注册 DispatcherServlet 并关联配置文件。

新建一个Moudle,springmvc-03-hello-annotation ,添加web支持!

2. 配置 Maven 资源过滤

为防止 .xml 等资源文件在打包时被忽略,建议在 pom.xml 中添加资源过滤配置。

xml 复制代码
<build>
   <resources>
       <resource>
           <directory>src/main/java</directory>
           <includes>
               <include>**/*.properties</include>
               <include>**/*.xml</include>
           </includes>
           <filtering>false</filtering>
       </resource>
       <resource>
           <directory>src/main/resources</directory>
           <includes>
               <include>**/*.properties</include>
               <include>**/*.xml</include>
           </includes>
           <filtering>false</filtering>
       </resource>
   </resources>
</build>
  • 在pom.xml文件引入相关的依赖:主要有Spring框架核心库、Spring MVC、servlet , JSTL等。在父依赖中已经引入了!

  • 配置web.xml

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_4_0.xsd"
         version="4.0">

    <!--1.注册servlet-->
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--通过初始化参数指定SpringMVC配置文件的位置,进行关联-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!-- 启动顺序,数字越小,启动越早 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!--所有请求都会被springmvc拦截 -->
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>
  • 注意/ 和 /* 的区别:< url-pattern > / </ url-pattern > 不会匹配到.jsp, 只针对我们编写的请求;即:.jsp 不会进入spring的 DispatcherServlet类 。< url-pattern > /* </ url-pattern > 会匹配 *.jsp,会出现返回 jsp视图 时再次进入spring的DispatcherServlet 类,导致找不到对应的controller所以报404错。
    • 注意web.xml版本问题,要最新版!
    • 注册DispatcherServlet
    • 关联SpringMVC的配置文件
    • 启动级别为1
    • 映射路径为 / 【不要用/*,会404】

3. 编写 springmvc-servlet.xml (注解版)

添加Spring MVC配置文件

在resource目录下添加springmvc-servlet.xml配置文件,配置的形式与Spring容器配置基本类似,为了支持基于注解的IOC,设置了自动扫描包的功能,具体配置信息如下:

使用 context:component-scanmvc:annotation-driven 来开启注解支持。

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="...">

    <!-- 1. 自动扫描包,让指定包下的注解生效 -->
    <context:component-scan base-package="com.github.subei.controller"/>
    
    <!-- 2. 不处理静态资源,交由 Tomcat 默认的 Servlet 处理 -->
    <mvc:default-servlet-handler />
    
    <!-- 3. 开启 MVC 注解驱动 -->
    <!-- 它会自动注册默认的 HandlerMapping 和 HandlerAdapter,是 @RequestMapping 等注解生效的基础 -->
    <!--
		支持mvc注解驱动
    在spring中一般采用@RequestMapping注解来完成映射关系
    要想使@RequestMapping注解生效
    必须向上下文中注册DefaultAnnotationHandlerMapping
    和一个AnnotationMethodHandlerAdapter实例
    这两个实例分别在类级别和方法级别处理。
    而annotation-driven配置帮助我们自动完成上述两个实例的注入。
 	-->
	<mvc:annotation-driven />

    <!-- 4. 视图解析器 (与配置版相同) -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

4. 编写 Controller (注解版)

使用 @Controller@RequestMapping 注解。

java 复制代码
package com.github.subei.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/HelloController") // 类级别的请求路径
public class HelloController {

    // 方法级别的请求路径
    // 真实访问地址: /your_project/HelloController/hello
    @RequestMapping("/hello")
    public String sayHello(Model model) {
        // 向模型中添加属性 msg,可以在 JSP 中获取
        model.addAttribute("msg", "Hello, SpringMVC! (Annotation Version)");
        // 返回视图名
        // WEB-INF/jsp/hello.jsp
        return "hello";
    }
}
  • @Controller:将该类声明为 Spring IoC 容器管理的一个控制器 Bean。
  • @RequestMapping:映射 URL 请求到具体的处理方法。
  • Model 参数:用于在控制器和视图之间传递数据。
  • 返回 String:该字符串会被视图解析器解析为具体的视图路径。

在WEB-INF/ jsp目录中创建hello.jsp , 视图可以直接取出并展示从Controller带回的信息;

可以通过EL表示取出Model中存放的值,或者对象;

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>SpringMVC</title>
</head>
<body>
${msg}
</body>
</html>

5. 启动测试

访问 http://localhost:8080/your_project/HelloController/hello 即可。

3.3 小结

Spring MVC 核心配置步骤

  1. 创建 Web 项目并导入依赖。
  2. web.xml 中注册 DispatcherServlet
  3. 创建 Spring MVC 核心配置文件。
  4. 配置处理器映射器处理器适配器视图解析器
  5. 编写 Controller 类来处理请求。
  6. 创建 JSP 视图来展示数据。
  7. 配置 Tomcat 并测试。
    注解驱动的优势

在现代开发中,我们通常只需要手动配置视图解析器 ,而处理器映射器处理器适配器 通过开启注解驱动 (<mvc:annotation-driven />) 即可自动配置,大大简化了 XML 文件。

4. Controller 与 RESTful 风格

4.1 Controller 接口实现

这是较老的一种方式,控制器类必须实现 org.springframework.web.servlet.mvc.Controller 接口。

java 复制代码
package com.github.subei.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// 实现 Controller 接口,表明这是一个控制器
public class ControllerTest implements Controller {
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "实现了 Controller 接口");
        mv.setViewName("test");
        return mv;
    }
}

然后在 XML 中通过 <bean name="/t1" ... /> 的方式注册。

缺点 :一个控制器类只能处理一个请求(一个 handleRequest 方法),如果要处理多个请求,需要定义多个 Controller 类,非常繁琐。

4.2 @Controller 注解

使用 @Controller 注解是目前的主流方式。Spring 会通过组件扫描 (<context:component-scan>) 找到并注册这些控制器。

java 复制代码
package com.github.subei.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller // 将该类声明为一个控制器
public class ControllerTest2 {
    
    @RequestMapping("/t2") // 映射请求路径
    public String index(Model model) {
        // Spring MVC 会自动实例化一个 Model 对象用于向视图传值
        model.addAttribute("msg", "使用了 @Controller 注解");
        // 返回视图位置
        return "test";
    }
}

这种方式下,一个控制器类可以包含多个处理方法,每个方法通过 @RequestMapping 映射不同的 URL,实现了逻辑的聚合,更加灵活和高效。

4.3 @RequestMapping 注解

@RequestMapping 是 Spring MVC 中最核心的注解之一,用于将 URL 请求映射到控制器类或处理方法上。

  • 用于类上:作为父路径,类中所有方法的请求路径都在这个父路径之下。
  • 用于方法上:定义具体的请求路径。
java 复制代码
@Controller
@RequestMapping("/s1") // 父路径
public class ControllerTest3 {
    
    @RequestMapping("/y1") // 子路径
    public String test(Model model) {
        model.addAttribute("msg", "访问了 s1/y1");
        return "test";
    }
}

访问路径为:http://localhost:8080/your_project/s1/y1

4.4 RestFul 风格详解

REST (Representational State Transfer) 是一种软件架构风格,而非标准或协议。它强调通过统一的接口(URL)和标准的 HTTP 方法(GET, POST, PUT, DELETE)来对网络资源进行操作。

HTTP 方法 传统 URL RESTful URL 操作
GET /item/queryItem.action?id=1 /item/1 查询
POST /item/saveItem.action /item 新增
PUT /item/updateItem.action /item 更新
DELETE /item/deleteItem.action?id=1 /item/1 删除

在 Spring MVC 中,我们通过 @PathVariable 注解来支持 RESTful 风格的 URL。

示例:

java 复制代码
@Controller
public class RestFulController {

    // 传统方式: http://localhost:8080/add?p1=1&p2=9
    // RESTful方式: http://localhost:8080/add/1/9

    @RequestMapping("/add/{p1}/{p2}")
    public String index(@PathVariable int p1, @PathVariable String p2, Model model) {
        String result = p1 + p2;
        model.addAttribute("msg", "RESTful 结果:" + result);
        return "test";
    }
}

@PathVariable 注解可以将 URL 模板中的变量 ({p1}) 绑定到方法的参数上。

使用 method 属性指定请求类型
@RequestMappingmethod 属性可以限定处理的 HTTP 请求类型。

java 复制代码
// 只处理 GET 请求
@RequestMapping(value = "/home", method = RequestMethod.GET)
public String index2(Model model) {
    model.addAttribute("msg", "这是一个 GET 请求");
    return "test";
}

如果使用非 GET 方式访问,服务器会返回 405 Method Not Allowed 错误。

为了简化,Spring 提供了更具体的组合注解:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping

它们分别是 @RequestMapping(method = ...) 的快捷方式,语义更清晰。

4.5 程序员的锦囊妙计:小黄鸭调试法

这是一个非常有效且有趣的调试技巧,源自《程序员修炼之道》。

核心思想:当你遇到一个棘手的 Bug 时,找一个(不存在的)同事------比如一只橡皮小黄鸭,然后耐心地、一行一行地向它解释你的代码是做什么的,以及为什么这么做。

在这个向外解释的过程中,你被迫整理自己的思路,审视每一行代码的逻辑。很多时候,当你讲到某一步时,你会突然发现:"哦!原来是这里错了!"

下次卡住时,不妨试试这个方法。

5. 数据处理与页面跳转

5.1 ModelAndView

这是最经典的方式,一个 ModelAndView 对象既包含模型数据,也包含视图信息。

java 复制代码
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ModelAndView mv = new ModelAndView();
    mv.addObject("msg", "使用 ModelAndView"); // 添加模型数据
    mv.setViewName("test"); // 设置视图名
    return mv;
}

5.2 原生 Servlet API

在 Controller 方法中,可以直接声明 HttpServletRequestHttpServletResponse 参数,Spring MVC 会自动注入它们。这允许我们使用原生的 Servlet API 进行操作,但通常不推荐,因为它会增加与 Servlet API 的耦合。

java 复制代码
@Controller
public class ModelTest1 {
    @RequestMapping("/m2/t3")
    public void test3(HttpServletRequest req, HttpServletResponse rsp) throws Exception {
        // 手动转发
        req.setAttribute("msg", "使用原生 Servlet API 转发");
        req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req, rsp);
    }
}

5.3 Spring MVC 的转发与重定向

Spring MVC 提供了更简洁的方式来实现页面的转发和重定向,只需在返回的视图字符串前加上特定前缀即可。

java 复制代码
@Controller
public class ResultSpringMVC {
    @RequestMapping("/p1/t1")
    public String test4(Model model) {
        model.addAttribute("msg", "转发到 test 页面");
        // 转发 (forward)
        // 配合视图解析器,只需返回视图名
        return "test"; 
    }

    @RequestMapping("/p1/t2")
    public String test5(Model model) {
        model.addAttribute("msg", "重定向到 index.jsp");
        // 重定向 (redirect)
        // 不经过视图解析器,需要写完整路径
        return "redirect:/index.jsp";
    }
}
  • 转发return "viewName";return "forward:/WEB-INF/jsp/view.jsp";
    • URL 地址不变,请求是一次。
    • 可以共享 request 中的数据。
  • 重定向return "redirect:/path/to/resource";
    • URL 地址会改变,请求是两次。
    • 无法共享 request 中的数据。

5.4 处理前端提交数据

Spring MVC 能够非常方便地将前端请求中的参数绑定到 Controller 方法的参数上。

1. 参数名与方法参数名一致

如果请求参数名与方法参数名相同,Spring MVC 会自动绑定。

URL: /user/t1?name=subei

java 复制代码
@GetMapping("/t1")
public String test(String name, Model model) {
    // 此时 name 的值就是 "subei"
    System.out.println("接收到参数: " + name);
    model.addAttribute("msg", "接收到的 name: " + name);
    return "test";
}

2. 参数名不一致 (@RequestParam)

使用 @RequestParam("requestParamName") 来指定映射关系。

URL: /user/t2?username=subei

java 复制代码
@GetMapping("/t2")
public String hello(@RequestParam("username") String name, Model model) {
    // 此时 name 的值也是 "subei"
    System.out.println("接收到参数: " + name);
    model.addAttribute("msg", "接收到的 username: " + name);
    return "test";
}

3. 提交的是一个对象

如果前端提交的参数与一个 Java 对象的属性名一一对应,可以直接使用该对象作为方法参数,Spring MVC 会自动将参数封装到对象中。

URL: /user/t3?id=1&name=subei&age=21

java 复制代码
// User.java 实体类
public class User {
    private int id;
    private String name;
    private int age;
    // ...getters and setters
}

// Controller 方法
@GetMapping("/t3")
public String user(User user, Model model) {
    // Spring MVC 会自动创建 User 对象并注入 id, name, age
    System.out.println("接收到对象: " + user);
    model.addAttribute("msg", "接收到的 User 对象: " + user);
    return "test";
}

5.5 数据显示到前端

将数据从 Controller 传递到 View 有多种方式:

方式 描述
ModelAndView 既包含模型数据 (addObject),也包含视图信息 (setViewName)。
Model 一个接口,提供了 addAttribute 方法,专注于传递数据。
ModelMap Model 的实现类,本质是一个 LinkedHashMap,功能与 Model 类似。

在注解驱动的开发中,使用 Model 作为方法参数是最常见和推荐的方式。

5.6 乱码问题解决方案

在处理 POST 请求中的中文字符时,经常会遇到乱码问题。Spring MVC 提供了 CharacterEncodingFilter 来统一解决。

web.xml 中配置该过滤器:

xml 复制代码
<filter>
    <filter-name>encoding</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>
</filter>
<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

注意 :对于 GET 请求的乱码,此过滤器可能支持不佳,通常需要在 Tomcat 的 server.xml 中配置 <Connector URIEncoding="UTF-8" />

6. JSON 交互处理

6.1 什么是 JSON

JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,因其简洁、清晰、易于解析的特点,已成为现代 Web 应用(尤其是前后端分离架构)数据传输的事实标准。

  • 本质:一个遵循特定格式的字符串。
  • 语法
    • 对象用花括号 {} 表示。
    • 数组用方括号 [] 表示。
    • 数据为 "键/值" 对,键名必须用双引号包裹。

JavaScript 对象与 JSON 字符串的互转

javascript 复制代码
// JS 对象
var user = { name: "subei", age: 4 };

// JS 对象 -> JSON 字符串
var jsonStr = JSON.stringify(user); // '{"name":"subei","age":4}'

// JSON 字符串 -> JS 对象
var userObj = JSON.parse(jsonStr); // { name: "subei", age: 4 }

6.2 Jackson 框架使用

Jackson 是 Spring MVC 默认使用的 JSON 处理库,功能强大且性能优异。

1. 引入依赖

xml 复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>

2. 使用 @ResponseBody 返回 JSON

在 Controller 方法上添加 @ResponseBody 注解,Spring MVC 会自动将方法的返回值(通常是 Java 对象或集合)通过 Jackson 转换为 JSON 字符串,并直接写入 HTTP 响应体中,而不是去查找视图

java 复制代码
import com.fasterxml.jackson.core.JsonProcessingException;
import com.github.subei.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {
    
    @RequestMapping("/j1")
    @ResponseBody // 此注解让方法直接返回字符串,而不是跳转视图
    public String json1() throws JsonProcessingException {
        User user = new User("哇哈哈4号", 22, "man");
        // Spring MVC 在引入 Jackson 依赖后,会自动处理对象到 JSON 的转换
        // 为了演示,这里手动转换,实际开发中直接 return user 即可
        return new ObjectMapper().writeValueAsString(user);
    }

    @RequestMapping("/j2")
    @ResponseBody
    public List<User> json2() {
        List<User> userList = new ArrayList<>();
        // ... 添加用户到列表
        return userList; // Spring MVC 会自动将 List 转换为 JSON 数组
    }
}

3. 乱码问题及代码优化

  • @ResponseBody 乱码 :可以通过在 @RequestMapping 中设置 produces 属性来解决。

    java 复制代码
    @RequestMapping(value = "/j1", produces = "application/json;charset=utf-8")
  • 统一配置解决乱码 :在 springmvc-servlet.xml 中配置消息转换器,一劳永逸。

    xml 复制代码
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                ...
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
  • @RestController :如果一个 Controller 中的所有方法都返回 JSON 数据,可以在类上使用 @RestController 注解,它相当于 @Controller + @ResponseBody 的组合,这样每个方法就无需再单独添加 @ResponseBody

4. 处理日期格式

Jackson 默认将 java.util.Date 对象序列化为时间戳(毫秒数)。为了输出格式化的日期字符串,可以进行如下配置:

java 复制代码
public String json4() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    // 1. 关闭时间戳输出
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    // 2. 自定义日期格式
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    mapper.setDateFormat(sdf);

    Date date = new Date();
    return mapper.writeValueAsString(date);
}

也可以将这些配置封装成一个 JsonUtils 工具类,方便复用。

6.3 FastJson 框架简介

FastJson 是阿里巴巴开源的一款高性能 JSON 库,其 API 设计简洁,使用方便。

1. 引入依赖

xml 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>

2. 核心 API

  • JSON.toJSONString(object): 将 Java 对象转换为 JSON 字符串。
  • JSON.parseObject(jsonStr, Class.class): 将 JSON 字符串转换为 Java 对象。
  • JSON.toJSON(object): 将 Java 对象转换为 JSONObject (本质是 Map)。
  • JSON.toJavaObject(jsonObject, Class.class): 将 JSONObject 转换为 Java 对象。
java 复制代码
public class FastJsonDemo {
    public static void main(String[] args) {
        User user = new User("FastJson 用户", 20, "man");
        
        // Java 对象 -> JSON 字符串
        String jsonString = JSON.toJSONString(user);
        System.out.println(jsonString);

        // JSON 字符串 -> Java 对象
        User parsedUser = JSON.parseObject(jsonString, User.class);
        System.out.println(parsedUser);
    }
}

7. 整合 SSM 框架实战

SSM (Spring + Spring MVC + MyBatis) 是 Java Web 开发的经典组合。下面通过一个书籍管理系统的案例,演示如何从零搭建一个完整的 SSM 项目。

7.1 基本环境搭建

  1. 创建数据库和表

    sql 复制代码
    CREATE DATABASE `ssmbuild`;
    USE `ssmbuild`;
    DROP TABLE IF EXISTS `books`;
    CREATE TABLE `books` (
    	`bookID` INT ( 10 ) NOT NULL AUTO_INCREMENT COMMENT '书id',
    	`bookName` VARCHAR ( 100 ) NOT NULL COMMENT '书名',
    	`bookCounts` INT ( 11 ) NOT NULL COMMENT '数量',
    	`detail` VARCHAR ( 200 ) NOT NULL COMMENT '描述',
    	KEY `bookID` ( `bookID` ) 
    ) ENGINE = INNODB DEFAULT CHARSET = utf8;
    INSERT INTO `books` ( `bookID`, `bookName`, `bookCounts`, `detail` )
    VALUES
    	( 1, 'Java', 1, '从入门到放弃' ),
    	( 2, 'MySQL', 10, '从删库到跑路' ),
    	( 3, 'Linux', 5, '从进门到进牢' );
  2. 创建 Maven Web 项目 (ssmbuild),添加对web的支持。

  3. 导入 pom.xml 依赖 :包括 junit, mysql-connector-java, c3p0, servlet-api, jsp-api, jstl, mybatis, mybatis-spring, spring-webmvc, spring-jdbc

    xml 复制代码
    <dependencies>
       <!--Junit-->
       <dependency>
           <groupId>junit</groupId>
           <artifactId>junit</artifactId>
           <version>4.12</version>
       </dependency>
       <!--数据库驱动-->
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>5.1.47</version>
       </dependency>
       <!-- 数据库连接池 -->
       <dependency>
           <groupId>com.mchange</groupId>
           <artifactId>c3p0</artifactId>
           <version>0.9.5.2</version>
       </dependency>
    
       <!--Servlet - JSP -->
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>servlet-api</artifactId>
           <version>2.5</version>
       </dependency>
       <dependency>
           <groupId>javax.servlet.jsp</groupId>
           <artifactId>jsp-api</artifactId>
           <version>2.2</version>
       </dependency>
       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>jstl</artifactId>
           <version>1.2</version>
       </dependency>
    
       <!--Mybatis-->
       <dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis</artifactId>
           <version>3.5.2</version>
       </dependency>
       <dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis-spring</artifactId>
           <version>2.0.2</version>
       </dependency>
    
       <!--Spring-->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-webmvc</artifactId>
           <version>5.1.9.RELEASE</version>
       </dependency>
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-jdbc</artifactId>
           <version>5.1.9.RELEASE</version>
       </dependency>
    </dependencies>
  4. 配置 Maven 资源过滤

    xml 复制代码
    <build>
      <resources>
          <resource>
              <directory>src/main/java</directory>
              <includes>
                  <include>**/*.properties</include>
                  <include>**/*.xml</include>
              </includes>
              <filtering>false</filtering>
          </resource>
          <resource>
              <directory>src/main/resources</directory>
              <includes>
                  <include>**/*.properties</include>
                  <include>**/*.xml</include>
              </includes>
              <filtering>false</filtering>
          </resource>
      </resources>
    </build>
  5. 建立项目结构 :创建 pojo, dao, service, controller 等包。

    mybatis-config.xml

    xml 复制代码
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
    </configuration>

    applicationContext.xml

    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"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    </beans>

7.2 MyBatis 层配置

  1. database.properties: 配置数据库连接信息。

    properties 复制代码
    jdbc.driver=com.mysql.jdbc.Driver
    # 如果使用的是MySQL8.0+,增加一个时区的配置。
    jdbc.url=jdbc:mysql://localhost:3306/ssmbuild?useSSL=true&useUnicode=true&characterEncoding=utf8
    jdbc.username=root
    jdbc.password=root

    IDEA关联数据库

  2. mybatis-config.xml : 配置类型别名 (typeAliases) 等。

    xml 复制代码
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <typeAliases>
            <package name="com.github.subei.pojo"/>
        </typeAliases>
        
        <mappers>
            <mapper class="com.github.subei.dao.BookMapper"/>
        </mappers>
    
    </configuration>
  3. Books.java Pojo : 创建与 books 表对应的实体类。

    java 复制代码
    package com.github.subei.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Books {
        private int bookID;
        private String bookName;
        private int bookCounts;
        private String detail;
    
    }
  4. BookMapper.java 接口: 定义数据库操作方法(增删改查)。

    java 复制代码
    package com.github.subei.dao;
    
    import com.github.subei.pojo.Books;
    import org.apache.ibatis.annotations.Param;
    
    import java.util.List;
    
    public interface BookMapper {
        // 增加一个Book
        int addBook(Books book);
    
        // 根据id删除一个Book
        int deleteBookById(@Param("bookID") int id);
    
        // 更新一个Book
        int updateBook(Books books);
    
        // 根据id查询,返回一个Book
        Books queryBookById(@Param("bookID") int id);
    
        // 查询全部Book,返回list集合
        List<Books> queryAllBook();
    }
  5. BookMapper.xml: 编写具体的 SQL 语句。

    xml 复制代码
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.github.subei.dao.BookMapper">
    
        <!--增加一个Book-->
        <insert id="addBook" parameterType="Books">
          insert into ssmbuild.books(bookName,bookCounts,detail)
          values (#{bookName}, #{bookCounts}, #{detail});
       </insert>
    
        <!--根据id删除一个Book-->
        <delete id="deleteBookById" parameterType="int">
          delete from ssmbuild.books where bookID=#{bookID};
       </delete>
    
        <!--更新Book-->
        <update id="updateBook" parameterType="Books">
          update ssmbuild.books
          set bookName = #{bookName},bookCounts = #{bookCounts},detail = #{detail}
          where bookID = #{bookID};
       </update>
    
        <!--根据id查询,返回一个Book-->
        <select id="queryBookById" resultType="Books">
          select * from ssmbuild.books
          where bookID = #{bookID};
       </select>
    
        <!--查询全部Book-->
        <select id="queryAllBook" resultType="Books">
          SELECT * from ssmbuild.books;
       </select>
    
    </mapper>
  6. BookService.java 接口与 BookServiceImpl.java 实现类: 编写业务逻辑。

    java 复制代码
    package com.github.subei.service;
    
    import com.github.subei.pojo.Books;
    
    import java.util.List;
    
    // BookService:底下需要去实现,调用dao层
    public interface BookService {
        // 增加一个Book
        int addBook(Books book);
        // 根据id删除一个Book
        int deleteBookById(int id);
        // 更新Book
        int updateBook(Books books);
        // 根据id查询,返回一个Book
        Books queryBookById(int id);
        // 查询全部Book,返回list集合
        List<Books> queryAllBook();
    }
    java 复制代码
    package com.github.subei.service;
    
    import com.github.subei.dao.BookMapper;
    import com.github.subei.pojo.Books;
    
    import java.util.List;
    
    public class BookServiceImpl implements BookService {
    
        // 调用dao层的操作,设置一个set接口,方便Spring管理
        private BookMapper bookMapper;
    
        public void setBookMapper(BookMapper bookMapper) {
            this.bookMapper = bookMapper;
        }
    
        public int addBook(Books book) {
            return bookMapper.addBook(book);
        }
    
        public int deleteBookById(int id) {
            return bookMapper.deleteBookById(id);
        }
    
        public int updateBook(Books books) {
            return bookMapper.updateBook(books);
        }
    
        public Books queryBookById(int id) {
            return bookMapper.queryBookById(id);
        }
    
        public List<Books> queryAllBook() {
            return bookMapper.queryAllBook();
        }
    }

    OK,到此,底层需求操作编写完毕!

7.3 Spring 业务层配置

这一层主要负责整合 MyBatis 和配置业务层的 Bean 及事务。

1. spring-dao.xml

  • 加载 database.properties : <context:property-placeholder>
  • 配置数据源 (DataSource): 使用 c3p0 连接池。
  • 配置 SqlSessionFactoryBean : 注入数据源,并关联 mybatis-config.xml
  • 配置 MapperScannerConfigurer : 自动扫描 dao 包下的 Mapper 接口,并为其生成代理实现,注入到 Spring 容器中。
xml 复制代码
<!-- spring-dao.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置整合mybatis -->
    <!-- 1.关联数据库文件 -->
    <context:property-placeholder location="classpath:database.properties"/>

    <!-- 2.数据库连接池 -->
    <!--数据库连接池
        dbcp 半自动化操作 不能自动连接
        c3p0 自动化操作(自动的加载配置文件 并且设置到对象里面)
    -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 配置连接池属性 -->
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>

        <!-- c3p0连接池的私有属性 -->
        <property name="maxPoolSize" value="30"/>
        <property name="minPoolSize" value="10"/>
        <!-- 关闭连接后不自动commit -->
        <property name="autoCommitOnClose" value="false"/>
        <!-- 获取连接超时时间 -->
        <property name="checkoutTimeout" value="10000"/>
        <!-- 当获取连接失败重试次数 -->
        <property name="acquireRetryAttempts" value="2"/>
    </bean>

    <!-- 3.配置SqlSessionFactory对象 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据库连接池 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

    <!-- 4.配置扫描Dao接口包,动态实现Dao接口注入到spring容器中 -->
    <!--解释 :https://www.cnblogs.com/jpfss/p/7799806.html-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!-- 给出需要扫描Dao接口包 -->
        <property name="basePackage" value="com.github.subei.dao"/>
    </bean>

</beans>

2. spring-service.xml

  • 注册 Service 实现类 : 将 BookServiceImpl 注册为 Bean。
  • 配置声明式事务 :
    • 配置 DataSourceTransactionManager 事务管理器。
    • 使用 <tx:advice> 定义事务通知(哪些方法需要事务)。
    • 使用 <aop:config> 将事务通知织入到 Service 层的方法上。
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"
       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">

    <!-- 扫描service相关的bean -->
    <context:component-scan base-package="com.github.subei.service" />
    <!-- 1. 注册 Service -->
    <bean id="BookServiceImpl" class="com.github.subei.service.BookServiceImpl">
        <property name="bookMapper" ref="bookMapper"/>
    </bean>
    <!-- 2. 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 3. 配置事务通知及 AOP -->
    ...
</beans>

7.4 Spring MVC 表现层配置

  1. web.xml:

    • 注册 DispatcherServlet,并指定其加载总的 Spring 配置文件 applicationContext.xml
    • 配置 CharacterEncodingFilter 解决乱码问题。
    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_4_0.xsd"
             version="4.0">
    
        <!--DispatcherServlet-->
        <servlet>
            <servlet-name>DispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <!--一定要注意:我们这里加载的是总的配置文件,之前被这里坑了!-->
                <param-value>classpath:applicationContext.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>DispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <!--encodingFilter-->
        <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>
        </filter>
        <filter-mapping>
            <filter-name>encodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <!--Session过期时间-->
        <session-config>
            <session-timeout>15</session-timeout>
        </session-config>
    
    </web-app>
  2. spring-mvc.xml:

    • 开启注解驱动 (<mvc:annotation-driven />)。
    • 配置静态资源处理 (<mvc:default-servlet-handler />)。
    • 配置视图解析器 (InternalResourceViewResolver)。
    • 扫描 controller 包 (<context:component-scan>)。
    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
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!-- 配置SpringMVC -->
        <!-- 1.开启SpringMVC注解驱动 -->
        <mvc:annotation-driven />
        <!-- 2.静态资源默认servlet配置-->
        <mvc:default-servlet-handler/>
    
        <!-- 3.配置jsp 显示ViewResolver视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
            <property name="prefix" value="/WEB-INF/jsp/" />
            <property name="suffix" value=".jsp" />
        </bean>
    
        <!-- 4.扫描web相关的bean -->
        <context:component-scan base-package="com.github.subei.controller" />
    
    </beans>
  3. applicationContext.xml (总配置文件):

    • 使用 <import> 标签将 spring-dao.xml, spring-service.xml, spring-mvc.xml 整合在一起。
    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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <import resource="spring-dao.xml"/>
        <import resource="spring-service.xml"/>
        <import resource="spring-mvc.xml"/>
    
    </beans>

    配置文件,暂时结束!Controller 和 视图层编写

7.5 案例功能实现

  1. BookController.java:

    • 注入 BookService
    • 编写处理查询全部书籍跳转到添加页面添加书籍跳转到修改页面修改书籍删除书籍按名查询等请求的方法。
    java 复制代码
    package com.github.subei.controller;
    
    import com.github.subei.pojo.Books;
    import com.github.subei.service.BookService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import java.util.List;
    
    @Controller
    @RequestMapping("/book")
    public class BookController {
    
        @Autowired
        @Qualifier("BookServiceImpl")
        private BookService bookService;
    
        @RequestMapping("/allBook")
        public String list(Model model) {
            List<Books> list = bookService.queryAllBook();
            model.addAttribute("list", list);
            return "allBook";
        }
        
        @RequestMapping("/toAddBook")
        public String toAddPaper() {
            return "addBook";
        }
    
        @RequestMapping("/addBook")
        public String addPaper(Books books) {
            System.out.println(books);
            bookService.addBook(books);
            return "redirect:/book/allBook";
        }
        
        @RequestMapping("/toUpdateBook")
        public String toUpdateBook(Model model, int id) {
            Books books = bookService.queryBookById(id);
            System.out.println(books);
            model.addAttribute("book",books );
            return "updateBook";
        }
    
        @RequestMapping("/updateBook")
        public String updateBook(Model model, Books book) {
            System.out.println(book);
            bookService.updateBook(book);
            Books books = bookService.queryBookById(book.getBookID());
            model.addAttribute("books", books);
            return "redirect:/book/allBook";
        }
        
        @RequestMapping("/del/{bookID}")
        public String deleteBook(@PathVariable("bookID") int id) {
            bookService.deleteBookById(id);
            return "redirect:/book/allBook";
        }
    }
  2. JSP 视图:

    • index.jsp (首页)

      jsp 复制代码
      <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
      <!DOCTYPE HTML>
      <html>
      <head>
        <title>首页</title>
        <style type="text/css">
          a {
            text-decoration: none;
            color: black;
            font-size: 18px;
          }
          h3 {
            width: 180px;
            height: 38px;
            margin: 100px auto;
            text-align: center;
            line-height: 38px;
            background: deepskyblue;
            border-radius: 4px;
          }
        </style>
      </head>
      <body>
      
      <h3>
        <a href="${pageContext.request.contextPath}/book/allBook">点击进入列表页</a>
      </h3>
      </body>
      </html>
    • allBook.jsp (书籍列表页)

      jsp 复制代码
      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <html>
      <head>
         <title>书籍列表</title>
         <meta name="viewport" content="width=device-width, initial-scale=1.0">
         <!-- 引入 Bootstrap -->
         <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
      </head>
      <body>
      
      <div class="container">
      
         <div class="row clearfix">
             <div class="col-md-12 column">
                 <div class="page-header">
                     <h1>
                         <small>书籍列表 ------ 显示所有书籍</small>
                     </h1>
                 </div>
             </div>
         </div>
      
         <div class="row">
             <div class="col-md-4 column">
                 <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增书籍</a>
             </div>
         </div>
      
         <div class="row clearfix">
             <div class="col-md-12 column">
                 <table class="table table-hover table-striped">
                     <thead>
                     <tr>
                         <th>书籍编号</th>
                         <th>书籍名字</th>
                         <th>书籍数量</th>
                         <th>书籍详情</th>
                         <th>操作</th>
                     </tr>
                     </thead>
      
                     <tbody>
                     <c:forEach var="book" items="${requestScope.get('list')}">
                         <tr>
                             <td>${book.getBookID()}</td>
                             <td>${book.getBookName()}</td>
                             <td>${book.getBookCounts()}</td>
                             <td>${book.getDetail()}</td>
                             <td>
                                 <a href="${pageContext.request.contextPath}/book/toUpdateBook?id=${book.getBookID()}">更改</a> |
                                 <a href="${pageContext.request.contextPath}/book/del/${book.getBookID()}">删除</a>
                             </td>
                         </tr>
                     </c:forEach>
                     </tbody>
                 </table>
             </div>
         </div>
      </div>
    • addBook.jsp (添加页)

      jsp 复制代码
      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      
      <html>
      <head>
         <title>新增书籍</title>
         <meta name="viewport" content="width=device-width, initial-scale=1.0">
         <!-- 引入 Bootstrap -->
         <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
      </head>
      <body>
      <div class="container">
      
         <div class="row clearfix">
             <div class="col-md-12 column">
                 <div class="page-header">
                     <h1>
                         <small>新增书籍</small>
                     </h1>
                 </div>
             </div>
         </div>
         <form action="${pageContext.request.contextPath}/book/addBook" method="post">
            书籍名称:<input type="text" name="bookName"><br><br><br>
            书籍数量:<input type="text" name="bookCounts"><br><br><br>
            书籍详情:<input type="text" name="detail"><br><br><br>
             <input type="submit" value="添加">
         </form>
      
      </div>
    • updateBook.jsp (修改页)

      jsp 复制代码
      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <html>
      <head>
          <title>修改信息</title>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <!-- 引入 Bootstrap -->
          <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
      </head>
      <body>
      <div class="container">
      
          <div class="row clearfix">
              <div class="col-md-12 column">
                  <div class="page-header">
                      <h1>
                          <small>修改信息</small>
                      </h1>
                  </div>
              </div>
          </div>
      
          <form action="${pageContext.request.contextPath}/book/updateBook" method="post">
              <input type="hidden" name="bookID" value="${book.getBookID()}"/>
              书籍名称:<input type="text" name="bookName" value="${book.getBookName()}"/>
              书籍数量:<input type="text" name="bookCounts" value="${book.getBookCounts()}"/>
              书籍详情:<input type="text" name="detail" value="${book.getDetail() }"/>
              <input type="submit" value="提交"/>
          </form>
      
      </div>
  1. BookController 类编写 , 方法四:删除书籍
java 复制代码
    @RequestMapping("/del/{bookID}")
    public String deleteBook(@PathVariable("bookID") int id) {
        bookService.deleteBookById(id);
        return "redirect:/book/allBook";
    }
  1. 书籍查询功能
  • 修改前端页面,allBook.jsp页面
jsp 复制代码
        <div class="col-md-4 column"></div>
        <div class="form-inline">
            <%-- 查询书籍 --%>
            <form action="" method="" style="float: right">
                <input type="text" name="queryBookName" class="form-control" placeholder="请输入需要查询的书籍名">
                <input type="submit" value="查询" class="btn btn-primary">
            </form>
        </div>
  • 修改DAO层:BookMapper.java
java 复制代码
    // 查询,搜索书籍
    Books queryBookByName(@Param("bookName")String bookName);
  • 修改DAO层:BookMapper.xml
xml 复制代码
    <!--搜索Book-->
    <select id="queryBookByName" resultType="Books">
      SELECT * from ssmbuild.books where bookName = #{bookName};
   </select>
  • 修改service层:BookService.java
java 复制代码
    // 查询,搜索书籍
    Books queryBookByName(String bookName);
  • 修改service层:BookServiceImpl.java
java 复制代码
    public Books queryBookByName(String bookName) {
        return bookMapper.queryBookByName(bookName);
    }
  • 修改controller层:BookController.java
java 复制代码
    // 查询书籍
    @RequestMapping("/queryBook")
    public String queryBook(String queryBookName,Model model){
        Books books = bookService.queryBookByName(queryBookName);
        List<Books> list = new ArrayList<Books>();
        list.add(books);
        model.addAttribute("list", list);
        return "allBook";
    }
  • 修改前端:allBook.jsp
jsp 复制代码
        <div class="col-md-4 column"></div>
        <div class="form-inline">
            <%-- 查询书籍 --%>
            <form action="${pageContext.request.contextPath}/book/queryBook" method="post" style="float: right">
                <input type="text" name="queryBookName" class="form-control" placeholder="请输入需要查询的书籍名">
                <input type="submit" value="查询" class="btn btn-primary">
            </form>
        </div>
  • 运行测试:

如果查询失败,需要显示全部书籍页面?

  • 修改前端页面allBook.jsp
jsp 复制代码
    <div class="row">
        <div class="col-md-4 column">
            <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增书籍</a>
            <a class="btn btn-primary" href="${pageContext.request.contextPath}/book/allBook">显示全部书籍</a>
        </div>
        <div class="col-md-4 column"></div>
        <div class="form-inline">
            <%-- 查询书籍 --%>
            <form action="${pageContext.request.contextPath}/book/queryBook" method="post" style="float: right">
                <span style="color: red;font-weight: bold" >${error}</span>
                <input type="text" name="queryBookName" class="form-control" placeholder="请输入需要查询的书籍名">
                <input type="submit" value="查询" class="btn btn-primary">
            </form>
        </div>
    </div>
  • 修改后台BookController.java
java 复制代码
    // 查询书籍
    @RequestMapping("/queryBook")
    public String queryBook(String queryBookName,Model model){
        Books books = bookService.queryBookByName(queryBookName);
        List<Books> list = new ArrayList<Books>();
        list.add(books);

        if(books==null){
            list = bookService.queryAllBook();
            model.addAttribute("error","未查到");
        }

        model.addAttribute("list", list);
        return "allBook";
    }

配置Tomcat,进行运行!

项目结构图

通过以上步骤,一个功能完整的 SSM 项目就搭建完成了。

8. Ajax 技术

8.1 初识 Ajax

AJAX = Asynchronous JavaScript and XML (异步的 JavaScript 和 XML)

AJAX 是一种在无需重新加载整个网页 的情况下,能够局部更新页面内容的技术。它通过在后台与服务器进行少量数据交换,实现了网页的异步通信,极大地提升了用户体验。

  • AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
  • AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
  • Ajax 不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术。
  • 在 2005 年,Google 通过其 Google Suggest 使 AJAX 变得流行起来。Google Suggest能够自动帮你完成搜索单词。
  • Google Suggest 使用 AJAX 创造出动态性极强的 web 界面:当您在谷歌的搜索框输入关键字时,JavaScript 会把这些字符发送到服务器,然后服务器会返回一个搜索建议的列表。
  • 和国内百度的搜索框一样!
  • 传统的网页(即不用ajax技术的网页),想要更新内容或者提交一个表单,都需要重新加载整个网页。
  • 使用ajax技术的网页,通过在后台服务器进行少量的数据交换,就可以实现异步局部更新。
  • 使用Ajax,用户可以创建接近本地桌面应用的直接、高可用、更丰富、更动态的Web用户界面。

伪造Ajax -- 可以使用前端的一个标签来伪造一个ajax的样子。iframe标签

  1. 新建一个module :springmvc-06-ajax , 导入web支持!
  1. 测试项目是否成功!
  • 配置web.xml
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_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <filter>
        <filter-name>encoding</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>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>
  • 配置applicationContext.xml
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
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 -->
    <context:component-scan base-package="com.github.subei.controller"/>

    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!-- 后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>

    <!-- JSON乱码问题 -->
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                        <property name="failOnEmptyBeans" value="false"/>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

</beans>
  • 配置AjaxController.java
java 复制代码
package com.github.subei.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AjaxController {

    @RequestMapping("k1")
    public String test(){
        return "hello";
    }
}
  • 这一步很关键,手动添加lib
  • 测试文件!
  1. 编写一个 ajax-frame.html 使用 iframe 测试,感受下效果。
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>subei</title>
</head>
<body>
<script type="text/javascript">
    window.onload = function(){
        var myDate = new Date();
        document.getElementById('currentTime').innerText = myDate.getTime();
    };

    function LoadPage(){
        var targetUrl =  document.getElementById('url').value;
        console.log(targetUrl);
        document.getElementById("iframePosition").src = targetUrl;
    }

</script>

<div>
    <p>请输入要加载的地址:<span id="currentTime"></span></p>
    <p>
        <input id="url" type="text" value="https://www.dogedoge.com/"/>
        <input type="button" value="提交" onclick="LoadPage()">
    </p>
</div>

<div>
    <h3>加载页面位置:</h3>
    <iframe id="iframePosition" style="width: 100%;height: 500px;"></iframe>
</div>
</body>
</html>
  1. 使用IDEA开浏览器测试一下!

利用AJAX可以做:

  • 注册时,输入用户名自动检测用户是否已经存在。
  • 登陆时,提示用户名密码错误
  • 删除数据行时,将行ID发送到后台,后台在数据库中删除,数据库删除成功后,在页面DOM中将数据行也删除。
  • ...等等

8.2 结合 jQuery 使用 Ajax

原生 JavaScript 实现 Ajax 比较繁琐,通常我们会使用 jQuery 提供的 Ajax API,它对原生 XMLHttpRequest 进行了优雅的封装。

核心方法是 $.ajax(),以及其简化版如 $.get(), $.post()

  • 纯JS原生实现Ajax不作详细赘述,直接使用jquery提供的,方便学习和使用,避免重复造轮子,有兴趣的同学可以去了解下JS原生XMLHttpRequest !
  • Ajax的核心是XMLHttpRequest对象(XHR)。XHR为向服务器发送请求和解析服务器响应提供了接口。能够以异步方式从服务器获取新数据。
  • jQuery 提供多个与 AJAX 有关的方法。
  • 通过 jQuery AJAX 方法,您能够使用 HTTP Get 和 HTTP Post 从远程服务器上请求文本、HTML、XML 或 JSON -- 同时您能够把这些外部数据直接载入网页的被选元素中。
  • jQuery 不是生产者,而是大自然搬运工。
  • jQuery Ajax本质就是 XMLHttpRequest,对他进行了封装,方便调用!
js 复制代码
jQuery.ajax(...)
      部分参数:
            url:请求地址
            type:请求方式,GET、POST(1.9.0之后用method)
        headers:请求头
            data:要发送的数据
    contentType:即将发送信息至服务器的内容编码类型(默认: "application/x-www-form-urlencoded; charset=UTF-8")
          async:是否异步
        timeout:设置请求超时时间(毫秒)
      beforeSend:发送请求前执行的函数(全局)
        complete:完成之后执行的回调函数(全局)
        success:成功之后执行的回调函数(全局)
          error:失败之后执行的回调函数(全局)
        accepts:通过请求头发送给服务器,告诉服务器当前客户端可接受的数据类型
        dataType:将服务器端返回的数据转换成指定类型
          "xml": 将服务器端返回的内容转换成xml格式
          "text": 将服务器端返回的内容转换成普通文本格式
          "html": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。
        "script": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式
          "json": 将服务器端返回的内容转换成相应的JavaScript对象
        "jsonp": JSONP 格式使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数
  • 放入项目

案例测试

  1. 配置web.xml 和 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
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 -->
    <context:component-scan base-package="com.github.subei.controller"/>
    <!-- 静态资源过滤 -->
    <mvc:default-servlet-handler />
    <mvc:annotation-driven />

    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!-- 后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>

    <!-- JSON乱码问题 -->
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                        <property name="failOnEmptyBeans" value="false"/>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

</beans>
  1. 编写一个AjaxController
java 复制代码
package com.github.subei.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
public class AjaxController {

    @RequestMapping("/a1")
    public void a1(String name , HttpServletResponse response) throws IOException {
        System.out.println("a1:param=>"+name);
        if ("subei".equals(name)){
            response.getWriter().print("true");
        }else{
            response.getWriter().print("false");
        }
    }
}
  1. 导入jquery
html 复制代码
    <script src="${pageContext.request.contextPath}/statics/js/jquery-3.5.1.js"></script>
  1. 编写index.jsp测试
js 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>

    <script src="${pageContext.request.contextPath}/statics/js/jquery-3.5.1.js"></script>

    <script>
      function a(){
        $.post({
          url:"${pageContext.request.contextPath}/a1",
          data:{'name':$("#username").val()},
          success:function (data,status) {
            console.log("data=" + data);
            console.log("status=" + status);
          }
        });
      }
    </script>
  </head>
  <body>

  <%-- 失去焦点的时候,发起一个请求到后台 --%>
  用户名:<input type="text" id="txtName" onblur="a()"/>

  </body>
</html>
  1. 启动tomcat测试!打开浏览器的控制台,当我们鼠标离开输入框的时候,可以看到发出了一个ajax的请求!是后台返回给我们的结果!测试成功!

8.3 Ajax异步加载数据

  • 实体类user
java 复制代码
package com.github.subei.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private int age;
    private String sex;

}
  • 获取一个集合对象,展示到前端页面
java 复制代码
    @RequestMapping("/a2")
    public List<User> a2(){
        List<User> list = new ArrayList<User>();
        list.add(new User("哇哈哈1号",9,"男"));
        list.add(new User("哇哈哈2号",6,"男"));
        list.add(new User("哇哈哈3号",1,"男"));
        return list; // 由于@RestController注解,将list转成json格式返回
    }
  • 前端页面
html 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<input type="button" id="btn" value="获取数据"/>
<table width="80%" align="center">
    <tr>
        <td>姓名</td>
        <td>年龄</td>
        <td>性别</td>
    </tr>
    <tbody id="content">
    </tbody>
</table>

<script src="${pageContext.request.contextPath}/statics/js/jquery-3.5.1.js"></script>
<script>

    $(function () {
        $("#btn").click(function () {
            $.post("${pageContext.request.contextPath}/a2",function (data) {
                console.log(data)
                var html="";
                for (var i = 0; i <data.length ; i++) {
                    html+= "<tr>" +
                        "<td>" + data[i].name + "</td>" +
                        "<td>" + data[i].age + "</td>" +
                        "<td>" + data[i].sex + "</td>" +
                        "</tr>"
                }
                $("#content").html(html);
            });
        })
    })
</script>
</body>
</html>
  • 测试服务器

4.Ajax验证用户名体验

  • 编写Controller
java 复制代码
    @RequestMapping("/a3")
    public String a3(String name,String pwd){
        String msg = "";
        //模拟数据库中存在数据
        if (name!=null){
            if ("admin".equals(name)){
                msg = "OK";
            }else {
                msg = "用户名输入错误";
            }
        }
        if (pwd!=null){
            if ("123456".equals(pwd)){
                msg = "OK";
            }else {
                msg = "密码输入有误";
            }
        }
        return msg; //由于@RestController注解,将msg转成json格式返回
    }
  • 前端页面 login.jsp
jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>ajax</title>
    <script src="${pageContext.request.contextPath}/statics/js/jquery-3.5.1.js"></script>
    <script>

        function a1(){
            $.post({
                url:"${pageContext.request.contextPath}/a3",
                data:{'name':$("#name").val()},
                success:function (data) {
                    if (data.toString()=='OK'){
                        $("#userInfo").css("color","green");
                    }else {
                        $("#userInfo").css("color","red");
                    }
                    $("#userInfo").html(data);
                }
            });
        }
        function a2(){
            $.post({
                url:"${pageContext.request.contextPath}/a3",
                data:{'pwd':$("#pwd").val()},
                success:function (data) {
                    if (data.toString()=='OK'){
                        $("#pwdInfo").css("color","green");
                    }else {
                        $("#pwdInfo").css("color","red");
                    }
                    $("#pwdInfo").html(data);
                }
            });
        }

    </script>
</head>
<body>
<p>
    用户名:<input type="text" id="name" οnblur="a1()"/>
    <span id="userInfo"></span>
</p>
<p>
    密码:<input type="text" id="pwd" οnblur="a2()"/>
    <span id="pwdInfo"></span>
</p>
</body>
</html>
  • 测试一下效果

5.获取baidu接口Demo

html 复制代码
<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>JSONP百度搜索</title>
    <style>
        #q{
            width: 500px;
            height: 30px;
            border:1px solid #ddd;
            line-height: 30px;
            display: block;
            margin: 0 auto;
            padding: 0 10px;
            font-size: 14px;
        }
        #ul{
            width: 520px;
            list-style: none;
            margin: 0 auto;
            padding: 0;
            border:1px solid #ddd;
            margin-top: -1px;
            display: none;
        }
        #ul li{
            line-height: 30px;
            padding: 0 10px;
        }
        #ul li:hover{
            background-color: #f60;
            color: #fff;
        }
    </style>
    <script>

        // 2.步骤二
        // 定义demo函数 (分析接口、数据)
        function demo(data){
            var Ul = document.getElementById('ul');
            var html = '';
            // 如果搜索数据存在 把内容添加进去
            if (data.s.length) {
                // 隐藏掉的ul显示出来
                Ul.style.display = 'block';
                // 搜索到的数据循环追加到li里
                for(var i = 0;i<data.s.length;i++){
                    html += '<li>'+data.s[i]+'</li>';
                }
                // 循环的li写入ul
                Ul.innerHTML = html;
            }
        }

        // 1.步骤一
        window.onload = function(){
            // 获取输入框和ul
            var Q = document.getElementById('q');
            var Ul = document.getElementById('ul');

            // 事件鼠标抬起时候
            Q.onkeyup = function(){
                // 如果输入框不等于空
                if (this.value != '') {
                    // 创建标签
                    var script = document.createElement('script');
                    //给定要跨域的地址 赋值给src
                    //这里是要请求的跨域的地址 我写的是百度搜索的跨域地址
                    script.src = 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd='+this.value+'&cb=demo';
                    // 将组合好的带src的script标签追加到body里
                    document.body.appendChild(script);
                }
            }
        }
    </script>
</head>

<body>
<input type="text" id="q" />
<ul id="ul">

</ul>
</body>
</html>

9. 拦截器 (Interceptor)

9.1 概述与区别

Spring MVC 的拦截器(HandlerInterceptor)类似于 Servlet 的过滤器(Filter),用于对处理器(Controller)进行预处理和后处理。它是 AOP 思想的一种具体应用。

过滤器 (Filter) vs. 拦截器 (Interceptor)

特性 过滤器 (Filter) 拦截器 (Interceptor)
归属 Servlet 规范的一部分,任何 Java Web 项目可用 Spring MVC 框架的一部分,仅在 Spring MVC 中使用
拦截范围 可拦截所有资源(URL-Pattern /* 只拦截对 Controller 的请求,不拦截静态资源
控制粒度 较粗 更细,可以访问 Handler 等 Spring 容器中的对象

9.2 自定义拦截器

自定义拦截器需要实现 HandlerInterceptor 接口,该接口包含三个方法:

  • preHandle(req, res, handler): 在 Controller 方法执行之前 调用。
    • 返回 true:放行,执行下一个拦截器或 Controller。
    • 返回 false:请求被拦截,不再继续执行。
  • postHandle(req, res, handler, mv): 在 Controller 方法执行之后 ,视图渲染之前 调用。可以修改 ModelAndView
  • afterCompletion(req, res, handler, ex): 在整个请求完成,视图渲染之后调用。通常用于资源清理。

那如何实现拦截器呢?

想要自定义拦截器,必须实现 HandlerInterceptor 接口。

1、新建一个Moudule , springmvc-07-Interceptor , 添加web支持。

2、配置web.xml 和 applicationContext.xml 文件

  • web.xml
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_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>encoding</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>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>
  • applicationContext.xml
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
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 -->
    <context:component-scan base-package="com.github.subei.controller"/>
    <!-- 静态资源过滤 -->
    <mvc:default-servlet-handler />
    <mvc:annotation-driven />

    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!-- 后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>

    <!-- JSON乱码问题 -->
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                        <property name="failOnEmptyBeans" value="false"/>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

</beans>
  1. 编写一个拦截器
java 复制代码
package com.github.subei.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

package com.github.subei.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Interceptor implements HandlerInterceptor {

    // 在请求处理的方法之前执行
    // 如果返回true执行下一个拦截器
    // 如果返回false就不执行下一个拦截器
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        System.out.println("------------处理前------------");
        return true;
    }

    // 在请求处理方法执行之后执行
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        System.out.println("------------处理后------------");
    }

    // 在dispatcherServlet处理后执行,做清理工作.
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        System.out.println("------------清理------------");
    }
}
  1. 在applicationContext.xml的配置文件中配置拦截器
xml 复制代码
<!--关于拦截器的配置-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--/** 包括路径及其子路径-->
        <!--/admin/* 拦截的是/admin/add等等这种 , /admin/add/user不会被拦截-->
        <!--/admin/** 拦截的是/admin/下的所有-->
        <mvc:mapping path="/**"/>
        <!--bean配置的就是拦截器-->
        <bean class="com.github.subei.interceptor.Interceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  1. 编写一个Controller,接收请求
java 复制代码
package com.github.subei.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

// 测试拦截器的控制器
@Controller
public class InterceptorController {
    @RequestMapping("/k1")
    @ResponseBody
    public String testFunction() {
        System.out.println("控制器中的方法执行了==》");
        return "hello";
    }
}
  1. 前端 index.jsp
jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <a href="${pageContext.request.contextPath}/k1">拦截器测试</a>
  </body>
</html>

7、启动tomcat 测试一下!

9.3 实战:用户登录验证

实现思路

  1. 有一个登陆页面,需要写一个controller访问页面。
  2. 登陆页面有一提交表单的动作。需要在controller中处理。判断用户名密码是否正确。如果正确,向session中写入用户信息。返回登陆成功。
  3. 拦截用户请求,判断用户是否登陆。如果用户已经登陆。放行, 如果用户未登陆,跳转到登陆页面

案例测试:

  1. 编写一个登陆页面 login.jsp
jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>

<h1>登录页面</h1>
<hr>

<body>
<form action="${pageContext.request.contextPath}/user/login">
    用户名:<input type="text" name="username"> <br>
    密码:<input type="password" name="pwd"> <br>
    <input type="submit" value="提交">
</form>
</body>
</html>
  1. 编写一个Controller处理请求
java 复制代码
package com.github.subei.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

@Controller
@RequestMapping("/user")
public class UserController {

    // 跳转到登陆页面
    @RequestMapping("/jumplogin")
    public String jumpLogin() throws Exception {
        return "login";
    }

    // 跳转到成功页面
    @RequestMapping("/jumpSuccess")
    public String jumpSuccess() throws Exception {
        return "success";
    }

    // 登陆提交
    @RequestMapping("/login")
    public String login(HttpSession session, String username, String pwd) throws Exception {
        // 向session记录用户身份信息
        System.out.println("接收前端==="+username);
        session.setAttribute("user", username);
        return "success";
    }

    // 退出登陆
    @RequestMapping("logout")
    public String logout(HttpSession session) throws Exception {
        // session 过期
        session.invalidate();
        return "login";
    }
}
  1. 编写一个登陆成功的页面 success.jsp
jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<h1>登录成功页面</h1>
<hr>

${user}
<a href="${pageContext.request.contextPath}/user/logout">注销</a>
</body>
</html>
  1. 在 index.jsp 页面上测试跳转!启动Tomcat 测试,未登录也可以进入主页!
jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <%--登录--%>
  <a href="${pageContext.request.contextPath}/user/jumplogin">登录</a>
  <a href="${pageContext.request.contextPath}/user/jumpSuccess">成功页面</a>
  </body>
</html>
  1. 编写用户登录拦截器
java 复制代码
package com.github.subei.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

public class LoginInterceptor implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException, ServletException, IOException {
        // 如果是登陆页面则放行
        System.out.println("uri: " + request.getRequestURI());
        if (request.getRequestURI().contains("login")) {
            return true;
        }

        HttpSession session = request.getSession();

        // 如果用户已登陆也放行
        if(session.getAttribute("user") != null) {
            return true;
        }

        // 用户没有登陆跳转到登陆页面
        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
        return false;
    }

    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}
  1. 在applicationContext.xml的配置文件中注册拦截器
jsp 复制代码
    <!--关于拦截器的配置-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--/** 包括路径及其子路径-->
            <!--/admin/* 拦截的是/admin/add等等这种 , /admin/add/user不会被拦截-->
            <!--/admin/** 拦截的是/admin/下的所有-->
            <mvc:mapping path="/**"/>
            <!--bean配置的就是拦截器-->
            <bean class="com.github.subei.interceptor.LoginInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
  1. 再次重启Tomcat测试!

10. 文件上传和下载

10.1 准备工作

  • 文件上传是项目开发中最常见的功能之一 ,springMVC 可以很好的支持文件上传,但是SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。
  • 前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器;

对表单中的 enctype 属性做个详细的说明:

  • application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
  • multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
  • text/plain:除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
jsp 复制代码
<form action="" enctype="multipart/form-data" method="post">
   <input type="file" name="file"/>
   <input type="submit">
</form>

一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。

  • Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。
  • 而Spring MVC则提供了更简单的封装。
  • Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。
  • Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:
  • CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。

10.2 文件上传

  1. 导入文件上传的jar包,commons-fileupload , Maven会自动帮我们导入依赖包 commons-io包;
xml 复制代码
<dependencies>
    <!--文件上传-->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.3</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
        <scope>provided</scope>
    </dependency>

</dependencies>
  1. 配置bean:multipartResolver,在 applicationContext.xml 中配置。

注意!!!这个bena的id必须为:multipartResolver , 否则上传文件会报400的错误!在这里栽过坑,教训!

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
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 -->
    <context:component-scan base-package="com.github.subei.controller"/>
    <!-- 静态资源过滤 -->
    <mvc:default-servlet-handler />
    <mvc:annotation-driven />

    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!-- 后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>

    <!--文件上传配置-->
    <bean id="multipartResolver"  class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
        <property name="defaultEncoding" value="utf-8"/>
        <!-- 上传文件大小上限,单位为字节(10485760=10M) -->
        <property name="maxUploadSize" value="10485760"/>
        <property name="maxInMemorySize" value="40960"/>
    </bean>

    <!-- JSON乱码问题 -->
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                        <property name="failOnEmptyBeans" value="false"/>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

</beans>

CommonsMultipartFile 的 常用方法:

  • String getOriginalFilename():获取上传文件的原名
  • InputStream getInputStream():获取文件流
  • void transferTo(File dest):将上传文件保存到一个目录文件中
  1. 编写前端页面,index.jsp
jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
    <input type="file" name="file"/>
    <input type="submit" value="upload">
  </form>
  </body>
</html>
  1. Controller
java 复制代码
package com.github.subei.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;

@Controller
public class FileController {
    // @RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
    // 批量上传CommonsMultipartFile则为数组即可
    @RequestMapping("/upload")
    public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {

        // 获取文件名 : file.getOriginalFilename();
        String uploadFileName = file.getOriginalFilename();

        // 如果文件名为空,直接回到首页!
        if ("".equals(uploadFileName)){
            return "redirect:/index.jsp";
        }
        System.out.println("上传文件名 : "+uploadFileName);

        // 上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        // 如果路径不存在,创建一个
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        System.out.println("上传文件保存地址:"+realPath);

        InputStream is = file.getInputStream(); //文件输入流
        OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流

        // 读取写出
        int len=0;
        byte[] buffer = new byte[1024];
        while ((len=is.read(buffer))!=-1){
            os.write(buffer,0,len);
            os.flush();
        }
        os.close();
        is.close();
        return "redirect:/index.jsp";
    }
}
报错:Cannot resolve method 'getServletContext' in 'HttpServletRequest'
  • 这个报错原因是因为pom依赖导包servlet-api的版本错误:
  • 在调用request.getServletContext()的方法需要servlet-api的版本3.0以上才可以。
  • 所以把之前的版本修改一下就可以了,修改为4.0.1版本即可。如下:
xml 复制代码
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
  1. 测试上传,成功!!!

采用file.Transto 来保存上传的文件

  1. 编写Controller
java 复制代码
    /*
     * 采用file.Transto 来保存上传的文件
     */
    @RequestMapping("/upload2")
    public String  fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {

        //上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        //上传文件地址
        System.out.println("上传文件保存地址:"+realPath);

        //通过CommonsMultipartFile的方法直接写文件(注意这个时候)
        file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));

        return "redirect:/index.jsp";
    }
  1. 前端表单提交地址修改
  1. 访问提交测试,OK!

3.文件下载

文件下载步骤:

  1. 设置 response 响应头
  2. 读取文件 -- InputStream
  3. 写出文件 -- OutputStream
  4. 执行操作
  5. 关闭流 (先开后关)

代码实现:

java 复制代码
@RequestMapping(value="/download")
public String downloads(HttpServletResponse response ,HttpServletRequest request) throws Exception{
   // 要下载的图片地址
   String  path = request.getServletContext().getRealPath("/upload");
   String  fileName = "winter.jpg";

   // 1、设置response 响应头
   response.reset(); // 设置页面不缓存,清空buffer
   response.setCharacterEncoding("UTF-8"); // 字符编码
   response.setContentType("multipart/form-data"); // 二进制传输数据
   // 设置响应头
   response.setHeader("Content-Disposition",
           "attachment;fileName="+URLEncoder.encode(fileName, "UTF-8"));

   File file = new File(path,fileName);
   // 2、 读取文件--输入流
   InputStream input=new FileInputStream(file);
   // 3、 写出文件--输出流
   OutputStream out = response.getOutputStream();

   byte[] buff =new byte[1024];
   int index=0;
   // 4、执行 写出操作
   while((index= input.read(buff))!= -1){
       out.write(buff, 0, index);
       out.flush();
  }
   out.close();
   input.close();
   return null;
}
  • 前端
jsp 复制代码
  <a href="${pageContext.request.contextPath}/download">点击下载</a>
  • 测试,文件下载OK

参考

相关推荐
青鱼入云2 分钟前
spring如何通过实现BeanPostProcessor接口计算并打印每一个bean的加载耗时
spring
CodeLongBear5 分钟前
Spring Boot 与 Spring MVC 的区别与联系:从本质到实践
spring boot·spring·mvc
vivi_and_qiao9 分钟前
HTML的form表单
java·前端·html
Slaughter信仰25 分钟前
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第四章知识点问答补充及重新排版
java·开发语言·jvm
心灵宝贝28 分钟前
Mac用户安装JDK 22完整流程(Intel版dmg文件安装指南附安装包下载)
java·开发语言·macos
ta是个码农30 分钟前
Mysql——日志
java·数据库·mysql·日志
今***b43 分钟前
Python 操作 PPT 文件:从新手到高手的实战指南
java·python·powerpoint
David爱编程1 小时前
volatile 关键字详解:轻量级同步工具的边界与误区
java·后端
THMAIL2 小时前
深度剖析Spring AI源码(七):化繁为简,Spring Boot自动配置的实现之秘
人工智能·spring boot·spring