文章

01.Spring MVC入门

01.Spring MVC入门

Spring MVC

MVC 概述

在早期 Java Web 的开发中,统一把显示层、控制层、数据层的操作全部交给 JSP 或者 JavaBean 来进行处理,我们称之为 Model1:

600

出现的弊端:

  • JSP 和 Java Bean 之间严重耦合,Java 代码和 HTML 代码也耦合在了一起
  • 要求开发者不仅要掌握 Java ,还要有高超的前端水平
  • 前端和后端相互依赖,前端需要等待后端完成,后端也依赖前端完成,才能进行有效的测试
  • 代码难以复用

正因为上面的种种弊端,所以很快这种方式就被 Servlet + JSP + Java Bean 所替代了,早期的 MVC 模型 (Model2) 就像下图这样:

首先用户的请求会到达 Servlet,然后根据请求调用相应的 Java Bean,并把所有的显示结果交给 JSP 去完成,这样的模式我们就称为 MVC 模式。

  • M 代表模型(Model) 模型是什么呢? 模型就是数据,就是 dao, bean
  • V 代表 视图(View) 视图是什么呢? 就是网页, JSP,用来展示模型中的数据
  • C 代表 控制器(controller) 控制器是什么? 控制器的作用就是把不同的数据 (Model),显示在不同的视图 (View) 上,Servlet 扮演的就是这样的角色。

扩展阅读:Web开发模式

MVC 是一种软件架构的思想,将软件按照模型、视图、控制器来划分

M:Model,模型层,指工程中的 JavaBean,作用是处理数据

JavaBean 分为两类:

  • 一类称为实体类 Bean:专门存储业务数据的,如 Student、User 等
  • 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。

V:View,视图层,指工程中的 html 或 jsp 等页面,作用是与用户进行交互,展示数据

C:Controller,控制层,指工程中的 servlet,作用是接收请求和响应浏览器

MVC 的工作流程: 用户通过视图层发送请求到服务器,在服务器中请求被 Controller 接收,Controller 调用相应的 Model 层处理请求,处理完毕将结果返回到 Controller,Controller 再根据请求处理的结果找到相应的 View 视图,渲染数据后最终响应给浏览器

什么是 Spring MVC?

SpringMVC 是 Spring 的一个后续产品,是 Spring 的一个子项目

SpringMVC 是 Spring 为表述层开发提供的一整套完备的解决方案。在表述层框架历经 Strust、WebWork、Strust2 等诸多产品的历代更迭之后,目前业界普遍选择了 SpringMVC 作为 Java EE 项目表述层开发的首选方案

注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台 servlet

SpringMVC 的特点

  • Spring 家族原生产品,与 IOC 容器等基础设施无缝对接
  • 基于原生的 Servlet,通过功能强大的前端控制器 DispatcherServlet,对请求和响应进行统一处理
  • 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
  • 代码清新简洁,大幅度提升开发效率
  • 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
  • 性能卓著,尤其适合现代大型、超大型互联网项目要求

Spring MVC 的架构

为解决持久层中一直未处理好的数据库事务的编程,又为了迎合 NoSQL 的强势崛起,Spring MVC 给出了方案:

Hello Spring MVC

xml+ 注解混合

开发环境

  • IDE:idea 2024.2
  • 构建工具:maven 4.0
  • 服务器:tomcat 9
  • Spring 版本:5.3.15

项目目录

![600](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/202411060034945.png)

新建 Maven 工程,配置 pom.xml

  • pom.xml 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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>me.hacket</groupId>
    <artifactId>SpringMVCDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- 设置为web工程 -->
    <packaging>war</packaging>

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

    <!-- 导入SpringMVC相关坐标-->
    <dependencies>
        <!--springMVC坐标-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.15</version>
        </dependency>
        <!--servlet坐标-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- Spring5和Thymeleaf整合包 -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.12.RELEASE</version>
        </dependency>
    </dependencies>

</project>

配置 web.xml

注册 SpringMVC 的前端控制器DispatcherServlet

默认配置方式

此配置作用下,SpringMVC 的配置文件默认位于 WEB-INF 下,默认名称为 -servlet.xml,例如,以下配置所对应 SpringMVC 的配置文件位于 WEB-INF 下,文件名为 springMVC-servlet.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <!--
        设置springMVC的核心控制器所能处理的请求的请求路径
        /所匹配的请求可以是/login或.html或.js或.css方式的请求路径
        但是/不能匹配.jsp请求路径的请求
    -->
    <url-pattern>/</url-pattern>
</servlet-mapping>
扩展配置方式

可通过 init-param 标签设置 SpringMVC 配置文件的位置和名称,通过 load-on-startup 标签设置 SpringMVC 前端控制器 DispatcherServlet 的初始化时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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">
    <!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
        <init-param>
            <!-- contextConfigLocation为固定值 -->
            <param-name>contextConfigLocation</param-name>
            <!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources -->
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--
             作为框架的核心组件,在启动过程中有大量的初始化操作要做
            而这些操作放在第一次请求时才执行会严重影响访问速度
            因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <!--
            设置springMVC的核心控制器所能处理的请求的请求路径
            /所匹配的请求可以是/login或.html或.js或.css方式的请求路径
            但是/不能匹配.jsp请求路径的请求
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

标签中使用 //* 的区别:

  • / 所匹配的请求可以是/login 或.html 或.js 或.css 方式的请求路径,但是/不能匹配.jsp 请求路径的请求;因此就可以避免在访问 jsp 页面时,该请求被 DispatcherServlet 处理,从而找不到相应的页面
  • /* 则能够匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/* 的写法

请求控制器

由于前端控制器对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理过程,因此需要创建处理具体请求的类,即请求控制器

请求控制器中每一个处理请求的方法成为控制器方法

因为 SpringMVC 的控制器由一个 POJO(普通的 Java 类)担任,因此需要通过 @Controller 注解将其标识为一个控制层组件,交给 Spring 的 IoC 容器管理,此时 SpringMVC 才能够识别控制器的存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Controller
@SuppressWarnings("all")
public class HelloController {

    // @RequestMapping注解:处理请求和控制器方法之间的映射关系
	// @RequestMapping注解的value属性可以通过请求地址匹配请求,/表示的当前工程的上下文路径
	// localhost:8080/springMVC/
	@RequestMapping("/")
	public String index() {
	    //设置视图名称
	    return "index";
	}

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

}

创建 springMVC 的配置文件: springmvc-servlet.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?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/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 自动扫描包 -->
    <context:component-scan base-package="me.hacket.controller"/>

    <!-- 配置Thymeleaf视图解析器 -->
    <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        <!-- 视图前缀 -->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!-- 视图后缀 -->
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8" />
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
    <!--
       处理静态资源,例如html、js、css、jpg
      若只设置该标签,则只能访问静态资源,其他请求则无法访问
      此时必须设置<mvc:annotation-driven/>解决问题
     -->
    <!--<mvc:default-servlet-handler/>

    &lt;!&ndash; 开启mvc注解驱动 &ndash;&gt;
    <mvc:annotation-driven>
        <mvc:message-converters>
            &lt;!&ndash; 处理响应中文内容乱码 &ndash;&gt;
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="defaultCharset" value="UTF-8" />
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html</value>
                        <value>application/json</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>-->

</beans>

测试

![400](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20241106170711.png)

纯注解(无 web.xml)

注解配置 SpringMVC(去掉 web.xml 和 mvc 配置文件)

src/main 下创建 webapp/WEB-INF 目录

![300](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/202411130105477.png)

依赖 pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?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>me.hacket</groupId>
    <artifactId>SpringMVCAnnotationDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <dependencies>
        <!-- SpringMVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.1</version>
        </dependency>

        <!-- ServletAPI -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- Spring5和Thymeleaf整合包 -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.12.RELEASE</version>
        </dependency>


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
    </dependencies>

</project>

创建初始化类 WebInit.java,代替 web.xml

在 Servlet3.0 环境中,容器会在类路径中查找实现 javax.servlet.ServletContainerInitializer 接口的类,如果找到的话就用它来配置 Servlet 容器。Spring 提供了这个接口的实现,名为 SpringServletContainerInitializer,这个类反过来又会查找实现 WebApplicationInitializer 的类并将配置的任务交给它们来完成。Spring3.2 引入了一个便利的 WebApplicationInitializer 基础实现,名为 AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了 AbstractAnnotationConfigDispatcherServletInitializer 并将其部署到 Servlet3.0 容器的时候,容器会自动发现它,并用它来配置 Servlet 上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * web工程的初始化,用来代替web.xml
 */
// spring-webmvc< 5.3.x有,5.2.x没有
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * 获取Spring的根配置类,用于配置Spring容器
     *
     * @return 根配置类数组
     */
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    /**
     * 获取SpringMVC的配置类,用于配置SpringMVC容器
     *
     * @return SpringMVC配置类数组
     */
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    /**
     * 指定DispatcherServlet的映射规则,即url-pattern
     *
     * @return 映射路径数组
     */
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

//    /**
//     * 注册过滤器
//     * @return
//     */
//    @Override
//    protected Filter[] getServletFilters() {
//        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();;
//        characterEncodingFilter.setEncoding("UTF-8");
//        characterEncodingFilter.setForceResponseEncoding(true);
//        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
//        return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
//    }
}

创建 SpringConfig 配置类,代替 Spring 的配置文件

1
2
3
4
5
6
7
8
// 创建SpringConfig配置类,代替spring的配置文件
@Configuration
@ComponentScan("me.hacket.service") // 扫描指定包下的组件和Bean
@PropertySource("classpath:jdbc.properties") // 加载jdbc.properties属性文件
//@Import({JdbcConfig.class, MyBatisConfig.class}) // 导入JdbcConfig和MyBatisConfig配置类
@EnableTransactionManagement //开启事务
public class SpringConfig {
}

创建 SpringMvcConfig 配置类,代替 SpringMVC 的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
 * 代替springMVC配置文件:
 * 1、扫描组件  2、视图解析器  3、view-controller  4、default-servlet-handler   5、mvc注解驱动
 * 6、文件上传解析器   7、异常处理    8、拦截器
 */
@Configuration

// 1、扫描组件
@ComponentScan("me.hacket.controller") // 扫描控制器组件所在的包
// 5、mvc注解驱动
@EnableWebMvc // 启用Spring MVC功能
public class SpringMvcConfig implements WebMvcConfigurer {

    // 使用默认的servlet处理静态资源
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    // 配置拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        TestInterceptor testInterceptor = new TestInterceptor();
        registry.addInterceptor(testInterceptor).addPathPatterns("/**");
    }

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

    // 配置文件上传解析器
    @Bean
    public CommonsMultipartResolver multipartResolver() {
        return new CommonsMultipartResolver();
    }

    // 配置异常映射
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
        Properties prop = new Properties();
        prop.setProperty("java.lang.ArithmeticException", "error");
        //设置异常映射
        exceptionResolver.setExceptionMappings(prop);
        //设置共享异常信息的键
        exceptionResolver.setExceptionAttribute("ex");
        resolvers.add(exceptionResolver);
    }

    // 配置生成模板解析器
    @Bean
    public ITemplateResolver templateResolver() {
        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        // ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
        assert webApplicationContext != null;
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
                Objects.requireNonNull(webApplicationContext.getServletContext()));
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        return templateResolver;
    }

    // 生成模板引擎并为模板引擎注入模板解析器
    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }

    // 生成视图解析器并未解析器注入模板引擎
    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }
}

测试

1
2
3
4
5
6
7
8
@Controller
public class UserController {

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

Spring MVC 基本使用

@RequestMapping 注解

@RequestMapping 的功能

从注解名称上我们可以看到,@RequestMapping 注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系。

SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的控制器方法来处理这个请求。

@RequestMapping 的位置

  • @RequestMapping 标识一个类:设置映射请求的请求路径的初始信息
  • @RequestMapping 标识一个方法:设置映射请求请求路径的具体信息
1
2
3
4
5
6
7
8
9
10
@Controller
@RequestMapping("/test")
public class RequestMappingController {

	// 此时请求映射所映射的请求的请求路径为:/test/testRequestMapping
    @RequestMapping("/testRequestMapping")
    public String testRequestMapping(){
        return "success";
    }
}

@RequestMapping 属性

value 属性
  • @RequestMapping 注解的 value 属性通过请求的请求地址匹配请求映射
  • @RequestMapping 注解的 value 属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求
  • @RequestMapping 注解的 value 属性必须设置,至少通过请求地址匹配请求映射
1
2
3
4
5
6
@RequestMapping(
  value = {"/testRequestMapping", "/test"}
)
public String testRequestMapping(){
    return "success";
}

请求 /testRequestMapping/test 都可以:

1
2
<a th:href="@{/testRequestMapping}">测试@RequestMapping的value属性-->/testRequestMapping</a><br>
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
method 属性
  • @RequestMapping 注解的 method 属性通过请求的请求方式(get 或 post)匹配请求映射
  • @RequestMapping 注解的 method 属性是一个 RequestMethod 类型的数组,表示该请求映射能够匹配多种请求方式的请求
  • 若当前请求的请求地址满足请求映射的 value 属性,但是请求方式不满足 method 属性,则浏览器报错 405:Request method ‘POST’ not supported
1
2
3
4
5
6
7
8
9
10
11
12
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
<form th:action="@{/test}" method="post">
    <input type="submit">
</form>

@RequestMapping(
        value = {"/testRequestMapping", "/test"},
        method = {RequestMethod.GET, RequestMethod.POST}
)
public String testRequestMapping(){
    return "success";
}
![300](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20241106171748.png)

对于处理指定请求方式的控制器方法,SpringMVC 中提供了 @RequestMapping 的派生注解

  • 处理 get 请求的映射–>@GetMapping
  • 处理 post 请求的映射–>@PostMapping
  • 处理 put 请求的映射–>@PutMapping
  • 处理 delete 请求的映射–>@DeleteMapping

@GetMapping 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * @description: GetMapping表示get请求
 * @author: Xuan Li
 * @date: 2021/10/6 21:46
*/
@GetMapping("testGet")
public String test(){
	return "target";
}

/**
 * @description: params表示必须携带的参数 !params表示不能携带的参数,headers请求头信息
 * @author: Xuan Li
 * @date: 2021/10/6 21:53
*/
@GetMapping(value = "testParams",params = "password",headers = "host=localhost:8080")
public String params(){
	return "target";
}

常用的请求方式有 get,post,put,delete

  • 但是目前浏览器只支持 get 和 post,若在 form 表单提交时,为 method 设置了其他请求方式的字符串(put 或 delete),则按照默认的请求方式 get 处理
  • 若要发送 put 和 delete 请求,则需要通过 Spring 提供的过滤器 HiddenHttpMethodFilter
params 属性(了解)
  • @RequestMapping 注解的 params 属性通过请求的请求参数匹配请求映射
  • @RequestMapping 注解的 params 属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系
    • param“:要求请求映射所匹配的请求必须携带 param 请求参数
    • !param“:要求请求映射所匹配的请求必须不能携带 param 请求参数
    • param=value“:要求请求映射所匹配的请求必须携带 param 请求参数且 param=value
    • param!=value“:要求请求映射所匹配的请求必须携带 param 请求参数但是 param!=value
1
2
3
4
5
6
7
8
@RequestMapping(
	value = {"/testRequestMapping", "/test"} ,
	method = {RequestMethod.GET, RequestMethod.POST} ,
	params = {"username","password!=123456"}
)
public String testRequestMapping(){
    return "success";
}

测试

1
<a th:href="@{/test(username='admin',password=123456)">测试@RequestMapping的params属性-->/test</a><br>

若当前请求满足@RequestMapping 注解的 value 和 method 属性,但是不满足 params 属性,此时页面回报错 400:Parameter conditions “username, password!=123456” not met for actual request parameters: username={admin}, password={123456}

headers 属性(了解)
  • @RequestMapping 注解的 headers 属性通过请求的请求头信息匹配请求映射
  • @RequestMapping 注解的 headers 属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系
    • “header”:要求请求映射所匹配的请求必须携带 header 请求头信息
    • “!header”:要求请求映射所匹配的请求必须不能携带 header 请求头信息
    • “header=value”:要求请求映射所匹配的请求必须携带 header 请求头信息且 header=value
    • “header!=value”:要求请求映射所匹配的请求必须携带 header 请求头信息且 header!=value

若当前请求满足@RequestMapping 注解的 value 和 method 属性,但是不满足 headers 属性,此时页面显示 404 错误,即资源未找到

SpringMVC 支持 ant 风格的路径

  • :表示任意的单个字符
  • *:表示任意的 0 个或多个字符
  • **:表示任意的一层或多层目录

注意:在使用 ** 时,只能使用/**/xxx 的方式

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * ?匹配任意一个字符
 * *匹配0或多个字符
 * **表示任意一层或多层目录
*/
@SuppressWarnings("all")
//    @GetMapping(value = "testAnt?")
//    @GetMapping(value = "testAnt*")
@GetMapping(value = "/**/testAnt")
public String testAnt(){
	return "target";
}

SpringMVC 支持路径中的占位符(重点)

原始方式:/deleteUser?id=1

RESTful 方式:/deleteUser/1

SpringMVC 路径中的占位符常用于 RESTful 风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping 注解的 value 属性中通过占位符 {xxx} 表示传输的数据,再通过通过 @PathVariable 注解,将占位符所表示的数据赋值给控制器方法的形参。

1
2
3
4
5
@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id") String id, @PathVariable("username") String username){
    System.out.println("id:"+id+",username:"+username);
    return "success";
}

访问:/testRest/1/admin

输出:id:1,username:admin

SpringMVC 获取请求参数

SpringMVC 中,接收页面提交的数据是通过方法的形参来接收的,而不是在 Controller 类定义成员变更接收

通过 ServletAPI 获取

HttpServletRequest 作为控制器方法的形参,此时 HttpServletRequest 类型的参数表示封装了当前请求的请求报文的对象

1
2
3
4
5
6
7
8
9
@GetMapping
@RequestMapping(value = {"/user"})
public String user(HttpServletRequest request) {
	System.out.println("get /user");
	String username = request.getParameter("username");
	String password = request.getParameter("password");
	System.out.println("username:" + username + ",password:" + password);
	return "success";
}

测试:http://localhost:8080/SpringMVCDemo_war_exploded/user?username=hacket&password=123

通过 Controller 方法的形参获取请求参数

在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在 DispatcherServlet 中就会将请求参数赋值给相应的形参

1
2
3
4
5
6
7
@GetMapping
@RequestMapping(value = {"/user2"})
public String user(String username, String password) {
	System.out.println("get /user2");
	System.out.println("username:" + username + ",password:" + password);
	return "success";
}

测试:http://localhost:8080/SpringMVCDemo_war_exploded/user2?username=hacket&password=123

  • 若请求所传输的请求参数中有多个同名的请求参数,此时可以在控制器方法的形参中设置字符串数组或者字符串类型的形参接收此请求参数
  • 若使用字符串数组类型的形参,此参数的数组中包含了每一个数据
  • 若使用字符串类型的形参,此参数的值为每个数据中间使用逗号拼接的结果

@RequestParam

@RequestParam 是将请求参数和控制器方法的形参创建映射关系,@RequestParam 注解一共有三个属性:

  • value:指定为形参赋值的请求参数的参数名
  • required:设置是否必须传输此请求参数,默认值为 true。
    • 若设置为 true 时,则当前请求必须传输 value 所指定的请求参数,若没有传输该请求参数,且没有设置 defaultValue 属性,则页面报错 400:Required String parameter ‘xxx’ is not present
    • 若设置为 false,则当前请求不是必须传输 value 所指定的请求参数,若没有传输,则注解所标识的形参的值为 null
  • defaultValue:不管 required 属性值为 true 或 false,当 value 所指定的请求参数没有传输或传输的值为 “” 时,则使用默认值为形参赋值
1
2
3
4
5
6
7
@GetMapping
@RequestMapping(value = {"/user3"})
public String user3(@RequestParam(value = "name") String username, @RequestParam(value = "pwd") String password) {
	System.out.println("get /user3");
	System.out.println("username:" + username + ",password:" + password);
	return "success";
}

测试:http://localhost:8080/SpringMVCDemo_war_exploded/user3?name=hacket&pwd=123 如果用 http://localhost:8080/SpringMVCDemo_war_exploded/user3?username=hacket&password=123 测试会报 404 错误。

@RequestBody 、@RequestParam 和 @PathVariable 区别

区别

  • @RequestParam 用于接收 url 地址传参,表单传参【application/x-www-form-urlencoded
  • @RequestBody 用于接收 json 数据【application/json
  • @PathVariable 用于接收路径参数,使用 {参数名称} 描述路径参数

应用

  • 后期开发中,发送 json 格式数据为主,@RequestBody 应用较广
  • 如果发送非 json 格式数据,选用 @RequestParam 接收请求参数
  • 采用 RESTful 进行开发,当参数数量较少时,例如 1 个,可以采用 @PathVariable 接收请求路径变量,通常用于传递 id 值

@RequestHeader

@RequestHeader 注解用于将请求头中的值绑定到方法参数上。它可以用于获取特定请求头的值,或者获取所有请求头的值。该注解可以应用于方法参数、方法、类级别的处理器方法上。

@RequestHeader 注解一共有三个属性:valuerequireddefaultValue,用法同@RequestParam

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RequestMapping("/testHeader")
public String testHeader(@RequestHeader(value = "host") String host, @CookieValue("Idea-c928d5a5") String cookie){
	System.out.println(host);
	System.out.println(cookie);
	return "target";
}

// - 获取特定请求头的值
@GetMapping("/example")
public void exampleMethod(@RequestHeader("User-Agent") String userAgent) {
    // 处理方法逻辑
}
// - 获取所有请求头的值
@GetMapping("/init2")
public void init2(@RequestHeader Map<String, String> headerMap) {
    
    // 使用Map接收所有的请求头
    System.out.println(headerMap);
    // js中使用header名为addressList,使用map接收后需要使用addresslist
    System.out.println(headerMap.get("addresslist"));  
   
}

@CookieValue

@CookieValue 注解用于将 Cookie 中的值绑定到方法参数上。它可以用于获取特定 Cookie 的值,或者获取所有 Cookie 的值。该注解可以应用于方法参数、方法、类级别的处理器方法上。

@CookieValue 注解一共有三个属性:valuerequireddefaultValue,用法同@RequestParam

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RequestMapping("/testHeader")  
public String testHeader(@RequestHeader(value = "host") String host, @CookieValue("Idea-c928d5a5") String cookie){  
    System.out.println(host);  
    System.out.println(cookie);  
    return "target";  
}

// 获取特定cookie
@GetMapping("/example")
public void exampleMethod(@CookieValue("sessionId") String sessionId) {
    // 处理方法逻辑
}
// 获取所有cookie的值
@GetMapping("/example")
public void exampleMethod(@CookieValue Map<String, String> cookies) {
    // 处理方法逻辑
    for (Map.Entry<String, String> entry : cookies.entrySet()) {
        String cookieName = entry.getKey();
        String cookieValue = entry.getValue();
        // 处理每个Cookie的逻辑
    }
}

通过 POJO 获取请求参数

可以在控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值

1
2
3
4
5
6
@GetMapping  
@RequestMapping(value = {"/user4"})  
public String testUserPOJO(User user) {  
    System.out.println(user);  
    return "success";  
}

测试链接:http://localhost:8080/SpringMVCDemo_war_exploded/user4?username=hacket&password=123&nickname=%E5%A4%A7%E5%9C%A3

相同参数名多个

数组参数

 请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型形参即可接收参数。

1
2
3
4
5
6
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
    System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
    return "{'module':'array param'}";
}
集合保存普通参数

请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam 绑定参数关系

1
2
3
4
5
6
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
    System.out.println("集合参数传递 likes ==> "+ likes);
    return "{'module':'list param'}";
}

若使用集合保存普通参数时,SpringMVC 框架会将此集合当成 PoJo 对象进行传递。而集合又没有构造方法,因此会报错:

此时,加上 @RequestParam 注解后,SpringMVC 框架会直接将请求中涉及到的参数放入集合中

嵌套 POJO 参数

请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套 POJO 属性参数

1
2
3
4
5
6
@RequestMapping("/pojoContainPojoParam")
@ResponseBody
public String pojoContainPojoParam(User user){
    System.out.println("pojo嵌套pojo参数传递 user ==> "+user);
    return "{'module':'pojo contain pojo param'}";
}

传递 JSON

基本 @RequestBody
  • 导入 json 库
1
2
3
4
5
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.0</version>
</dependency>
  • 设置发送 json 数据(请求 body 中添加 json 数据)
  • 开启自动转换 json 数据的支持
1
2
3
4
5
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc // 在SpringMVC中需要开启Json数据自动类型转换,与SpringBoot不同
public class SpringMvcConfig {
}

@EnableWebMvc 注解功能强大,该注解整合了多个功能,此处仅使用其中一部分功能,即json 数据进行自动类型转换

  • 设置接收 json 数据
1
2
3
4
5
6
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
    System.out.println("list common(json)参数传递 list ==> "+likes);
    return "{'module':'list common for json param'}";
}

@RequestBody: 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次

传递 Json 对象

json 数据与形参对象属性名相同,定义 POJO 类型形参即可接收参数。处理 JSON 数据时使用。

1
2
3
4
5
6
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
    System.out.println("pojo(json)参数传递 user ==> "+user);
    return "{'module':'pojo for json param'}";
}
传递 Json 数组

json 数组数据与集合泛型属性名相同,定义 List 类型形参即可接收参数。

1
2
3
4
5
6
@RequestMapping("/listPojoParamForJson")
@ResponseBody
public String listPojoParamForJson(@RequestBody List<User> list){
    System.out.println("list pojo(json)参数传递 list ==> "+list);
    return "{'module':'list pojo for json param'}";
}
日期类型
  • 日期类型数据基于系统不同格式也不尽相同
    • 2088-08-18
    • 2088/08/18
    • 08/18/2088
  • 接收形参时,根据不同的日期格式设置不同的接收方式
1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
	@DateTimeFormat(pattern = "yyyy-MM-dd") Date date1,
	@DateTimeFormat(pattern = "yyyy/MM/dd HH:mm:ss")Date date2) {
    System.out.println("参数传递 date ==> "+date);
    System.out.println("参数传递 date(yyyy-MM-dd) ==> "+date1);
    System.out.println("参数传递 date(yyyy/MM/dd HH:mm:ss) ==> "+date2);
    return "{'module':'data param'}";
}
// 测试的请求:http://localhost/dataParam?date=2088/08/08&date1=2088-08-18&date2=2088/08/28 8:08:08

扩展:通过注解来进行参数的传递,内部的工作其实是通过 Converter 接口来进行实现

1
2
3
4
5
6
7
public interface Converter<S, T> {
    @Nullable
    T convert(S var1);
}
// 请求参数年龄数据(String→Integer)
// json数据转对象(json → POJO)
// 日期格式转换(String → Date)
自定义参数绑定实现日期类型绑定
  • @DateTimeFormat 注解
  • ConversionServiceFactoryBean

解决获取请求参数的乱码问题

解决获取请求参数的乱码问题,可以使用 SpringMVC 提供的编码过滤器 CharacterEncodingFilter,但是必须在 web.xml 中进行注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?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">
    <!--设置编码过滤器-->
    <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>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--注册前端控制器-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

注:SpringMVC 中处理编码的过滤器一定要配置到其他过滤器之前,否则无效

响应

响应则是服务器向客户端返回的消息,用于提供请求所需的数据或执行操作的结果

响应分类:

  1. 响应页面
  2. 响应数据
    • 文本数据
    • json 数据

响应页面(了解)

1
2
3
4
@RequestMapping("/toPage")
public String toPage(){
    return "page.jsp";
}

响应文本数据(了解)

1
2
3
4
5
@RequestMapping("/toText")
@ResponseBody
public String toText(){
    return "response text";
}

@ResponseBody SpringMVC 控制器方法定义上方,设置当前控制器返回值作为响应体

响应 Json 数据

对象转 Json
1
2
3
4
5
6
7
8
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO(){
    User user = new User();
    user.setName("赵云");
    user.setAge(41);
    return user;
}
对象集合转 Json 数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
    User user1 = new User();
    user1.setName("赵云");
    user1.setAge(41);
    User user2 = new User();
    user2.setName("master 赵云");
    user2.setAge(40);
    List<User> userList = new ArrayList<User>();
    userList.add(user1);
    userList.add(user2);
    return userList;
}

HttpMessageConverter

类型转换器 HttpMessageConverter 接口:

1
2
3
4
5
6
7
8
9
public interface HttpMessageConverter<T> {
   boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
   boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
   List<MediaType> getSupportedMediaTypes();
   T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
         throws IOException, HttpMessageNotReadableException;
   void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
         throws IOException, HttpMessageNotWritableException;
}

当使用 @ResponseBody 注解,进行内容的响应时,并不是通过 Converter 接口来进行转换,而是通过全新的接口 HttpMessageConverter 接口来进行转换。

域对象共享数据

使用 ServletAPI 向 request 域对象共享数据

1
2
3
4
5
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
    request.setAttribute("testScope", "hello,servletAPI");
    return "success";
}

使用 ModelAndView 向 request 域对象共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
    /**
     * ModelAndView有Model和View的功能
     * Model主要用于向请求域共享数据
     * View主要用于设置视图,实现页面跳转
     */
    ModelAndView mav = new ModelAndView();
    //向请求域共享数据
    mav.addObject("testScope", "hello,ModelAndView");
    //设置视图,实现页面跳转
    mav.setViewName("success");
    return mav;
}

使用 Model 向 request 域对象共享数据

1
2
3
4
5
@RequestMapping("/testModel")
public String testModel(Model model){
    model.addAttribute("testScope", "hello,Model");
    return "success";
}

使用 map 向 request 域对象共享数据

1
2
3
4
5
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
    map.put("testScope", "hello,Map");
    return "success";
}

使用 ModelMap 向 request 域对象共享数据

1
2
3
4
5
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
    modelMap.addAttribute("testScope", "hello,ModelMap");
    return "success";
}
Model、ModelMap、Map 的关系

ModelModelMapMap 类型的参数其实本质上都是 BindingAwareModelMap 类型的

1
2
3
4
public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}

向 session 域共享数据

1
2
3
4
5
@RequestMapping("/testSession")
public String testSession(HttpSession session){
    session.setAttribute("testSessionScope", "hello,session");
    return "success";
}

向 application 域共享数据

1
2
3
4
5
6
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
	ServletContext application = session.getServletContext();
    application.setAttribute("testApplicationScope", "hello,application");
    return "success";
}

SpringMVC 的视图

SpringMVC 中的视图是 View 接口,视图的作用渲染数据,将模型 Model 中的数据展示给用户

SpringMVC 视图的种类很多,默认有转发视图和重定向视图

当工程引入 jstl 的依赖,转发视图会自动转换为 JstlView

若使用的视图技术为 Thymeleaf,在 SpringMVC 的配置文件中配置了 Thymeleaf 的视图解析器,由此视图解析器解析之后所得到的是 ThymeleafView

ThymeleafView

当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被 SpringMVC 配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转。

1
2
3
4
@RequestMapping("/testHello")
public String testHello(){
    return "hello";
}

转发视图

SpringMVC 中默认的转发视图是 InternalResourceView

SpringMVC 中创建转发视图的情况:

当控制器方法中所设置的视图名称以 “forward:” 为前缀时,创建 InternalResourceView 视图,此时的视图名称不会被 SpringMVC 配置文件中所配置的视图解析器解析,而是会将前缀 “forward:” 去掉,剩余部分作为最终路径通过转发的方式实现跳转

例如 “forward:/“,”forward:/employee

1
2
3
4
@RequestMapping("/testForward")
public String testForward(){
    return "forward:/testHello";
}

重定向视图

SpringMVC 中默认的重定向视图是 RedirectView

当控制器方法中所设置的视图名称以 “redirect:” 为前缀时,创建 RedirectView 视图,此时的视图名称不会被 SpringMVC 配置文件中所配置的视图解析器解析,而是会将前缀 “redirect:” 去掉,剩余部分作为最终路径通过重定向的方式实现跳转

例如 “redirect:/“,”redirect:/employee

1
2
3
4
@RequestMapping("/testRedirect")
public String testRedirect(){
    return "redirect:/testHello";
}

注:重定向视图在解析时,会先将 redirect: 前缀去掉,然后会判断剩余部分是否以/开头,若是则会自动拼接上下文路径

视图控制器 view-controller

当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用 view-controller 标签进行表示

1
2
3
4
5
<!--
	path:设置处理的请求地址
	view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>·

注:

当 SpringMVC 中设置任何一个 view-controller 时,其他控制器中的请求映射将全部失效,此时需要在 SpringMVC 的核心配置文件中设置开启 mvc 注解驱动的标签:

1
<mvc:annotation-driven />

拦截器

拦截器定义

​ 拦截器(Interceptor)是在 Web 开发中常用的一种技术,用于在请求处理的过程中对请求进行拦截和处理。拦截器可以在请求进入控制器处理之前或之后执行一些自定义的逻辑,例如日志记录、权限验证、请求参数预处理等。

拦截器作用

在指定的方法调用前后执行预先设定后的的代码以及阻止原始方法的执行。作用功能如下:

  • 权限验证:拦截器可以用于验证用户的权限,例如检查用户是否已登录、是否具有执行某个操作的权限等。如果权限验证失败,可以中断请求或进行相应的处理。
  • 日志记录:拦截器可以用于记录请求的日志信息,包括请求的 URL、请求参数、请求时间等,方便后续的统计分析、错误排查等工作。
  • 请求参数预处理:拦截器可以在请求进入控制器之前对请求参数进行预处理,例如对参数进行校验、格式化、加密等操作,以保证参数的正确性和安全性。
  • 异常处理:拦截器可以用于捕获控制器中抛出的异常,并进行统一的异常处理,例如返回统一的错误信息页面、记录异常日志等。
  • 请求响应处理:拦截器可以在请求处理完成后对响应进行处理,例如设置响应头信息、修改响应内容等。
  • 缓存控制:拦截器可以用于控制响应的缓存策略,例如设置响应的缓存时间、验证缓存的有效性等。

通常情况下,在实际开发中,拦截器通常用于 Token 权限。

拦截器和过滤器区别

拦截器(Interceptor)和过滤器(Filter)都是在 Web 开发中用于对请求进行处理的组件,但它们有以下区别:

  • 应用范围不同:拦截器是针对 Spring MVC 框架而言的,它是基于方法的拦截,只能拦截到控制器中的请求处理方法。而过滤器是 Servlet 规范定义的,它是基于 URL 模式的拦截,可以拦截到所有请求,包括静态资源和 Servlet 请求。
  • 执行顺序不同:拦截器是在处理器(Controller)之前或之后执行的,可以对请求进行预处理或后处理。过滤器是在请求进入 Servlet 容器之前或之后执行的,可以对请求进行过滤、修改或包装。
  • 功能不同:拦截器主要用于处理请求的业务逻辑,如权限验证、日志记录、异常处理等。过滤器主要用于对请求进行过滤和修改,如字符编码转换、请求包装、请求参数处理等。
  • 配置方式不同:拦截器的配置是在Spring MVC的配置文件中进行的,需要手动配置拦截器的顺序和路径匹配规则。过滤器的配置是在web.xml文件中进行的,通过配置 URL 模式来指定过滤器的拦截路径和顺序。
  • 归属不同:Filter 属于 Servlet 技术Interceptor 属于 SpringMVC 技术
  • 拦截内容不同:Filter 对所有访问进行增强,Interceptor 仅针对springMVC 的访问进行增强

​总体来说,拦截器更加灵活和精细化,适用于处理业务逻辑;过滤器更加通用,适用于对请求进行过滤和修改。在使用时,可以根据具体需求选择拦截器或过滤器来实现相应的功能。

拦截器实现

定义拦截器 Bean

  • 声明拦截器的 Bean,并实现 HandlerInterceptor 接口
  • 扫描加载 bean,记得添加 @Component 注解
  • ​通常来说,拦截器用于表现层的处理,因此将拦截器放置在 Controller 包下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component //拦截器
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    //原始方法调用前执行的内容
    //返回值类型可以拦截控制的执行,true放行,false终止
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle..."+contentType);
        return true;
    }

    @Override
    //原始方法调用后执行的内容
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    @Override
    //原始方法调用完成后执行的内容
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
    }
}

配置类

方式一:定义配置类,继承 WebMvcConfigurationSupport,实现 addInterceptor 方法(注意:扫描加载配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    /**
     * 配置静态资源映射
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    }

    /**
     * 配置动态资源的拦截器
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    }
}
  • 通过以上配置,实现了对静态资源的访问和对指定路径的拦截器配置
  • 添加拦截器并设定拦截的访问路径,路径可以通过可变参数设置多个

方式二:实现标准接口 WebMvcConfigurer 简化开发(注意: 侵入式较强)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置多拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    }
}
  • 通过实现 WebMvcConfigurer 接口,也可以达到继承 WebMvcConfigurationSupport 配置类实现资源拦截的效果
  • 通过实现 WebMvcConfigurer 接口来实现拦截器的方式,入侵性较强

拦截器参数

前置处理

前置处理就是,在请求拦截前做的一些处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    //原始方法调用前执行的内容
    //返回值类型可以拦截控制的执行,true放行,false终止
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String contentType = request.getHeader("Content-Type");
        HandlerMethod hm = (HandlerMethod)handler;
        System.out.println("preHandle..."+contentType);
        return true;
    }
}
  • request: 请求对象: request 中可以获得请求头中的信息,此时可以取出便于操作
  • response:响应对象
  • handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的 Method 对象进行了再包装
  • 返回值为 false,被拦截的处理器将不执行

后置处理

1
2
3
4
5
6
public void postHandle(HttpServletRequest request,
   HttpservletResponse response,
   object handler,
   ModelAndview modelAndView) throws Exception {
    System.out.println( "postHandle..." );
}
  • modelAndView: 如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整

拦截器工作流程

基本流程

  • 当没有拦截器的时候,请求通过表现层不会做任何拦截
  • 当有拦截器时,请求通过表现成会对,原始方法调用前执行的内容、原始方法调用后执行的内容、原始方法调用完成后执行的内容进行拦截处理
  • 如果拦截处理后响应了 True 则表示放行,响应为 False 表示拦截该请求内容

拦截链

  • 若在第三个拦截器返回 False 时,则只会运行第一个和第二个拦截的后置处理器
  • 若在第二个拦截器返回 False 时,则只会运行第一个拦截的后置处理器
  • 若在第一个拦截器返回 False 时,则一个拦截的后置处理器都不会运行

RESTful

RESTful 简介

REST:Representational State Transfer,表现层资源状态转移。

  • 资源

资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个 URI 来标识。URI 既是资源的名称,也是资源在 Web 上的地址。对某个资源感兴趣的客户端应用,可以通过资源的 URI 与其进行交互。

  • 资源的表述

资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端 - 服务器端之间转移(交换)。资源的表述可以有多种格式,例如 HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求 - 响应方向的表述通常使用不同的格式。

  • 状态转移

状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。

RESTful 的实现

​REST(Representational State Transfer)是一种软件架构风格,用于设计网络应用程序的分布式系统。它主要关注资源的状态资源之间互动,通过使用统一的接口和 HTTP 协议的各种方法来实现。

优点:

  • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
  • 书写简化

设定 HTTP 请求动作(动词)

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping(value = "/users", method = RequestMethod.POST)
@ResponseBody
public String save(@RequestBody User user){
    System.out.println("user save..." + user);
    return "{'module':'user save'}";
}

@RequestMapping(value = "/users" ,method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody User user){
    System.out.println("user update..."+user);
    return "{'module':'user update'}";
}

设定请求参数(路径变量) @PathVariable

1
2
3
4
5
6
@RequestMapping(value = "/users/{id}" ,method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id){
    System.out.println("user delete..." + id);
    return "{'module':'user delete'}";
}

RESTful 快速开发

@RestController

基于 SpringMVC 的 RESTful 开发控制器类定义上方,设置当前控制器类为 RESTful 风格,等同于@Controller 与@ResponseBody两个注解组合功能。

1
2
3
@RestController
public class BookController {
}
@GetMapping @PostMapping @PutMapping @DeleteMapping

基于 SpringMVC 的 RESTful 开发控制器方法定义上方;设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如@GetMapping 对应 GET 请求

1
2
3
4
5
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
    System.out.println("book getById..."+id);
    return "{'module':'book getById'}";
}

HiddenHttpMethodFilter

由于浏览器只支持发送 get 和 post 方式的请求,那么该如何发送 put 和 delete 请求呢?

SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求

HiddenHttpMethodFilter 处理 put 和 delete 请求的条件:

  • 当前请求的请求方式必须为 post
  • 当前请求必须传输请求参数 method

满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数 method 的值,因此请求参数 method 的值才是最终的请求方式。

在 web.xml 中注册HiddenHttpMethodFilter

1
2
3
4
5
6
7
8
<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.xml 中注册时,必须先注册 CharacterEncodingFilter,再注册 HiddenHttpMethodFilter

原因:

  • CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集的
  • request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作
  • HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:String paramValue = request.getParameter(this.methodParam);

遇到的问题

404/HTTP 状态 404:源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示

![300](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/202411190037586.png)

工程目录:

![300](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/202411190038489.png)

解决: File → Project Structure

访问:http://localhost:8080/SSMDemo_war_exploded/pages/books.html

【IDEA】HTTP状态404:源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示_idea源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示。-CSDN博客

本文由作者按照 CC BY 4.0 进行授权