Spring MVC 学习笔记:配置、注解、RESTful、JSON、拦截器、SSM整合、文件上传下载

0 阅读22分钟

1、什么是MVC

  • MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。
  • 是将业务逻辑、数据、显示分离的方法来组织代码。
  • MVC主要作用是降低了视图与业务逻辑间的双向偶合。
  • MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。

Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao)和服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。

最典型的MVC就是JSP + servlet + javabean的模式。

Spring MVC = Spring 框架的“Web 模块”

Spring 是一个非常庞大的“家族”(也叫 Spring 生态),而 Spring 框架(Spring Framework)  是这个家族的基石。这个基石本身又是由多个模块组成的。

  • Spring Core (核心) :负责管理对象(IoC)和依赖注入(DI),是整个 Spring 的基石。
  • Spring AOP (面向切面) :负责处理日志、事务等公共逻辑。
  • Spring JDBC / ORM:负责数据库操作。
  • ...
  • Spring MVC:专门负责 Web 层 的模块。

我们为什么要学习SpringMVC呢?

Spring MVC的特点:

  1. 轻量级,简单易学
  2. 高效,基于请求响应的MVC框架
  3. 与Spring兼容性好,无缝结合
  4. 约定优于配置
  5. 功能强大:RESTful、数据验证、格式化、本地化、主题等
  6. 简洁灵活

Spring的web框架围绕DispatcherServlet [ 调度Servlet ] 设计。

DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解形式进行开发,十分简洁;

正因为SpringMVC好,简单,便捷,易学,天生和Spring无缝集成(使用SpringIoC和Aop),使用约定优于配置.能够进行简单的junit测试.支持Restful风格 .异常处理,本地化,国际化,数据验证,类型转换,拦截器 等等……所以我们要学习.

2、回顾Servlet

1、添加相关依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>7.0.6</version>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>6.1.0</version>
    <scope>provided</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/jakarta.servlet.jsp/jakarta.servlet.jsp-api -->
<dependency>
    <groupId>jakarta.servlet.jsp</groupId>
    <artifactId>jakarta.servlet.jsp-api</artifactId>
    <version>4.0.0</version>
    <scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/jakarta.servlet.jsp.jstl/jakarta.servlet.jsp.jstl-api -->
<dependency>
    <groupId>jakarta.servlet.jsp.jstl</groupId>
    <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
    <version>3.0.2</version>
    <scope>compile</scope>
</dependency>
  • 如何把一个普通模块添加web支持

image.png

2、编写Servlet

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//      获取前端参数
        String method = req.getParameter("method");
        if (method.equals("add")) {
            req.getSession().setAttribute("msg","add方法");
        }
        if(method.equals("delete")) {
            req.getSession().setAttribute("msg","delete方法");
        }
//        调用业务层(因为暂时没有业务所以没写)
//        视图转发或者重定向
        req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req, resp);

    }

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

jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%--从 JSP 的四大作用域(Page、Request、Session、Application)中,
按顺序查找名为 msg 的属性,找到后将其值输出到 HTML 页面上--%>
${msg}
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/hello" method="post">
    <input type="text" name="method">
    <input type="submit">
</form>
</body>
</html>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.zhang.servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>form.jsp</welcome-file>
    </welcome-file-list>
</web-app>

web.xml除了可以注册Servlet,还可以干嘛

1. 配置项目首页(欢迎文件列表)

当用户访问根路径(如 http://localhost:8080/app/)时,服务器按顺序查找文件并返回第一个存在的页面。

<welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>login.html</welcome-file>
</welcome-file-list>
2. 配置过滤器(Filter)

过滤器可以在请求到达 Servlet 之前,或响应返回客户端之前,执行一些预处理或后处理逻辑(例如:编码过滤、登录校验、日志记录)。

<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>
3. 配置监听器(Listener)

监听器可以监听 Servlet 上下文(应用启动/销毁)、Session(会话创建/销毁)、Request 等事件,常用于在项目启动时加载资源或初始化数据。

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
4. 配置会话超时时间

可以设置用户 Session 的有效期,防止用户无限期占用服务器资源。

<session-config>
    <!-- 单位:分钟,30分钟后Session自动失效 -->
    <session-timeout>30</session-timeout>
</session-config>
5. 配置错误页面

可以根据不同的 HTTP 错误码(如 404, 500)或 Java 异常类型,跳转到自定义的友好提示页面,提升用户体验。

<error-page>
    <error-code>404</error-code>
    <location>/error/404.jsp</location>
</error-page>
<error-page>
    <exception-type>java.lang.NullPointerException</exception-type>
    <location>/error/exception.jsp</location>
</error-page>

3、配置Tomcat

image.png 效果如下:

image.png

3、HelloMVC

1、配置DispatcherServlet

DispatcherServlet 是什么?

DispatcherServlet 是 Spring MVC 的核心(前端控制器) ,它是一个 Servlet,负责接收所有 HTTP 请求,并将请求分发给相应的处理器(Controller)进行处理,最后返回响应结果。

可以把它理解为 Spring MVC 的  "总指挥"  或  "交通警察"

这段配置是 Spring MVC 框架的核心配置,定义在 web.xml 文件中。它的作用是:

将所有的请求(/)都交给 Spring MVC 的前端控制器 DispatcherServlet 来处理。

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    
<!--        绑定spring配置文件
指定 DispatcherServlet 启动时要加载的 Spring MVC 配置文件位置。-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
<!--        配置启动级别
控制 Servlet 是在 Web 容器启动时立即创建,还是在第一次访问时才创建。
    数字越小,启动优先级越高
    0 或负数是懒加载,第一次收到请求时才创建-->
        <load-on-startup>1</load-on-startup>
</servlet>

<!---   `/`:匹配所有请求,但不会拦截 JSP
         `/*` :匹配所有请求,包括 JSP 
        -->
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
'/' 和 '/*' 匹配有什么区别?
  • /:匹配所有请求,但不会拦截 JSP

  • /* :匹配所有请求,包括 JSP

    • 使用 / 映射时,DispatcherServlet 只处理像 /test 这样的普通请求,而 .jsp 请求会被 Tomcat 内置的 JspServlet 直接处理,因此 JSP 页面能正常显示且不会去 Controller 中查找匹配的方法;但是其他的静态资源比如css也会被匹配

image.png

  • 但使用 /* 映射时,所有请求(包括 .jsp 和 JSP 内部的 include、转发)都会被强制交给 DispatcherServlet,导致 JSP 请求也被当作普通 Controller 映射去查找 @RequestMapping("/login.jsp"),由于找不到对应方法,就会返回 404 错误,甚至 JSP 页面内的子页面也会因拦截而无法正常加载。

image.png 最佳实践:DispatcherServlet永远使用 /,不要用 /*

2、配置springmvc配置文件

组件作用类比
处理器映射器根据 URL 找 Controller前台根据房间号找人
处理器适配器执行 Controller 方法让人去做具体工作
视图解析器把逻辑视图名转成真实路径把"餐厅"转成"3楼西餐厅"
<!--处理器映射器-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--    处理器适配器-->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!--    视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!--      前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
<!--        后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>

这三个配置是 Spring MVC 中最经典、最基础的三大组件配置,它们共同完成请求的完整处理流程。现在虽然很少直接这样配置(通常用 <mvc:annotation-driven/> 代替),但理解它们能帮助你深刻理解 Spring MVC 的核心工作流程

3、HelloController

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
//实现mvc控制器接口是传统的编写方式,注解在有@ResponseBody下有了更灵活的选择,这个只适合理解mvc
public class HelloController implements Controller {
    @Override
    public @Nullable ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
//        下面写业务代码
        String result="Hello SpringMVC";
        modelAndView.addObject("msg" ,result);
//        下面跳转视图
        modelAndView.setViewName("test");//这个会通过视图解析器,然后变为/WEB-INF/jsp/test.jsp
        return modelAndView;

    }
}

4、测试

依赖的 JAR 包没有同步部署而引发的 404 错误

  • 项目部署后,依赖的 JAR 包没有复制到 WEB-INF/lib 目录下,导致运行时找不到类,从而引发 404 错误,IDEA不会自动运行这个

image.png

  • 解决方法

把 Available Elements 中的依赖包(如 spring-webmvcspring-core 等)Put into Output Root,这些 JAR 包会被复制到输出目录的 WEB-INF/lib 下 image.png

image.png

4、使用注解开发

1、新的spring-mvc.xml配置

  • 首先依旧配置web.xml

image.png

<?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">
<context:component-scan base-package="com.zhang.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>
</beans>
  • <context:component-scan> :让 Spring 自动扫描指定包及其子包下的类,寻找带有特定注解(@Controller)的类,并将它们自动注册为 Spring 容器中的 Bean。

    • 替代了旧版中手动在 XML 里写每一个 <bean> 来声明 Controller 的工作
  • mvc:annotation-driven/> 它让 Spring MVC 全面支持基于注解的开发方式。启用 Spring MVC 的注解驱动功能,自动注册处理 @RequestMapping@RestController 等注解所必需的组件。 <mvc:annotation-driven/> 

    • 替代了 BeanNameUrlHandlerMapping + SimpleControllerHandlerAdapter 并且提供了更多功能(参数绑定、JSON 转换、数据验证等)
  • <mvc:default-servlet-handler/> 是 Spring MVC 中用于处理静态资源的配置。它解决了一个核心问题:让动态请求和静态资源请求能共存让 Spring MVC 能够正确处理静态资源(HTML、CSS、JS、图片等),而不会被 DispatcherServlet "拦截" 导致 404。

    • /也会拦截静态资源,只是不拦截jsp,jsp有Tomcat(或其他容器),内部有一个 JSP Servlet,它专门处理 *.jsp 和 *.jspx
    • /* 会拦截所有资源(包括 JSP),更糟糕
    • 所以才需要 <mvc:default-servlet-handler/> 来解决 / 带来的静态资源问题

2、新的HelloController

  • 还是新建一个jsp来做视图转发目的地

image.png

  • 通过注解编写控制器
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
//@RequestMapping("/test")
public class HelloController {
    @RequestMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("msg", "Hello Spring MVC");
        return "hello";
        //返回 `hello` 时:
// Spring MVC 会自动执行 `prefix + 返回值 + suffix`拼接操作
    }
}

@RequestMapping 用于将特定的 HTTP 请求(如 URL、方法等)映射到控制器类或方法上,从而让 Spring MVC 知道当收到什么请求时,由哪个类的哪个方法来处理。

  • 这种写法完全等同于
public ModelAndView hello() {
    ModelAndView mv = new ModelAndView();
    // 设置模型数据
    mv.addObject("msg", "Hello Spring MVC");
    // 设置视图逻辑名
    mv.setViewName("hello"); 
    return mv;
}

3、测试

image.png

4、小结

实现步骤其实非常的简单:

  1. 新建一个web项目
  2. 导入相关jar包
  3. 编写web.xml,注册DispatcherServlet
  4. 编写springmvc配置文件
  5. 接下来就是去创建对应的控制类,controller
  6. 最后完善前端视图和controller之间的对应
  7. 测试运行调试

使用springMVC必须配置的三大件:

处理器映射器、处理器适配器、视图解析器

通常,我们只需要手动配置视图解析器,而处理器映射器和处理器适配器只需要开启注解驱动即可,而省去了大段的xml配置

5、RestFul风格

RESTful 是一种软件架构风格,不是强制标准,而是一套设计 Web API 的约定和最佳实践。它的核心思想是:用 HTTP 协议本身来表达对资源的操作

image.png

1、传统写法

@RequestMapping("/add")
public String add(int a, int b,Model model ) {
    int sum = a + b;
    model.addAttribute("msg", sum);
    return "hello";
}

image.png 这里是我模仿老师的写法得到的,但不知为何我的500了,可能是spring无法识别参数,ds建议说用 (@RequestParam("a") int a, @RequestParam("b") int b, Model model)

http://localhost:8080/springMVC03_annotation_Web_exploded/add?a=1&b=2

这条 URL 的本质是:通过 HTTP GET 请求,向服务器传递 a=1, b=2 两个参数,请求执行 /add 这个计算逻辑,并返回结果页面。  这是传统风格的URL表示

2、RESTful写法

@Controller
public class RESTfulController {
    @RequestMapping("/add/{a}/{b}")
    public String add(@PathVariable int a, @PathVariable int b, Model model ) {
        int sum = a + b;
        model.addAttribute("msg", sum);
        return "hello";
    }
}

@PathVariable 的作用

一句话概括:将 URL 路径中的占位符参数,绑定到控制器方法的参数上。@PathVariable 的作用就是从 URL 路径中"挖"出动态的值,填到方法参数里。  它是实现简洁、规范的 RESTful API 的关键工具。

image.png

http://localhost:8080/springMVC03_annotation_Web_exploded/add/10/20

  • 它把“操作”和“参数”从请求的“角落”移到了 URL 的“主干”上,让 URL 本身成为一个清晰的名词短语。
  • RESTful 的核心思想是将一切视为资源,并用 HTTP 的方法(GET, POST, PUT, DELETE)来表达操作。
  • 这就像你去图书馆找一本叫《add/10/20》的书(获取资源),而不是对图书管理员喊“帮我把10和20加起来”(执行操作)。

6、重定向与转发

1、Controller与Servlet

  • 因为 Spring MVC 的 @Controller 本质上就是基于 Servlet API 构建的。 HttpServletRequest 和 HttpServletResponse 参数可由 Spring 在底层调用原生 Servlet 时传递进来的。
@Controller
public class ModelTest {
    @RequestMapping("/test1")
    public String test1(HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession();
        System.out.println(session.getId());

        return "hello";
    }
}

image.png Spring MVC 的 @Controller 方法中,只要通过方法参数声明,就能拿到 Servlet API 中的几乎所有核心对象,然后调用它们的全部方法。

对象创建者作用域获取方式
HttpServletRequestServlet容器一次请求方法参数
HttpServletResponseServlet容器一次请求方法参数
HttpSessionServlet容器一个用户会话request.getSession()
ServletContextServlet容器整个应用request.getServletContext() 或 getServletContext()

2、原生MVC实现转发(不用视图解析器)

在上个小节的例子里,用的就是基于视图处理器才能实现的转发,如果没有视图处理器要怎么做?

  • 直接指定视图目录,是 Spring MVC 中最经典的转发实现方式
@RequestMapping("/test1")
public String test1(Model model) {
    model.addAttribute("msg", "zhang");
    return "/WEB-INF/jsp/hello.jsp";
    //或者“forward:/WEB-INF/jsp/hello.jsp”
}
- 因为是转发,所以可以看到实际url没有变

image.png

  • 重定向 redirect
@RequestMapping("/test1")
public String test1(Model model) {
    model.addAttribute("msg", "zhang");
    return "redirect:test.jsp";
}
- 我们可以看到url到了一个新的目录,并且数据没有被传递过来,
这是因为重定向被视为一个新的请求,与转发有本质区别

image.png

redirect: 和 forward: 前缀会被 Spring 的 InternalResourceViewResolver 直接识别并跳过不会进行任何路径拼接

7、与前端数据传递与处理

1、 前端传递了同名数据

@RequestMapping("/t1")
public String test01(String name, Model model) {

    model.addAttribute("msg", name);

    return "hello";
}

image.png

2、 前端传递参数名与后端不同

@RequestMapping("/t1")
public String test01(@RequestParam("username") String name, Model model) {

    model.addAttribute("msg", name);

    return "hello";
}

image.png

@RequestParam和@PathVariable有什么区别?

1、 编码问题

  • @PathVariable:路径参数,Tomcat 默认会解码(需注意中文)

    • URL格式:  /user/张三/18
  • @RequestParam:查询参数,自动处理 URL 编码

    • URL格式:  /user?name=张三&age=18
    • 可用 Map 接收所有参数
@GetMapping("/params")
public String getAllParams(@RequestParam Map<String, String> allParams) {
    return allParams.toString();
}
// 请求:GET /params?name=张三&age=18&city=北京
// 结果:{name=张三, age=18, city=北京}
  1. 性能考虑
  • @PathVariable:路径参数,URL 更短,某些场景下性能略好
  • @RequestParam:查询参数,URL 更长,但更灵活
  1. 缓存影响
  • 相同路径参数的不同值会被视为不同 URL
  • 查询参数的顺序不影响 URL 的唯一性(?a=1&b=2 和 ?b=2&a=1 被视为相同)

@RequestParam 问号后面,@PathVariable 斜杠之间!

3、 前端传递个对象

controller会自动匹配前端数据名与对象字段相同的赋值

@RequestMapping("/t2")
public  String test02(User user,Model model) {
    model.addAttribute("msg", user.toString());
    return "hello";
}

image.png

8、乱码问题

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/e" method="post">
    <input type="text" name="name">
    <input type="submit">
</form>
</body>
</html>

在Web项目中,任何内部路径(表单action、超链接href、重定向地址等)都不要写死,一律使用 ${pageContext.request.contextPath} 作为前缀,这样项目无论部署在根目录还是任意子路径下,路径都能自动适配,避免404错误。

@PostMapping("/e")
public String test1(String name, Model model){
    model.addAttribute("msg", name);

    return "hello";
}

image.png

  • 可以看到英文输出没有问题,而中文有可能会出现下面的乱码

image.png

  • MVC给出的解决方案,在web配置中添加过滤器
<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>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>  <!-- 强制编码,包括响应 -->
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>  <!-- 改为/*,拦截所有请求和转发 ,包括jsp-->
</filter-mapping>
  • 有时控制器返回的内容不经过视图没有响应头,那还是可能会乱码,mvc添加配置为下列可解决
<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <constructor-arg value="UTF-8"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
  • 你也可以像servlet里学的那样自定义filter代替MVC的,不过也要是“ /* ”
package com.zhang.filter;

import jakarta.servlet.*;

import java.io.IOException;

public class CharacterEncodingFilter implements Filter {
    @Override
//    初始化方法,在web服务器启动时就调用,随时监听程序
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("CharacterEncodingFilter init");
    }
//过滤特定请求
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
//        将当前请求传递给过滤器链(Filter Chain)中的下一个组件。没有这个就会拦截在这里
//        就像是当前安检关卡说:“检查通过,去下一个关卡(或最终目的地)
        chain.doFilter(request, response);

    }
//  销毁:在服务器关闭时销毁
    @Override
    public void destroy() {
        System.out.println("CharacterEncodingFilter destroy");
    }
}

    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>com.zhang.filter.CharacterEncodingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
<!--        在指定url下的请求会全部进入过滤-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

  • 有的乱码可能是Tomcat的配置问题
    • 检查Tomcat conf/server.xml
<!-- 问题配置(会乱码)-->
<Connector port="8080" protocol="HTTP/1.1"/>

<!-- 正确配置 -->
<Connector port="8080" protocol="HTTP/1.1" URIEncoding="UTF-8"/>

9、JSON

1、什么是JSON?

  • JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。
  • 采用完全独立于编程语言的文本格式来存储和表示数据。
  • 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
  • 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:

  • 对象表示为键值对,数据由逗号分隔
  • 花括号保存对象
  • 方括号保存数组

JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:

{"name": "QinJiang"}

{"age": "3"}

{"sex": "男"}

JSON对象与JavaScript对象互相转换

<script>
    let obj={
        name:"zhanglei",
        age:18
    }
    console.log("输出对象:")
    console.log(obj);
    let json=JSON.stringify(obj);
    console.log("对象转换为字符串为");
    console.log(json);
    let obj2=JSON.parse(json);
    console.log("字符串转换为对象:");
    console.log(obj2);

</script>

image.png

2、Controller返回JSON

1、@RequestMapping与@RestController

@RequestMapping("/j1")
@ResponseBody//用了这个注解,返回值就不会被视图解析器解析了,会直接返回字符串
public String json1(){
    User user = new User("张磊", 18, "男");
    return user.toString();
}

同样,@RestController代替掉@Controller也可以实现阻止视图,只返回字符串的效果

2、如何转换数据为JSON格式数据

Jackson Databind
<!-- Source: https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.21.2</version>
    <scope>compile</scope>
</dependency>

在上面可以实现把字符串返回回去,JSON对java来说其实也只是字符串, 这个 Jackson Databind 是Java中最常用的 JSON处理库,主要功能是在 Java对象 和 JSON格式 之间相互转换。

@RequestMapping("/j1")
@ResponseBody//用了这个注解,返回值就不会被视图解析器解析了,会直接返回字符串
public String json1() throws JsonProcessingException {
    User user = new User("张磊", 18, "男");
    ObjectMapper objectMapper = new ObjectMapper();
    String s = objectMapper.writeValueAsString(user);
    return s;
}

得到了JSON格式字符串

image.png

3、JSON字符串 → Java对象(反序列化)

String json = "{"name":"李四","age":20,"sex":""}";
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(json, User.class);
// 结果:User对象,属性被自动填充

4、自动转换

  • 导入依赖 +  <mvc:annotation-driven/>  = 自动转换
  • 缺少任意一个,都不会自动转换
  • 返回类型必须是对象(不是String),才会触发JSON转换
@Controller
public class UserController {
    
    // 返回对象时,Spring自动用Jackson转成JSON
    @RequestMapping("/getUser")
    @ResponseBody
    public User getUser() {
        User user = new User("张磊", 18, "男");
        return user;  // 自动变成 {"name":"张磊","age":18,"sex":"男"}
    }
    
    // 接收JSON数据,自动转成Java对象
    @PostMapping("/saveUser")
    @ResponseBody
    public String saveUser(@RequestBody User user) {
        System.out.println(user.getName());  // 直接使用对象
        return "success";
    }
}

Spring Boot 内置了这个功能,这也是它能成为主流框架的重要原因之一

国内有一个同类工具,Fastjson2 是由 阿里巴巴 开源的一款高性能 JSON 处理库

<!-- Source: https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.61</version>
    <scope>compile</scope>
</dependency>
  • ✍️ 基本用法示例
    • ✅ 对象 → JSON
import com.alibaba.fastjson2.JSON;

User user = new User("Alice", 20);
String json = JSON.toJSONString(user);

System.out.println(json);
// {"name":"Alice","age":20}

  • ✅ JSON → 对象
String json = "{"name":"Alice","age":20}";
User user = JSON.parseObject(json, User.class);

  • ✅ 使用 JSONB
    • 把原本的 JSON 文本,编译成一种更高效的二进制格式,更快更小
byte[] bytes = JSON.toJSONBytes(user);
User u = JSON.parseObject(bytes, User.class);

10、整合SSM

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 '描述',
  PRIMARY KEY (`bookID`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `books` (`bookID`, `bookName`, `bookCounts`, `detail`) VALUES
(1, 'Java', 1, '从入门到放弃'),
(2, 'MySQL', 10, '从删库到跑路'),
(3, 'Linux', 5, '从进门到进牢');

一、MyBatis 配置(数据持久层)

<?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.zhang.pojo"/>
    </typeAliases>
<!--    注册 SQL 映射文件-->
    <mappers>
        <mapper resource="com/zhang/dao/BookMapper.xml"/>
    </mappers>
</configuration>

二、Spring 配置(核心容器)

管:Service + DAO + 数据源 + MyBatis

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">
<!--加载数据库配置(用户名、密码等)-->
    <context:property-placeholder location="classpath:db.properties"/>
<!--    创建数据库连接池(连接复用)-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${driver}"/>
        <property name="jdbcUrl" value="${url}"/>
        <property name="user" value="${username}"/>
        <property name="password" value="${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>
<!--让 Spring 帮忙创建并管理 MyBatis 的核心对象 SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--        用哪个数据库-->
        <property name="dataSource" ref="dataSource"/>
<!--        加载 MyBatis 配置-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>

    </bean>
<!--自动创建 Mapper 接口的代理对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--        去容器里找名叫这个sqlSessionFactory的 Bean
    SqlSessionFactory 和扫描器共同生产一个mapper的制造机器,
    把  这个制造机器注册到容器,再取的时候是这个机器生产的mapper-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="com.zhang.dao"/>
    </bean>

</beans>

spring-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" xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--Service 扫描,自动识别:@Service-->
    <context:component-scan base-package="com.zhang.service"/>

    <bean id="BookServiceImpl" class="com.zhang.service.BookServiceImpl">
        <property name="bookMapper" ref="bookMapper" />
    </bean>
<!--    管理数据库事务(提交 / 回滚)-->
    <bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--让 @Transactional 生效-->
    <tx:annotation-driven/>
</beans>

applicationContext.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:mvc="http://www.springframework.org/schema/mvc"
       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/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--整合 Spring 配置(统一入口)-->
    <import resource="classpath:spring-dao.xml"/>
    <import resource="classpath:spring-service.xml"/>
</beans>

三、SpringMVC 配置(Web 层)

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

<!--    让静态资源(css/js)可以访问-->
    <mvc:default-servlet-handler/>
    <!--   开启:@RequestMapping,@ResponseBody参数绑定-->
    <mvc:annotation-driven/>
<!--把 Controller 返回的字符串 → JSP 路径-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
<!--    扫描 @Controller-->
    <context:component-scan base-package="com.zhang.controller"/>
</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
<!--        SpringMVC 的核心控制器(前端控制器)-->
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <!--        绑定spring配置文件
        指定 DispatcherServlet 启动时要加载的 Spring MVC 配置文件位置。-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <!--        配置启动级别
        控制 Servlet 是在 Web 容器启动时立即创建,还是在第一次访问时才创建。
            数字越小,启动优先级越高
            0 或负数是懒加载,第一次收到请求时才创建-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!---   `/`:匹配所有请求,但不会拦截 JSP
             `/*` :匹配所有请求,包括 JSP
            -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <context-param>
<!--        告诉 Spring:主配置文件在哪-->
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
<!--在 Tomcat 启动时:

1️⃣ 读取 contextConfigLocation
2️⃣ 创建 Spring 容器(ApplicationContext)
3️⃣ 加载所有 Bean(Service / DAO / MyBatis)-->
<!--    ContextLoaderListener 的作用是创建 Spring 根容器,
使业务层(Service、DAO)与 Web 层(Controller)解耦。在简单项目中可以省略,
但在标准 SSM 架构中是推荐配置。-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

四、小结

SSM 本质就是:

  • Spring:管理对象(IoC + 事务)
  • SpringMVC:处理请求(Web层)
  • MyBatis:操作数据库(持久层)

SSM 的核心不是配置,而是三层解耦:
Controller → Service → DAO

浏览器请求
     ↓
DispatcherServletSpringMVC)
     ↓
Controller(控制层)
     ↓
Service(业务层,Spring管理)
     ↓
Mapper(持久层,MyBatis)
     ↓
数据库
     ↓
返回结果 → JSP / JSON

11、Ajax技术

  • AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
  • AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
  • Ajax 不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术。
  • 在 2005 年,Google 通过其 Google Suggest 使 AJAX 变得流行起来。Google Suggest能够自动帮你完成搜索单词。
  • Google Suggest 使用 AJAX 创造出动态性极强的 web 界面:当您在谷歌的搜索框输入关键字时,JavaScript 会把这些字符发送到服务器,然后服务器会返回一个搜索建议的列表。
  • 就和国内百度的搜索框一样:

1、iframe体验

<iframe>(内联框架)是 HTML 中一个非常经典且实用的标签,用于在当前网页中嵌入另一个独立的 HTML 页面

你可以把它理解为“网页中的网页”或“一个窗口”。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>iframe</title>
</head>
<script>
    function test(){
        let va=document.getElementById("url").value;
        document.getElementById("iframe").src=va;
    }
</script>
<body>
<div>
    <p>
        <input type="text" id="url">
        <input type="button" value="提交" id="btn" onclick="test()">
    </p>
</div>
<div>
    <iframe id="iframe" style="width: 100%; height: 500px;" >

    </iframe>
</div>
</body>
</html>

image.png iframe 和 Ajax 在理念上有相似之处(都是实现局部刷新、按需加载),但在技术实现和体验上是完全不同的两条技术路线。

Ajax (Asynchronous JavaScript and XML) :是  “无刷新数据交互” 。浏览器在后台静默地请求数据(通常是 JSON/XML),然后 JavaScript 拿到数据后,动态修改当前页面的 DOM(文档对象模型)。用户感觉页面没动,但内容变了

2、失去焦点的Ajax

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
<%--    引入 jQuery 库,方便进行 AJAX 操作--%>
  <script src="${pageContext.request.contextPath}/static/js/jquery-4.0.0.min.js"></script>
  <script>
    function checkUsername(){
        // 当用户输入框失去焦点时,这个函数会被触发
      $.ajax({
          // 请求的服务器地址,对应的后端处理器
        url:"${pageContext.request.contextPath}/a1",
          // 要发送到服务器的数据(键值对),`$()` 是jQuery 库的核心函数,通常被称为 jQuery 选择器
        data:{"name":$("#username").val()},
          // 请求成功后的回调函数
        //   回调函数是只要这个ajax触发后就执行一遍,他的data是后端处理后又放回前端的data
        //   只要后端在前端写东西的行为都是返回的data
        //,如response.getWriter().print(),@ResponseBody,@RestController,返回对象(自动转 JSON)
        success:function(data){
          alert(data);
        }
      })
    }
  </script>
</head>
<body>
<%--onblur="checkUsername()":当输入框失去焦点时调用函数--%>
用户名:<input type="text" id="username" onblur="checkUsername()">
</body>
</html>
@RequestMapping("/a1")
public void a1(@RequestParam("name") String name, HttpServletResponse response) throws IOException {
    if(name.equals("zhanglei")){
        response.getWriter().print("ture");
    }
    else{
        response.getWriter().print(false);
    }
}

image.png 这段代码展示了一个典型的 AJAX 异步验证模式:

  • 触发时机:失去焦点事件(onblur
  • 数据传输:通过 AJAX 异步发送
  • 结果展示:使用弹窗(alert)显示
  • 核心价值:无需刷新页面即可与服务器交互
1. 基本语法结构
$.ajax({
    // 必需参数
    url: "请求地址",           // 如:"/api/user"
    type: "请求方式",          // GET、POST、PUT、DELETE 等
    
    // 可选参数
    data: {},                 // 发送的数据
    dataType: "json",         // 期望返回的数据类型
    success: function(data){}, // 成功回调
    error: function(){},       // 失败回调
    timeout: 5000,            // 超时时间(毫秒)
    async: true               // 是否异步(默认true)
});
2. 常用简写方法
// GET 请求
$.get(url, data, successCallback);

// POST 请求
$.post(url, data, successCallback);

// GET 请求(返回 JSON)
$.getJSON(url, data, successCallback);

// GET 请求(加载 HTML)
$.load(url, data, callback);


// 简写
$.get("/api/user", { "id": 1 }, function(data) {
    console.log(data);
}, "json");

下面的例子展示了另一个ajax的运用,实时验证用户名

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
<%--    引入 jQuery 库,方便进行 AJAX 操作--%>
  <script src="${pageContext.request.contextPath}/static/js/jquery-4.0.0.min.js"></script>
  <script>
    function checkUsername(){
        $.ajax({
            url:"${pageContext.request.contextPath}/a3",
            data: {"name":$("#username").val()},
            success: function(data){
                if(data.toString()==="ok"){
                    $("#userIfo").html("ok")
                    $("#userIfo").css({"background-color":"#95ec93"});
                }
            }
        })
    }
  </script>
</head>
<body>
<%--onblur="checkUsername()":当输入框失去焦点时调用函数--%>
用户名:<input type="text" id="username" onblur="checkUsername()">
<span id="userIfo" ></span>
</body>
</html>
@RequestMapping("/a3")
public String a3(@RequestParam("name") String name, HttpServletResponse response) throws IOException {
    if(name.equals("zhanglei")){
        return "ok";
    }
    else{
        return "error";
    }

}

image.png

12、拦截器

SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。

过滤器与拦截器的区别:拦截器是AOP思想的具体应用。

过滤器

  • servlet规范中的一部分,任何java web工程都可以使用
  • 在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截

拦截器

  • 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
  • 拦截器只会拦截访问的控制器方法,如果访问的是jsp/html/css/image/js是不会进行拦截的

1、自定义拦截器

这段代码是 Spring MVC 拦截器(HandlerInterceptor)  的一个标准实现

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }
}
 <mvc:interceptors>
        <mvc:interceptor>
<!--            "/**"表示拦截所有请求,包括这个请求下的所有请求,比如admin/user1
Spring 的路径匹配中 /* 只匹配一层,必须用 /** 才能匹配多层-->
            <mvc:mapping path="/**"/>
            <bean class="com.zhang.config.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

image.png

preHandle - 前置处理

返回值含义

  • true:继续执行(调用 Controller)

  • false:中断请求(不调用 Controller,通常返回错误信息)

  • 主要用途

    • ✅ 登录检查(是否已登录)
    • ✅ 权限验证(是否有权限访问)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    String uri = request.getRequestURI();
    
    // 只拦截需要权限的资源
    if (uri.startsWith("/admin/") || uri.startsWith("/user/")) {
        String username = (String) request.getSession().getAttribute("username");
        
        if (!"admin".equals(username)) {
            // 未登录或不是admin,重定向到登录页
            response.sendRedirect("/login.jsp");
            return false;
        }
    }
    
    return true;  // 登录页、静态资源等都放行
}
-   ✅ 参数预处理(校验、格式化)
-   ✅ 记录请求日志
-   ✅ 限流控制

postHandle - 后置处理

  • 执行时机:Controller 执行之后,视图渲染之前

  • 主要用途

    • ✅ 修改 ModelAndView(添加公共数据)
    • ✅ 记录 Controller 执行时间
    • ✅ 响应数据加密/压缩
    • ✅ 操作日志记录

afterCompletion - 完成处理

  • 执行时机:整个请求完全结束之后(视图渲染完成)

  • 主要用途

    • ✅ 资源清理(关闭文件流、数据库连接)
    • ✅ 释放内存(清理 ThreadLocal)
    • ✅ 记录最终响应结果
    • ✅ 异常处理日志

13、文件上传与下载

文件上传是项目开发中最常见的功能之一,springMVC 可以很好的支持文件上传,但是 SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。

前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data 只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器;

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

  • application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
  • multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
  • text/plain:除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
<!-- Source: https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.6.0</version>
    <scope>compile</scope>
</dependency>

文件上传

  • 首先一个表单

enctype 是 HTML 表单中的一个属性,用于指定表单数据在发送到服务器之前应该如何编码

特性application/x-www-form-urlencodedmultipart/form-datatext/plain
是否默认✅ 是(浏览器默认) 不写enctype就是它❌ 否 必须手动指定❌ 否 几乎不手动指定
适用场景• 登录/注册表单 • 搜索框 • 评论提交 • 设置页面 • 任何只有文本的表单• 上传头像/照片 • 发送附件邮件 • 上传文档/压缩包 • 提交带文件的表单 • 批量文件上传• 调试测试 • 查看原始数据 • 简单的纯文本传输 • ⚠️ 生产环境几乎不用
二进制数据❌ 不支持 二进制数据会被破坏 只能传文本内容✅ 完全支持 保持原始二进制格式 图片/视频/PDF都能传❌ 不支持 二进制数据会乱码 无法恢复原始文件
数据大小限制⚠️ 较小 • POST理论上可大 • 但服务器常限制(2-8MB) • URL编码后体积增大约20% • 不适合大文件✅ 很大 • 可达GB级别 • 分块传输,内存友好 • 主要受服务器配置限制 • 适合大文件上传⚠️ 中等 • 无特殊限制 • 但不适合二进制 • 实际很少用
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit"  value="Submit" />
</form>
</body>
</html>
  • 控制器

MultipartFile 是 Spring 框架提供的文件上传抽象接口,用于接收 HTTP 请求中上传的文件。它是 Spring 处理文件上传的核心类。

@RequestMapping("/upload")
public String fileUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException {

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

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

    // 上传路径保存设置,输出在out下
    //上传文件名:copy.png
//上传文件保存地址:D:\code\JAVA\IDEA\SpringMVC\out\artifacts\spring_05_file_Web_exploded\upload
    String path = request.getServletContext().getRealPath("/upload");
    // 如果路径不存在,创建一个
    File realPath = new File(path);
    if (!realPath.exists()){
        realPath.mkdirs();  // 建议使用 mkdirs()
    }
    System.out.println("上传文件保存地址:" + realPath);

    // 方式1:使用 MultipartFile.transferTo() 最简单(推荐)
    //`transferTo()` 是 Spring 提供的最便捷的文件保存方法,它的作用就是直接将上传的文件保存到磁盘上的目标位置**。
    File targetFile = new File(realPath, uploadFileName);
    file.transferTo(targetFile);

    // 方式2:手动读写流(如果你需要特殊处理)
    // InputStream is = file.getInputStream();
    // OutputStream os = new FileOutputStream(new File(realPath, uploadFileName));
    // byte[] buffer = new byte[1024];
    // int len;
    // while ((len = is.read(buffer)) != -1){
    //     os.write(buffer, 0, len);
    // }
    // os.close();
    // is.close();

    return "redirect:/index.jsp";
}

image.png

image.png

文件下载

ResponseEntity 是 Spring 提供的用于完整控制 HTTP 响应的类,尖括号里的 <byte[]> 表示响应体的数据类型是字节数组

它让你可以完全自定义 HTTP 响应的状态码、响应头和响应体。

@RequestMapping(value = "/download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception {
    // 要下载的文件路径:src/upload/copy.png
    String path = request.getServletContext().getRealPath("/upload");
    String fileName = "copy.png";

    File file = new File(path, fileName);

    // 检查文件是否存在
    if (!file.exists()) {
        return ResponseEntity.notFound().build();
    }

    // 读取文件内容
    byte[] fileBytes = java.nio.file.Files.readAllBytes(file.toPath());

    // 设置响应头
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 二进制流
    headers.setContentDispositionFormData("attachment",
            new String(fileName.getBytes("UTF-8"), "ISO-8859-1")); // 解决中文文件名乱码

  return ResponseEntity.ok()           // 1. 设置状态码为 200 OK
            .headers(headers)        // 2. 设置响应头
            .contentLength(file.length())  // 3. 设置内容长度
            .body(fileBytes);        // 4. 设置响应体(文件内容)
}

image.png