01.Spring MVC入门

Spring MVC

MVC 概述

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

600

出现的弊端:

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

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

扩展阅读:Web开发模式

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

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

JavaBean 分为两类:

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 MVC 的架构

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

Hello Spring MVC

xml+ 注解混合

开发环境

项目目录

|600

新建 Maven 工程,配置 pom.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>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

<!-- 配置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 的初始化时间

<?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>

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

请求控制器

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

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

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

@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

<?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

纯注解(无 web.xml)

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

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

|300

依赖 pom.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>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 上下文。

/**
 * 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 的配置文件

// 创建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 的配置文件

/**
 * 代替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;
    }
}

测试

@Controller
public class UserController {

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

Spring MVC 基本使用

@RequestMapping 注解

@RequestMapping 的功能

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

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

@RequestMapping 的位置

@Controller
@RequestMapping("/test")
public class RequestMappingController {

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

@RequestMapping 属性

value 属性
@RequestMapping(
  value = {"/testRequestMapping", "/test"}
)
public String testRequestMapping(){
    return "success";
}

请求 /testRequestMapping/test 都可以:

<a th:href="@{/testRequestMapping}">测试@RequestMapping的value属性-->/testRequestMapping</a><br>
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
method 属性
<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

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

@GetMapping 示例:

/**
 * @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

params 属性(了解)
@RequestMapping(
	value = {"/testRequestMapping", "/test"} ,
	method = {RequestMethod.GET, RequestMethod.POST} ,
	params = {"username","password!=123456"}
)
public String testRequestMapping(){
    return "success";
}

测试

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

headers 属性(了解)

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

SpringMVC 支持 ant 风格的路径

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

/**
 * ?匹配任意一个字符
 * *匹配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 注解,将占位符所表示的数据赋值给控制器方法的形参。

@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 类型的参数表示封装了当前请求的请求报文的对象

@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 中就会将请求参数赋值给相应的形参

@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 注解一共有三个属性:

@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 区别

区别

应用

@RequestHeader

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

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

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

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

@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 获取请求参数

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

@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=大圣

相同参数名多个

数组参数

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

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

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

@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 属性参数

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

传递 JSON

基本 @RequestBody
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.0</version>
</dependency>
@Configuration
@ComponentScan("com.itheima.controller")
@EnableWebMvc // 在SpringMVC中需要开启Json数据自动类型转换,与SpringBoot不同
public class SpringMvcConfig {
}

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

@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 数据时使用。

@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 类型形参即可接收参数。

@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'}";
}
日期类型
@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 接口来进行实现

public interface Converter<S, T> {
    @Nullable
    T convert(S var1);
}
// 请求参数年龄数据(String→Integer)
// json数据转对象(json → POJO)
// 日期格式转换(String → Date)
自定义参数绑定实现日期类型绑定

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

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

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
	 version="4.0">
    <!--设置编码过滤器-->
    <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 数据

响应页面(了解)

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

响应文本数据(了解)

@RequestMapping("/toText")
@ResponseBody
public String toText(){
    return "response text";
}

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

响应 Json 数据

对象转 Json
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO(){
    User user = new User();
    user.setName("赵云");
    user.setAge(41);
    return user;
}
对象集合转 Json 数组
@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 接口:

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 域对象共享数据

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

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

@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 域对象共享数据

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

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

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

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

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

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

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

向 session 域共享数据

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

向 application 域共享数据

@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 配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转。

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

转发视图

SpringMVC 中默认的转发视图是 InternalResourceView

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

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

例如 "forward:/","forward:/employee"

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

重定向视图

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

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

例如 "redirect:/","redirect:/employee"

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

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

视图控制器 view-controller

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

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

注:

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

<mvc:annotation-driven />

拦截器

拦截器定义

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

拦截器作用

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

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

拦截器和过滤器区别

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

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

拦截器实现

定义拦截器 Bean

@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 方法(注意:扫描加载配置)

@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 简化开发(注意: 侵入式较强)

@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/*");
    }
}

拦截器参数

前置处理

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

@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;
    }
}

后置处理

public void postHandle(HttpServletRequest request,
   HttpservletResponse response,
   object handler,
   ModelAndview modelAndView) throws Exception {
    System.out.println( "postHandle..." );
}

拦截器工作流程

基本流程

拦截链

RESTful

RESTful 简介

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

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

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

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

RESTful 的实现

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

优点:

设定 HTTP 请求动作(动词)

@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

@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两个注解组合功能。

@RestController
public class BookController {
}
@GetMapping @PostMapping @PutMapping @DeleteMapping

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

@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 请求的条件:

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

在 web.xml 中注册HiddenHttpMethodFilter

<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

原因:

遇到的问题

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

|300

工程目录:

|300

解决: File → Project Structure

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

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