跳转至

MVC架构模式#

约 3795 个字 255 行代码 10 张图片 预计阅读时间 84 分钟

不使用MVC模式存在的问题#

  • 不使用MVC模式,Servlet需要负责数据接收核心业务逻辑处理数据库连接和增删改查操作页面展示等功能。职责过重。
  • 代码的复用性差,相同的业务操作或数据库操作,需要在不同Servlet中编写重复代码,不方便维护。
  • 代码耦合度高,导致代码很难扩展。
  • 操作数据库的代码和处理业务逻辑的代码混杂在一起,很容易出错,无法专注于业务逻辑的编写。

MVC模式概述#

  • M(Model、模型):用于处理业务
  • V(View、视图):负责页面展示
  • C(Controller、控制器):控制器是MVC架构的核心,
MVC架构图解
MVC请求响应过程

DAO(Data Access Object、数据访问对象)

属于JavaEE的设计模式之一。只负责数据库的增删改查,没有任何业务逻辑在里面

三层架构#

  • 表示层:Controller控制器+View视图
  • 业务逻辑层:Service服务
  • 持久化层:DAO数据访问对象

Spring MVC概述#

Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从 Spring 框架诞生之初就已包含其中。其正式名称Spring Web MVC 源自其源码模块名称:spring-webmvc,但更常被称为Spring MVC

与 Spring Web MVC 并行,Spring Framework 5.0 引入了一个名为Spring WebFlux的响应式堆栈Web框架,其名称也基于其源模块 spring-webflux

Spring MVC是实现MVC架构模式的Web框架。底层使用Servlet实现。

Spring MVC能干什么

  • 入口控制:通过DispatcherServlet作为入口控制器负责接收请求和分发请求。
  • 自动将表单请求参数封装为JavaBean对象
  • 统一使用IOC容器管理对象
  • 统一请求处理:提供拦截器、统一异常处理等机制
  • 视图解析:轻松切换JSP、Freemarker、Velocity等视图模板
  • 对Controller进行单元测试时
  • 入口控制:Servlet开发中,每个Servlet都需要在web.xml中进行配置,Spring MVC通过DispatcherServlet作为入口控制器负责统一接收请求和分发请求。
  • Spring MVC会自动将表单数据封装为JavaBean对象,而不需要手动通过request对象获取表单数据。
  • Spring MVC通过IOC容器管理对象,不需要手动创建对象。
  • Spring MVC提供拦截器、异常处理器等统一处理请求机制。不需要手动编写过滤器。
  • 视图解析器:Spring MVC提供了JSP、Freemarker、Velocity等视图解析器。

Spring MVC入门案例#

创建maven工程,将工程改为war包,引入依赖:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.luguosong</groupId>
    <artifactId>springmvc-hello</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--Spring MVC依赖中包含了Spring的相关依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>6.1.8</version>
        </dependency>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>6.0.0</version>
            <!--provided表示该依赖只在编译和测试时有效-->
            <!--打war包时不会包含,由Tomcat提供-->
            <scope>provided</scope>
        </dependency>
        <!--日志-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!--thymeleaf与spring6整合-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring6</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Warning

注意,需要将maven工程改为war包:<packaging>war</packaging>

创建webapp/WEB-INF/web.xml目录和文件。

web.xml中配置前端控制器(DispatcherServlet):

Note

相比于Servlet开发,Spring MVC会配置一个全局统一的DispatcherServlet来管理所有请求。

XML
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--优化:让DispatcherServlet在启动时就加载,而不是首次访问时加载,提高首次访问速度-->
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--url-pattern配置/表示处理除jsp外的所有请求-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

在Spring MVC配置文件配置包扫描视图解析器:

其中常见的视图解析器有以下几种:

  • JSP的视图解析器:InternalResourceViewResolver
  • FreeMarker的视图解析器:FreeMarkerViewResolver
  • Velocity的视图解析器:VelocityViewResolver
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">
    <!--配置组件扫描-->
    <context:component-scan base-package="com.luguosong.controller"/>

    <!--配置视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <property name="characterEncoding" value="UTF-8"/>
        <property name="order" value="1"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
</beans>

编写视图:

hello.thymeleaf

编写Controller:

Java
package com.luguosong.controller;

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

/**
 * @author luguosong
 */
@Controller
public class HelloController {
    //请求映射
    @RequestMapping("/hello-mvc")
    public String hello() {
        //返回逻辑视图名称
        return "hello";
    }
}

Note

逻辑视图名称会根spring mvc配置文件中的prefixsuffix属性进行拼接。找到具体的视图位置(物理视图名称)。

启动Tomcat,通过以下地址可以访问视图:

Text Only
http://localhost:8080/springmvc_hello_war_exploded/hello-mvc

Spring MVC执行流程#

Spring MVC执行流程
  1. 发送请求,DispatcherServlet类接收请求。
    1. doDispatch方法负责处理请求。
      1. 通过HttpServletRequest请求对象得到uri,根据uri得到HandlerExecutionChain处理器执行链对象( 其中包含拦截器和处理器)。
      2. HandlerExecutionChain处理器执行链获取处理器适配器HandlerAdapter对象。
      3. HandlerExecutionChain对象执行该请求所有拦截器中的preHandle方法
      4. 通过消息转换器将请求参数进行转换,HandlerAdapter对象调用Controller处理器方法。返回ModelAndView对象。
      5. HandlerExecutionChain对象执行该请求所有拦截器中的postHandle方法
      6. processDispatchResult方法处理响应结果。
        1. 通过视图解析器解析,返回视图对象。调用视图对象的渲染方法。
        2. 执行该请求所有拦截器中的afterCompletion方法

自定义Spring MVC配置文件名称#

默认情况下,Spring MVC会根据web.xml中<servlet-name>标签的值去寻找Spring MVC配置文件。

比如<servlet-name>的值为springmvc,那么就会去寻找/WEB-INF/springmvc-servlet.xml配置文件。

当然,也可以手动指定Spring MVC配置文件:

XML
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.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:springmvc-servlet.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

@RequestMapping注解#

@RequestMapping注解的使用#

您可以使用@RequestMapping注解将请求映射到控制器方法。

@RequestMapping可以作用于或者方法

value属性#

value属性path属性功能相同,都是用于映射请求路径。

Java
@Controller
public class HelloController {
    //请求映射
    @RequestMapping("/hello1-1")
    public String hello() {
        //返回逻辑视图名称
        return "hello";
    }

    //多个映射可以指向同一个方法
    @RequestMapping({"/hello2-1", "/hello2-2"})
    public String hello() {
        //返回逻辑视图名称
        return "hello";
    }
}

value属性Ant风格#

value属性也支持Ant风格的通配符:

  • ?:匹配任意单个字符
  • *:匹配任意多个字符
  • **:匹配任意多个字符(包括目录,即/

Warning

如果使用**,左右两边只能是/

Java
@Controller
public class HelloController {
    //?表示任意单个字符,比如 hello1 或 helloa 都会访问到该方法
    @RequestMapping("/hello?")
    public String hello() {
        //返回逻辑视图名称
        return "hello";
    }
}

value属性占位符#

使用占位符,可以实现Restful风格的参数传递

方法中通过@PathVariable获取参数

Java
@Controller
public class HelloController {
    @RequestMapping("/login/{username}/{password}")
    public String login(@PathVariable String username,
                        @PathVariable String password) {
        //用户登录
        //...
        return "ok";
    }
}

method属性#

method属性用于限制请求方法,method属性的值可以是GETPOSTPUTDELETE等。

Java
@Controller
public class HelloController {
    //只会接收Get类型的请求
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String hello() {
        //返回逻辑视图名称
        return "hello";
    }
}

衍生Mapping#

除了@RequestMapping注解,还可以使用@GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping 等注解。表示具体method方法的请求。

params属性#

params属性对请求参数进行限制

Java
@Controller
public class HelloController {
    //表示请求参数中必须存在username和password,且username必须为张三
    @PostMapping(value = "/hello", params = {"username=张三", "password"})
    public String hello() {
        //返回逻辑视图名称
        return "ok";
    }
}

headers属性#

headers属性对请求头进行限制

Java
@Controller
public class HelloController {
    //表示请求头中必须存在token
    @PostMapping(value = "/hello", headers = {"token"})
    public String hello() {
        //返回逻辑视图名称
        return "ok";
    }
}

请求参数处理⭐#

消息转换器#

消息转换器可以将HTTP请求的消息转换为Java对象,或者将Java对象转换为HTTP响应。

消息转换器接口和实现类
消息转换器作用

Form请求-形参解析参数#

FormController.java
package com.luguosong.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("/form")
public class FormController {

    @GetMapping("/formView")
    public String formView() {
        return "get-parameters/form";
    }

    /*
     * 模拟Servlet接收参数
     * */
    @PostMapping("/servlet")
    public String servletPost(HttpServletRequest request) {
        request.setAttribute("username", request.getParameter("username"));
        return "get-parameters/form";
    }

    /*
     * @RequestParam注解value值为请求参数名
     * required属性表示参数是否必须,默认为true
     * defaultValue属性为默认值
     * */
    @PostMapping("/springMvc")
    public String springMvc(
            @RequestParam(
                    value = "username",
                    required = false,
                    defaultValue = "张三") String username,
            HttpServletRequest request) {
        request.setAttribute("username", username);
        return "get-parameters/form";
    }


    /*
     * 如果请求参数的形参名和方法参数名一样,则@RequestParam注解可以省略
     * */
    @PostMapping("/springMvc2")
    public String springMvc2(
            String username,
            HttpServletRequest request) {
        request.setAttribute("username", username);
        return "get-parameters/form";
    }
}

如果是Spring6+,想要省略@RequestParam注解,需要在pom.xml中配置-parameters标记

XML
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.13.0</version>
            <configuration>
                <source>17</source>
                <target>17</target>
                <compilerArgs>
                    <arg>-parameters</arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

Form请求-Bean对象解析参数#

Note

SpringMVC会使用FormHttpMessageConverter消息转换器将表单数据转为JavaBean。

FormPojoController.java
package com.luguosong.controller;

import com.luguosong.pojo.User;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("/formPojo")
public class FormPojoController {

    @PostMapping("/springMvc")
    public String springMvcPojo(
            User user,
            HttpServletRequest request) {
        request.setAttribute("username", user.getUsername());
        return "get-parameters/form";
    }
}
User.java
package com.luguosong.pojo;

/**
 * @author luguosong
 */
public class User {
    private String username;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                '}';
    }
}

Form请求-获取参数原始字符串#

通过@RequestBody注解可以拿到请求参数的原始字符串。

FormStringController.java
package com.luguosong.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("/formString")
public class FormStringController {

    @RequestMapping("/springMvc")
    public String springMvc(
            @RequestBody String requestBody,
            HttpServletRequest request
    ) {
        request.setAttribute("username", requestBody);
        return "get-parameters/form";
    }
}

Note

底层使用FormHttpMessageConverter消息转换器。

JSON请求-Bean对象解析参数#

在pom.xml引入处理json的依赖:

XML
<!--负责json字符串和java对象之间的转换-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

通过@RequestBody注解可以将JSON格式的请求参数转为Java对象。

JsonPojoController.java
package com.luguosong.controller;

import com.luguosong.pojo.User;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("jsonPojo")
public class JsonPojoController {
    @RequestMapping("springMvc")
    public String springMvc(
            @RequestBody User user) {
        System.out.println(user);
        return "get-parameters/form";
    }
}

Get请求中文乱码问题#

Tomcat8以及之前版本,解决Get请求中文乱码,在Tomcat服务器CATALINA_HOME/conf/server.xml中配置:

解决Get请求乱码问题

Tomcat8之后版本,请求行默认采用UTF-8编码,无需解决中文乱码问题。

Post请求中文乱码问题#

Tomcat9以及之前的版本,需要解决Post请求中文乱码问题。

Servlet编程中,可以使用request.setCharacterEncoding("UTF-8");解决乱码问题。

但在Spring MVC中,无法在Controller中使用以上方法解决中文乱码。

解决方案一:编写Servlet过滤器,过滤器会在DispatcherServlet之前执行。因此在过滤器中设置 request.setCharacterEncoding("UTF-8"); 可以解决乱码问题。

解决方案二:Spring MVC为我们提供了类似的过滤器类CharacterEncodingFilter,无需我们重新手写过滤器类。只需要在web.xml 中配置该过滤器并设置encoding属性即可。

解决Post请求中文乱码问题
<web-app>
    <filter>
        <filter-name>characterEncodingFilter</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>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--是否强制设置编码-->
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

Tomcat10请求体默认采用UTF-8编码,无需解决中文乱码问题。

RequestEntity对象#

RequestEntity对象中存储了所有请求信息,包括请求行、请求头、请求体。

RequestEntity的泛型对应请求体信息,如果是String表示请求体字符串,如果是实体类会将请求体转换为实体类。

RequestEntityController.java
package com.luguosong.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("requestEntity")
public class RequestEntityController {

    @RequestMapping("/springMvc")
    public String springMvc(RequestEntity<String> requestEntity) {
        /*
         * 获取请求行信息
         * */
        System.out.println("请求方法:" + requestEntity.getMethod());
        System.out.println("请求地址:" + requestEntity.getUrl());

        /*
         * 获取请求头信息
         * */
        HttpHeaders headers = requestEntity.getHeaders();
        System.out.println("请求参数类型:" + headers.getContentType());

        /*
         * 获取请求体信息
         * */
        String entityBody = requestEntity.getBody();
        System.out.println("请求体内容:" + entityBody);
        return "get-parameters/form";
    }
}

文件上传#

❗Spring MVC 5以及之前版本在pom.xml引入处理文件的依赖:

XML
<!--负责文件上传-->
<!--Spring MVC 6之后不再需要添加此依赖-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>

上传参数配置:

XML
  <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
           version="6.0">
      <servlet>
          <servlet-name>springmvc</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <!--配置上传参数-->
          <multipart-config>
              <max-file-size>102400</max-file-size>
              <!--设置整个表单所有文件上传的最大值-->
              <max-request-size>102400</max-request-size>
              <!--最小上传大小-->
              <file-size-threshold>0</file-size-threshold>
          </multipart-config>
      </servlet>
      <servlet-mapping>
          <servlet-name>springmvc</servlet-name>
          <url-pattern>/</url-pattern>
      </servlet-mapping>
  </web-app>
XML
  <beans>
      <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
          <property name="maxUploadSizePerFile" value="#{10*1024*1024}"/>
          <property name="maxUploadSize" value="#{100*1024*1024}"/>
      </bean>
  </beans>

文件上传必须时post请求,因为文件数据需要通过请求体传递,get请求没有请求体。

设置请求参数类型为multipart/form-data

HTML
<form method="post" th:action="@{/fileUpload/springMvc}" enctype="multipart/form-data">
    文件上传:<input type="file" name="fileName">
    <input type="submit" value="文件上传">
</form>

Controller通过MultipartFile对象接收文件:

FileUploadController.java
package com.luguosong.controller;

import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.UUID;

/**
 * 文件上传
 *
 * @author luguosong
 */
@Controller
@RequestMapping("/fileUpload")
public class FileUploadController {

    @RequestMapping("/springMvc")
    public String springMvc(
            @RequestParam("fileName") MultipartFile file,
            @RequestParam("username") String username,
            HttpServletRequest request) throws IOException {
        System.out.println("请求参数名:" + file.getName());
        String originalFilename = file.getOriginalFilename();
        System.out.println("文件真实名称:" + originalFilename);

        System.out.println("额外参数:" + username);
        //将文件存储到服务端
        //输入流
        InputStream in = file.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(in);
        //输出流
        ServletContext application = request.getServletContext();//获取ServletContext
        String folderPath = application.getRealPath("/upload");
        File folder = new File(folderPath);
        if (!folder.exists()) {
            folder.mkdirs();
        }
        File saveFile = new File(folder.getAbsolutePath() + "/" + UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf('.')));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(saveFile));
        //开始传输文件
        byte[] bytes = new byte[1024 * 10];
        int readCount = 0;
        while ((readCount = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, readCount);
        }
        bos.flush();
        //释放资源
        bos.close();
        bis.close();

        return "get-parameters/form";
    }
}

响应结果处理⭐#

返回逻辑视图名称#

默认情况下,Controller返回String,回转到对应的视图解析器进行视图解析。

响应纯字符串#

默认情况下,Controller返回String,回转到对应的视图解析器进行视图解析。

可以通过@ResponseBody注解返回String字符串,此时返回的不再是逻辑视图名称,而是直接返回text/html

Note

@ResponseBody采用的是StringHttpMessageConverter消息转换器将String字符串转换为text/html格式。

ResponseStringController.java
package com.luguosong.controller;

import com.luguosong.pojo.User;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("return-string")
public class ResponseStringController {

    /*
     * 模拟servlet返回String字符串
     *
     * 理论上应该返回逻辑视图名称,但此处返回null
     * 通过HttpServletResponse做出响应
     * */
    @RequestMapping("/servlet")
    public String servlet(HttpServletResponse resp) throws IOException {
        resp.setHeader("content-type", "text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("servlet输出字符串");
        return null;
    }


    /*
     * 模拟springMvc返回String字符串
     *
     * 添加@ResponseBody后,返回的不再是逻辑视图名称
     * 而是直接返回text/html
     * */
    @RequestMapping(value = "/springMvc", produces = "text/html; charset=utf-8")
    @ResponseBody
    public String springMvc() {
        return "Spring MVC输出字符串";
    }
}

响应JSON字符串#

在pom.xml引入处理json的依赖:

XML
<!--负责json字符串和java对象之间的转换-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

在spring mvc配置文件中需要配置:

XML
<mvc:annotation-driven/>
ResponseJSONStringController.java
package com.luguosong.controller;

import com.luguosong.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("return-json-string")
public class ResponseJSONStringController {

    /*
     * 返回json字符串
     * */
    @RequestMapping("user")
    @ResponseBody
    public User getUser() {
        User user = new User();
        user.setUsername("luguosong");
        return user;
    }
}

Note

当处理器方法上面有@ResponseBody注解,并返回一个Java对象,SpringMVC会自动将对象转为json字符串并响应。

此时使用的是MappingJackson2HttpMessageConverter消息转换器。

RestController注解#

在类上添加@RestController注解,等同于在该类上添加了@Controller注解,同时为该类的所有方法添加了@ResponseBody注解

ResponseEntity对象#

ResponseEntity对象可以定制响应协议,包括状态行、响应头和响应体。当想自定定制响应协议时,可以使用该类。

ResponseEntityController.java
package com.luguosong.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("/responseEntity")
public class ResponseEntityController {
    @RequestMapping("/springmvc")
    public ResponseEntity<String> springMvc() {
        /*
         * 模拟响应404
         * */
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
    }
}

文件下载#

FileDownloadController.java
package com.luguosong.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

/**
 * 文件下载
 *
 * @author luguosong
 */
@Controller
@RequestMapping("/fileDownload")
public class FileDownloadController {

    @RequestMapping("/springMvc")
    public ResponseEntity<byte[]> springMvc(
            HttpServletRequest request
    ) throws IOException {
        File file = new File(request.getServletContext().getRealPath("/upload") + "/demo.jpg");
        //创建响应头对象
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", file.getName());

        //下载文件
        ResponseEntity<byte[]> entity = new ResponseEntity<>(Files.readAllBytes(file.toPath()), headers, HttpStatus.OK);
        return entity;
    }
}

获取请求头信息#

获取请求头信息#

HeaderInfoController.java
package com.luguosong.controller.header_info;

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

/**
 * @author luguosong
 */
@Controller
@RequestMapping("/header-info")
public class HeaderInfoController {

    @PostMapping("/springMvc")
    public String springMvc(@RequestHeader(value = "content-type", required = false) String contentType) {
        System.out.println(contentType);
        return "header-info/form";
    }
}
CookieController.java
package com.luguosong.controller.header_info;

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

/**
 * @author luguosong
 */
@Controller
@RequestMapping("/cookie")
public class CookieController {

    /*
     * ❗cookie需要在父路径相同的情况才能保存下来
     * 因此通过该地址访问视图
     * */
    @GetMapping("/view")
    public String view() {
        return "header-info/form";
    }

    @RequestMapping("/springMvc")
    public String springMvc(@CookieValue("username") String username) {
        System.out.println(username);
        return "header-info/form";
    }
}

域对象操作#

request域#

RequestScopeController.java
package com.luguosong.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.Map;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("/request-scope")
public class RequestScopeController {

    /**
     * 模拟Servlet存储Request域
     */
    @RequestMapping("/servlet")
    public String servletTest(HttpServletRequest request) {
        request.setAttribute("requestScope", "通过HttpServletRequest设置请求域");
        return "request-scope";
    }

    /*
     * Model对象存储request域
     * */
    @RequestMapping("/model")
    public String modelTest(Model model) {
        model.addAttribute("requestScope", "通过Model对象设置请求域");
        return "request-scope";
    }

    /*
     * Map集合存储request域
     * */
    @RequestMapping("/map")
    public String mapTest(Map<String, Object> map) {
        map.put("requestScope", "通过Map对象设置请求域");
        return "request-scope";
    }

    /*
     * ModelMap对象存储request域
     * */
    @RequestMapping("/modelMap")
    public String modelMapTest(ModelMap modelMap) {
        modelMap.addAttribute("requestScope", "通过ModelMap对象设置请求域");
        return "request-scope";
    }

    /*
     * ⭐ModelAndView对象存储request域
     * */
    @RequestMapping("/modelAndView")
    public ModelAndView modelAndViewTest() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("requestScope", "通过ModelAndView对象设置请求域");
        modelAndView.setViewName("request-scope");
        return modelAndView;
    }


}
BindingAwareModelMap类结构

不管是Model对象Map集合还是ModelMap对象,实际创建的的都是BindingAwareModelMap对象

Spring MVC为了更好的体现MVC架构模式,还提供了ModelAndView类 ,这个类封装了Model和View。也就是说这个类封装业务处理之后的数据,体支持跳转指定视图。通过ModelAndView也可以设置请求域。

session域#

SessionScopeController.java
package com.luguosong.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("/session-scope")
@SessionAttributes({"sessionScope"})
public class SessionScopeController {

    /*
    * 通过Servlet方式设置session
    * */
    @RequestMapping("/servlet")
    public String servletTest(HttpServletRequest request) {
        request.getSession().setAttribute("sessionScope", "通过Servlet原生方式设置session域");
        System.out.println(request.getSession().getAttribute("sessionScope"));
        return "session-scope";
    }

    /*
    * 通过@SessionAttributes注解设置session
    * */
    @RequestMapping("/modelAndView")
    public ModelAndView modelAndViewTest() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("sessionScope", "通过@SessionAttributes注解方式设置session域");
        modelAndView.setViewName("session-scope");
        return modelAndView;
    }
}

一般情况下modelAndView.addObject是设置request域的,但通过@SessionAttributes({"xxx"})注解可以指定特定字段为session域。

application域#

ApplicationScopeController.java
package com.luguosong.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author luguosong
 */
@Controller
@RequestMapping("/application-scope")
public class ApplicationScopeController {

    @RequestMapping("/servlet")
    public String servletTst(HttpServletRequest request) {
        request.getServletContext().setAttribute("applicationScope", "通过Servlet方式设置application域");
        return "application-scope";
    }
}

一般直接采用Servlet原始方式设置application域。

视图(View)#

常见的视图#

  • InternalResourceView:内部资源视图,Spring MVC框架内置,专门为JSP模板语法准备
  • RedirectView:重定向视图,Spring MVC框架内置,用来完成重定向效果
  • ThymeLeafView:Thymeleaf 是一种现代化的服务器端 Java 模板引擎,适用于网页和独立环境。Thymeleaf 的主要目标是为您的开发流程带来优雅的自然模板——这些 HTML 可以在浏览器中正确显示,同时也能作为静态原型使用,从而增强开发团队的协作。它提供了 Spring Framework 的模块、与您喜爱的工具的多种集成,并允许您插入自己的功能,因此 Thymeleaf 非常适合现代 HTML5 JVM 的网页开发——尽管它的功能远不止于此。
  • FreeMarkerView:Apache FreeMarker™ 是一个模板引擎:它是一个 Java 库,用于根据模板和变化的数据生成文本输出(如 HTML 网页、电子邮件、配置文件、源代码等)。模板使用 FreeMarker 模板语言(FTL)编写,这是一种简单的专用语言(不像 PHP 那样是完整的编程语言)。通常,会使用通用编程语言(如 Java)来准备数据(执行数据库查询、进行业务计算)。然后,Apache FreeMarker 使用模板展示这些准备好的数据。在模板中,你专注于如何展示数据,而在模板之外,你专注于展示哪些数据。
  • VelocityView:VelocityView 包含所有的 GenericTools,并增加了在 Web 应用程序(Java EE 项目)视图层中使用 Velocity 的基础设施和专用工具。这包括用于处理 Velocity 模板请求的 VelocityViewServlet 或 VelocityLayoutServlet,以及用于在 JSP 中嵌入 Velocity 的 VelocityViewTag。
  • PDFView:第三方,用于生成pdf文件视图
  • ExcelView:第三方,用于生成excel文件视图

配置JSP视图解析器#

springmvc-servlet.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">
    <!--配置组件扫描-->
    <context:component-scan base-package="com.luguosong.controller"/>

    <!--配置JSP视图解析器-->
    <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/templates/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

配置Thymeleaf视图解析器#

springmvc-servlet.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">
    <!--配置组件扫描-->
    <context:component-scan base-package="com.luguosong.controller"/>

    <!--配置视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <property name="characterEncoding" value="UTF-8"/>
        <property name="order" value="1"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
</beans>

视图控制器#

如何仅仅是进行视图转发,无需编写Controller类,可以通过spring mvc配置文件mvc:view-controller标签进行配置。

springmvc-servlet.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">

    <!--配置组件扫描-->
    <context:component-scan base-package="com.luguosong.controller"/>

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

    <!--在Spring MVC配置文件中配置视图控制器-->
    <mvc:view-controller path="/test" view-name="test"/>

    <!--<mvc:view-controller/>会让Spring MVC项目中的注解失效,需要重新开启-->
    <!--开启注解驱动-->
    <mvc:annotation-driven/>
</beans>

转发和重定向#

Java
package com.luguosong.controller;

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

/**
 * @author luguosong
 */
@Controller
public class TestController {

    /*
    * 转发
    * */
    @RequestMapping("forward")
    public String forwardTest() {
        return "forward:/test";
    }

    /*
    * 重定向
    * */
    @RequestMapping("redirect")
    public String redirectTest() {
        return "redirect:/test";
    }

    @RequestMapping("/test")
    public String test() {
        return "test";
    }
}

静态资源访问#

由于DispatcherServlet的url-pattern配置的是/,访问静态资源会经过DispatcherServletDispatcherServlet没有静态资源处理。

方式一:开启默认Servlet#

Tomcat目录conf/web.xml中存在名为default的servlet

DefaultServlet

url-patternDispatcherServlet一样也是/,因此默认servlet访问被DispatcherServlet覆盖。

default Servlet servlet-mapping

Spring MVC仍然允许静态资源请求由Tomcat的默认Servlet处理。它配置了一个DefaultServletHttpRequestHandler,URL映射为/** ,并且相对于其他URL映射具有最低优先级

以下示例展示了如何通过默认设置启用该功能:

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

以下示例展示了如何在Spring MVC配置文件 XML 中实现相同的配置:

XML
<beans>
    <mvc:default-servlet-handler/>
    <mvc:annotation-driven/>
</beans>

方式二:配置静态资源处理#

在下一个示例中,对于以 /resources 开头的请求,将使用相对路径来查找和提供相对于 Web 应用程序根目录下的 /public 或类路径下的 /static 的静态资源。这些资源的过期时间设置为一年,以确保最大限度地利用浏览器缓存并减少浏览器发出的 HTTP 请求。Last-Modified 信息通过 Resource#lastModified 推断,以支持带有 "Last-Modified" 头的 HTTP 条件请求。

以下列表展示了如何使用 Java 配置来实现:

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public", "classpath:/static/")
                .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));
    }
}

以下示例展示了如何在 XML 中实现相同的配置:XML

XML
<beans>
    <mvc:resources mapping="/resources/**"
                   location="/public, classpath:/static/"
                   cache-period="31556926"/>
    <mvc:annotation-driven/>
</beans>

RESTFul#

概述#

RESTFul是web服务接口的一种设计风格。提供了一套约束,可以让web服务接口更加简介、易于理解。

  • 查询:使用GET方法请求
  • 添加:使用POST方法请求
  • 更新:使用PUT方法请求
  • 删除:使用DELETE方法请求

请求参数从/springmvc/getUserById?id=1风格转为/springmvc/user/1风格,变得更加简洁。

HiddenHttpMethodFilter#

理论上表单只能发送get请求post请求

但是可以借助HiddenHttpMethodFilter过滤器,将post方法转为putdeletepatch方法。

示例#

模拟通过表单发送getpostputdelete请求。

在web.xml中配置HiddenHttpMethodFilter过滤器

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--优化:让DispatcherServlet在启动时就加载,而不是首次访问时加载,提高首次访问速度-->
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--url-pattern配置/表示处理除jsp外的所有请求-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>


    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

Controller中的@RequestMapping地址是一样的,通过请求方法区分请求:

Java
package com.luguosong.controller;

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

/**
 * @author luguosong
 */
@Controller

public class TestController {
    /*
     * 执行get方法,表示查询
     * */
    @RequestMapping(value = "user", method = RequestMethod.GET)
    public String get() {
        System.out.println("执行get方法");
        return "test";
    }

    /*
     * 执行post方法,表示新增
     * */
    @RequestMapping(value = "user", method = RequestMethod.POST)
    public String post() {
        System.out.println("执行post方法");
        return "test";
    }

    /*
     * 执行put方法,表示修改
     * */
    @RequestMapping(value = "user", method = RequestMethod.PUT)
    public String put() {
        System.out.println("执行put方法");
        return "test";
    }

    /*
     * 执行delete方法,表示删除
     * */
    @RequestMapping(value = "user", method = RequestMethod.DELETE)
    public String delete() {
        System.out.println("执行delete方法");
        return "test";
    }
}

表单发送不同方法的请求:

HTML
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Document</title>
</head>
<body>
<form th:action="@{/user}" method="get">
    <input type="submit" value="提交get请求">
</form>

<form th:action="@{/user}" method="post">
    <input type="submit" value="提交post请求">
</form>

<form th:action="@{/user}" method="post">
    <input type="hidden" name="_method" value="put">
    <input type="submit" value="提交put请求">
</form>

<form th:action="@{/user}" method="post">
    <input type="hidden" name="_method" value="delete">
    <input type="submit" value="提交delete请求">
</form>
</body>
</html>

异常处理器#

Controller在执行过程中发生异常,通过异常处理器跳转到对应视图,在视图上展示友好信息。

Spring MVC提供了一个接口:HandlerExceptionResolver,用于处理异常。

异常处理器接口

DefaultHandlerExceptionResolver#

DefaultHandlerExceptionResolver是Spring MVC默认的异常处理器。

比如Post方法的Controller方法,通过Get请求访问,就会进入这个处理器。

SimpleMappingExceptionResolver#

SimpleMappingExceptionResolver可以让我们自定义异常处理。

方式一:通过spring mvc配置文件配置SimpleMappingExceptionResolver

springmvc-servlet.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">
    <!--配置组件扫描-->
    <context:component-scan base-package="com.luguosong.controller"/>

    <!--配置视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <property name="characterEncoding" value="UTF-8"/>
        <property name="order" value="1"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <!--配置异常处理器-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!--表示发生错误跳转到error视图-->
                <prop key="java.lang.Exception">error</prop>
            </props>
        </property>
        <!--表示将错误存储在request域,域名为errMsg-->
        <property name="exceptionAttribute" value="errMsg"/>
    </bean>
</beans>

方式二:使用注解

ExceptionController.java
package com.luguosong.controller;


import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @author luguosong
 */
@ControllerAdvice
public class ExceptionController {

    @ExceptionHandler
    public String error(Exception e, Model model){
        model.addAttribute("errMsg", e);
        return "error";
    }
}

拦截器(Interceptor)#

概述#

Spring MVC拦截器作用是在请求到达Controller之前之后进行拦截,可以对请求和响应进行一些特殊处理。

常见用途:

  • 登录验证
  • 权限校验
  • 请求日志
  • 更改响应

示例#

通过实现HandlerInterceptor接口,实现拦截器。

preHandle方法如果返回false,请求将被拦截,不会再执行后续的拦截器和Controller。

编写拦截器:

Interceptor1.java
package com.luguosong.interceptors;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aopalliance.intercept.Interceptor;

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

/**
 * @author luguosong
 */
@Component
public class Interceptor1 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Interceptor1 =>preHandle 执行");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Interceptor1 =>postHandle 执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Interceptor1 =>afterCompletion 执行");
    }
}
Interceptor2.java
package com.luguosong.interceptors;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aopalliance.intercept.Interceptor;

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

/**
 * @author luguosong
 */
@Component
public class Interceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Interceptor2 =>preHandle 执行");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Interceptor2 =>postHandle 执行");
    }

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

在spring mvc配置文件中配置拦截器:

springmvc-servlet.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">
    <!--配置组件扫描-->
    <context:component-scan base-package="com.luguosong.controller,com.luguosong.interceptors"/>

    <!--配置视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <property name="characterEncoding" value="UTF-8"/>
        <property name="order" value="1"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <!--配置拦截器-->
    <mvc:interceptors>
        <!--方式一-->
        <!--<bean class="com.luguosong.interceptors.Interceptor1"/>-->

        <!--方式二:在Interceptor1上添加@Component注解,使用ref进行引用-->
        <!--<ref bean="interceptor1"/>-->

        <!--方式三:指定拦截路径-->
        <mvc:interceptor>
            <!--表示拦截所有请求-->
            <mvc:mapping path="/**"/>
            <!--排除指定请求-->
            <mvc:exclude-mapping path="/hello-interceptor2"/>
            <!--指定拦截器-->
            <ref bean="interceptor1"/>
        </mvc:interceptor>
        <mvc:interceptor>
            <!--表示拦截所有请求-->
            <mvc:mapping path="/**"/>
            <!--排除指定请求-->
            <mvc:exclude-mapping path="/hello-interceptor2"/>
            <!--指定拦截器-->
            <ref bean="interceptor2"/>
        </mvc:interceptor>

    </mvc:interceptors>
</beans>

全注解开发#

编写Spring 配置类,继承AbstractAnnotationConfigDispatcherServletInitializer类,相当于web.xml

WebAppInitialize.java
package com.luguosong.config;

import jakarta.servlet.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * 该类相当于web.xml
 *
 * @author luguosong
 */
@Configuration
public class WebAppInitialize extends AbstractAnnotationConfigDispatcherServletInitializer {
    /*
     * Spring配置
     * */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    /*
     * Spring MVC配置
     * */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        // 指定Spring MVC配置类
        return new Class[]{SpringMvcConfig.class};
    }

    /*
     * 配置DispatcherServlet的url-pattern
     * */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /*
     * 配置过滤器
     * */
    @Override
    protected Filter[] getServletFilters() {

        // 配置字符编码过滤器
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceRequestEncoding(true);
        characterEncodingFilter.setForceResponseEncoding(true);
        //配置过滤器,让form表单支持"PUT"、"DELETE"和"PATCH"方法
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();

        return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
    }
}

Spring MVC配置类:

SpringMvcConfig.java
package com.luguosong.config;

import com.luguosong.interceptors.MyInterceptor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;

import java.time.Duration;
import java.util.List;
import java.util.Properties;

/**
 * 注解@ComponentScan配置包扫描,相当于<context:component-scan/>标签
 * 注解@EnableWebMvc开启Spring MVC注解驱动,相当于<mvc:annotation-driven/>标签
 *
 * @author luguosong
 */
@Configuration
@ComponentScan("com.luguosong.controller")
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
    /*
     * 配置Thymeleaf视图解析器
     * */
    @Bean
    public ThymeleafViewResolver getThymeleafViewResolver(SpringTemplateEngine springTemplateEngine) {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(springTemplateEngine);
        resolver.setCharacterEncoding("UTF-8");
        resolver.setOrder(1);
        return resolver;
    }

    @Bean
    public SpringTemplateEngine getSpringTemplateEngine(ITemplateResolver iTemplateResolver) {
        SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
        springTemplateEngine.setTemplateResolver(iTemplateResolver);
        return springTemplateEngine;
    }

    @Bean
    public ITemplateResolver getITemplateResolver(ApplicationContext applicationContext) {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setApplicationContext(applicationContext);
        resolver.setPrefix("/WEB-INF/templates/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode(TemplateMode.HTML);
        resolver.setCharacterEncoding("UTF-8");
        resolver.setCacheable(false); //开发环境关闭缓存,生产环境建议开启缓存
        return resolver;
    }

    /*
     * 开启静态资源访问
     * */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    /*
     * 配置试图控制器
     * */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/view-controller").setViewName("view-controller");
    }

    /*
     * 配置异常处理器
     * */
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        properties.setProperty("java.lang.Exception", "error");
        resolver.setExceptionMappings(properties);
        resolver.setExceptionAttribute("errMsg");
        resolvers.add(resolver);
    }

    /*
     * 配置拦截器
     * */

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        MyInterceptor myInterceptor = new MyInterceptor();

        registry.addInterceptor(myInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/error");
    }
}

评论