首页 > 其他分享 >SpringMVC

SpringMVC

时间:2022-11-16 13:47:54浏览次数:70  
标签:return String SpringMVC 视图 public 请求

README

一、目录

0、简介

1、@RequestMapping 注解

2、获取请求参数

3、域对象共享数据

4、视图

5、RESTful

6、HttpMessageConverter

7、拦截器和异常处理

8、完全注解开发

9、执行流程

二、配置文件

1、pom.xml

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.16</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.15.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.2</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

2、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>
    <!--配置 org.springframework.web.filter.HiddenHttpMethodFilter: 可以把 POST 请求转为 DELETE 或 POST 请求 -->
    <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>

    <!--配置 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: 表示从类路径查找配置文件,java 工程默认 src 下, Maven 工程默认 src/main/resources 下-->
            <param-value>classpath:springMVC.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>

3、springMVC.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context 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.vectorx.springmvc"></context:component-scan>

    <!--配置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>
    <!--InternalResourceViewResolver-->
    <!--<bean id="InternalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">-->
    <!--    <property name="prefix" value="/WEB-INF/templates/"/>-->
    <!--    <property name="suffix" value=".jsp"/>-->
    <!--</bean>-->

    <!--视图控制器:当前请求映射对应的控制器方法中没有其他请求过程的处理,只需设置一个视图名称时,就可以使用`view-controller`-->
    <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
    <!--处理静态资源-->
    <mvc:default-servlet-handler/>
    <!--开启 MVC 的注解驱动-->
    <mvc:annotation-driven/>
    <!--配置文件上传解析器,将上传文件自动封装为MutilpartFile对象-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

    <!--自定义拦截器-->
    <!--方式一-->
    <!--<mvc:interceptors>-->
    <!--    <bean id="myInterceptor" class="com.vectorx.springmvc.s00_helloworld.interceptor.MyInterceptor"></bean>-->
    <!--</mvc:interceptors>-->
    <!--方式二-->
    <!--<mvc:interceptors>-->
    <!--    <ref bean="myInterceptor"></ref>-->
    <!--</mvc:interceptors>-->
    <!--<mvc:interceptors>-->
    <!--    <ref bean="myInterceptor"></ref>-->
    <!--    <ref bean="myInterceptor2"></ref>-->
    <!--</mvc:interceptors>-->
    <!--方式三-->
    <!--<mvc:interceptors>-->
    <!--    <mvc:interceptor>-->
    <!--        <mvc:mapping path="/**"/>-->
    <!--        <mvc:exclude-mapping path="/"/>-->
    <!--        <ref bean="myInterceptor"></ref>-->
    <!--    </mvc:interceptor>-->
    <!--</mvc:interceptors>-->

    <!--异常处理解析器-->
    <!--<bean id="simpleMappingExceptionResolver"-->
    <!--      class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">-->
    <!--    <property name="exceptionMappings">-->
    <!--        <props>-->
    <!--            <!–-->
    <!--                properties的键表示处理器方法执行过程中出现的异常-->
    <!--                properties的值表示若出现指定异常,设置一个新的视图名称,跳转到指定页面-->
    <!--            –>-->
    <!--            <prop key="java.lang.ArithmeticException">error</prop>-->
    <!--        </props>-->
    <!--    </property>-->
    <!--    <!–设置一个属性名,将出现的异常信息共享在请求域中–>-->
    <!--    <property name="exceptionAttribute" value="ex"></property>-->
    <!--</bean>-->
</beans>

三、完全注解开发

WebInit——web.xml

/**
 * web工程的初始化类,代替web.xml
 */
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * 指定Spring配置类
     *
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

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

    /**
     * 指定SpringMVC的映射规则,即url-pattern
     *
     * @return
     */
    @Override
    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};
    }
}

WebConfig——springMVC.xml

/**
 * SpringMVC的配置类清单
 * 1、扫描组件
 * 2、视图解析器
 * 3、view-controller
 * 4、default-servlet-handler
 * 5、MVC注解驱动
 * 6、文件上传解析器
 * 7、拦截器
 * 8、异常处理解析器
 */
@Configuration
// ========== 1、扫描组件 ==========
@ComponentScan("com.vectorx.springmvc.controller")
// ========== 5、MVC注解驱动 ==========
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    // ========== 2、视图解析器 ==========
    /**
     * 生成模板解析器
     *
     * @return
     */
    @Bean
    public ITemplateResolver templateResolver() {
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setCharacterEncoding("UTF-8");
        return templateResolver;
    }

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

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

    // ========== 3、view-controller ==========
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }

    // ========== 4、default-servlet-handler ==========
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    // ========== 6、文件上传解析器 ==========
    @Bean
    public MultipartResolver multipartResolver() {
        MultipartResolver multipartResolver = new CommonsMultipartResolver();
        return multipartResolver;
    }

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

    // ========== 8、异常处理解析器 ==========
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        properties.setProperty(ArithmeticException.class.getCanonicalName(), "error");
        exceptionResolver.setExceptionMappings(properties);
        exceptionResolver.setExceptionAttribute("ex");
        resolvers.add(exceptionResolver);
    }
}

01、SpringMVC简介

1、课程介绍

2、什么是 MVC?

MVC 是一种软件架构思想,将软件分为模型、视图、控制器三部分

  • M(Model,模型层):处理数据的 JavaBean 类

  • 实体类 Bean:存储业务数据

  • 业务处理 Bean:Service 或 Dao,处理业务逻辑和数据访问

  • V(View,视图层):展示数据的 HTML 页面,与用户进行交互

  • C(Controller,控制层):接受请求和响应的 Servlet

MVC 工作流程

  1. 用户通过视图层发送请求到服务器,在服务器中请求被Controller 接收

  2. Controller 调用相应的 Model 层处理请求

  3. Model 层处理完毕将结果返回到 Controller

  4. Controller 再根据请求处理的结果找到相应的 View 视图

  5. View 视图渲染数据后最终响应给浏览器

3、什么是 SpringMVC?

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

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

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

4、SpringMVC 的特点

  • Spring 家族原生产品:与 IOC 容器等基础设施无缝对接

  • 基于原生的 Servlet:通过了功能强大的前端控制器 DispatcherServlet,对请求和响应进行统一处理

  • 全面解决方案:表述层各细分领域需要解决的问题全方位覆盖

  • 代码清新简洁:大幅度提升开发效率

  • 即插即用:内部组件化程度高,组件可插拨,想要什么功能配置相应组件即可

  • 性能卓著:尤其适合现代大型、超大型互联网项目要求

5、HelloWorld

5.1、开发环境

  • IDE:idea 2021.1

  • 构建工具:maven-3.8.3

  • 服务器:tomcat7

  • Spring版本:5.3.16

5.2、创建 Maven 工程

1)新建工程,默认 NEXT

2)填写工程名称、保存为止和 GAV 坐标,点击 FINISH

3)pom.xml中添加并导入依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.16</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
        <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring5 -->
    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring5</artifactId>
        <version>3.0.15.RELEASE</version>
    </dependency>
</dependencies>

根据依赖的传递性,相关的依赖也会被导入

4)项目工程结构中添加 web 模块,注意web.xml的路径要放在src\main\webapp

完成后的目录结构

5.3、配置 web.xml

为什么要配置web.xml?注册 SpringMVC 的前端控制器 DispatcherServlet

1)默认配置方式

此配置作用下,SpringMVC 的配置文件默认位于 WEB-INF 下,默认名称为<servlet-name>-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>

默认配置方式对位置和名称都是默认的,这样并不好!Maven 工程配置文件应该统一放置在resources下,应该如何来实现呢?来看下面的“扩展配置方式”

2)扩展配置方式

  • 通过init-param标签设置 SpringMVC 配置文件的位置和名称

  • 通过load-on-startup标签设置 SpringMVC 前端控制器 DispatcherServlet 的初始化时间

<!--配置 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: 表示从类路径查找配置文件,java 工程默认src下,maven 工程默认 src/main/resources 下-->
        <param-value>classpath:springMVC.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>

注:<url-pattern>标签中使用//*的区别:

/所匹配的请求可以是/login.html.js.css方式的请求路径,但是/不能匹配.jsp请求路径的请求

因此就可以避免在访问.jsp页面时,该请求被 DispatcherServlet 处理,从而找不到相应的页面的情况

/*则能够匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/*的写法

5.4、创建请求控制器

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

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

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

@Controller
public class HelloController {
}

5.5、创建 SpringMVC 配置文件

<!--自动扫描包-->
<context:component-scan base-package="com.vectorx.springmvc"></context:component-scan>
<!--配置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>

5.6、测试

1)访问首页

创建首页index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>

在请求控制器中创建处理请求的方法

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

访问首页

2)访问指定页面

在主页index.html创建超链接

<a href="/target">访问指定页面target.html</a>

但是这种写法是不行的,可以看到,当鼠标悬浮在超链接上时,左下角的跳转路径提示信息从 8080 下访问的

这是因为我们是以/开头的,它分为浏览器解析和服务器解析两种方式,而超链接中的绝对路径就是由浏览器解析的,而不是从上下文路径访问

虽然我们可以通过添加上下文的方式实现,因为上下文路径可以改,所以这种方式肯定是杜绝的

<a href="/SpringMVC/target">访问指定页面target.html</a>

那应该如何处理呢?这里就可以使用thymeleaf来动态获取上下文路径

  • 首先需要在<html>标签中引入thymeleaf的命名空间xmlns:th="http://www.thymeleaf.org"

  • 然后使用th:前缀修饰标签属性,这里使用th:href来修饰<a>标签的<href>属性

  • 最后th:href中的属性值中包裹一层@{},这里值为@{/target.html}

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/target}">访问指定页面target.html</a>
</body>
</html>

同时,后台请求控制器也要加上对target请求进行处理的控制器方法

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

访问指定页面

跳转成功

6、SpringMVC 请求处理底层原理

  • 浏览器发送请求,若请求地址符合前端控制器的url-pattern,该请求就会被前端控制器 DispatcherServlet 处理

  • 前端控制器会读取 SpringMVC 的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中@RequestMapping注解的value属性值进行匹配。若匹配成功,该注解所标识的控制器方法就是处理请求的方法

  • 处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径,通过 Thymeleaf 对视图进行渲染,最终转发到视图所对应页面

附录:SpringMVC 工程创建整体流程

概览

  • 1)配置pom.xmlweb.xmlspringMVC.xml

  • 2)创建前台页面和后台请求控制器

详解

1)添加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>com.vectorx</groupId>
    <artifactId>springmvc-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.16</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.15.RELEASE</version>
        </dependency>
    </dependencies>
</project>

2)创建web.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>springMVC</servlet-name>
        <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>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

3)创建springMVC.xml配置文件

<context:component-scan base-package="com.vectorx.springmvc"></context:component-scan>
<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>

4)创建前台页面

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/target}">访问指定页面target.html</a>
</body>
</html>

target.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>HelloWorld</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>

5)创建Controller请求控制器

@Controller
public class HelloController {
    @RequestMapping("/")
    public String index() {
        //返回视图名称
        return "index";
    }
    @RequestMapping("/target")
    public String toTarget() {
        return "target";
    }
}

总结

最后奉上本节导图,内容仅供参考

02、@RequestMapping 注解

1、功能

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

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

控制器中有多个方法对应同一个请求的情况

这是一种特殊情况,我们定义至少两个控制类,其中定义的控制器方法上@ReqeustMapping指定同一请求路径

@Controller
public class HelloController {
    @RequestMapping("/")
    public String index() {
        return "index";
    }
}
@Controller
public class RequestMappingController {
    @RequestMapping("/")
    public String index() {
        return "target";
    }
}

如果存在两个或多个控制器,其控制器方法的@RequestMapping一致,即多个控制器方法试图都想要处理同一个请求时,这时启动 Tomcat 时会抛出BeanCreationException异常,并指明There is already 'helloController' bean method即 helloController 中存在处理同意请求的控制器方法

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'requestMappingController' method 
com.vectorx.springmvc.s00_helloworld.RequestMappingController#index()
to { [/]}: There is already 'helloController' bean method
com.vectorx.springmvc.s00_helloworld.HelloController#index() mapped.

但是这显然是不合理的,当一个系统方法很多,很难完全避免两个或多个同名方法的存在,那该怎么办呢?

2、位置

查看@RequestMapping注解的源码,可以清楚地看到@Target中有TYPEMETHOD两个属性值,表示其可以作用在类或方法上

也就是说@RequestMapping不仅可以在控制器方法上进行使用,也可以在控制器类上进行使用。那两种方式有什么区别呢?

  • @RequestMapping标识一个类:设置映射请求的请求路径的初始信息

  • @RequestMapping标识一个方法:设置映射请求的请求路径的具体信息

@Controller
@RequestMapping("/requestMappingController")
public class RequestMappingController {
    @RequestMapping("/success")
    public String success() {
        return "success";
    }
}

前台创建超链接,超链接跳转路径 = 控制器上@RequestMapping映射请求的初始路径 + 控制器方法上@RequestMapping映射请求的具体路径,即/requestMappingController/success,再将其使用Tymeleaf的@{}语法包裹起来,这样Tymeleaf会为我们自动加上上下文路径

<a th:href="@{/requestMappingController/success}">访问指定页面success.html</a>

测试结果

这样就可以为不同的控制器方法,在设置映射请求的请求路径时,指定不同的初始信息,从而避免控制器中有多个方法对应同一个请求的情况,这也就解决了之前的问题

3、value 属性

@RequestMapping注解的value属性有哪些用途呢?

  • 请求映射:通过请求的请求地址匹配请求映射

  • 匹配多个请求:是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的

  • 必须设置:至少通过一个请求地址匹配请求映射

匹配多个请求

查看@RequestMapping注解的源码,会发现其value属性返回值为 String 类型的数组,这也说明了之所以@RequestMapping注解的value属性可以匹配多个请求的原因。通过为value属性指定多个值的方式,就可以对个多个请求建立请求映射

在控制器方法上的@RequestMapping中新增一个/test请求映射

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

前台创建一个/test的请求路径的超链接,以便进行测试

<a th:href="@{/requestMappingController/test}">>测试RequestMapping注解的value属性-->/test</a>

测试结果

这样,同一个控制器方法就可以实现对多个请求进行统一处理

4、method 属性

@RequestMapping注解的method属性有哪些用途呢?

  • 通过请求的请求方式(GETPOST)匹配请求映射

1、常用的请求方式有 4 种:GET、POST、PUT、DELETE

  • GET 用于查询数据

  • POST 用于添加数据

  • PUT 用于更新数据

  • DELETE 用户删除数据

但是很多情况下,习惯上会让 POST 承担更多的职责,即通过 POST 进行增、删、改的操作,可以说是“一个人揽了三个人的活”

2、还有 4 种不常用的请求:HEAD、PATCH、OPTIONS、TRACE

  • HEAD 获取响应的报头,检查超链接的有效性、网页是否被修改,多用于自动搜索机器人获取网页的标志信息,获取 rss 种子信息,或者传递安全认证信息等

  • PATCH 用于更新数据

  • OPTIONS 用于测试服务器,解决同源策略和跨域请求问题

  • TRACE 用于测试或诊断

有的资料还会介绍 CONNECT 请求,好像用于 HTTP 代理的,很少人听过,当然用的更少(我也是刚知道,有懂的求科普)

  • 是一个RequestMethod类型的数组,表示该请求映射能够匹配多种请求方式的请求

源码为证:RequestMethod[] method() default {};

同时注意到method属性默认值为空数组,是否说明控制器方法不添加method属性时,不同的请求方法都能够匹配呢?

1)无 method 时匹配哪些请求方式?

通过测试验证猜想

<a th:href="@{/requestMappingController/test}">测试RequestMapping注解的method属性-->GET</a>
<br/>
<form th:action="@{/requestMappingController/test}" method="post">
    <input type="submit" value="测试RequestMapping注解的method属性-->POST">
</form>

测试结果:事实证明,控制器方法不添加method属性时,可以接收GETPOST的请求,那么应该是默认不对请求方式限制了

本着严谨的态度,再测试下是否在不添加method属性时,默认也支持PUTDELETE请求

不过,PUTDELETE请求比较特殊,需要使用到隐藏域,且method固定为POST

<form th:action="@{/requestMappingController/test}" method="post">
    <input type="hidden" name="_method" value="put"/>
    <input type="submit" value="测试RequestMapping注解的method属性-->PUT">
</form>
<br/>
<form th:action="@{/requestMappingController/test}" method="post">
    <input type="hidden" name="_method" value="delete"/>
    <input type="submit" value="测试RequestMapping注解的method属性-->DELETE">
</form>

同时在web.xml中需要添加隐藏域请求方式的过滤器配置

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

测试结果也是成功的

存疑点:本来也想测试下HEADPTACHOPTIONSTRACE这几种不常用的请求方式的,但是发现 form 表单好像不支持这些请求方式。即使使用隐藏域,也会变成GET的请求方式。很疑惑这些请求方式要怎么模拟,有懂的求科普。这里记录下,留个印象,以待后续考古o(╯□╰)o

2)不满足 method 会怎样?

值得注意的是

若当前请求的请求地址满足请求映射的value属性,但是请求方式不满足method属性,则浏览器报错

HTTP Status 405 - Request method 'POST' not supported

代码验证

@RequestMapping(value = {"/success", "/test"}, method = RequestMethod.GET)
public String success() {
    return "success";
}

验证结果:确实报了 405。同理可以将method属性值中GET改成POSTPUTDELETE对应进行验证即可,这里就不一一验证了

虽然无关紧要,但就是好奇为嘛我的是中文提示(灬°ω°灬)

发现这个版本 tomcat 的 lib 包中有 i18n 相关 jar 包,原来是做了国际化,“不一定有用”的知识又增加了~

3)派生注解

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

  • 处理GET请求的映射->@GetMapping

  • 处理POST请求的映射->@PostMapping

  • 处理PUT请求的映射->@PutMapping

  • 处理DELETE请求的映射->@DeleteMapping

这里对于派生注解很容易理解,用数学上的等价解释就是

通过代码进行测试

后台代码

@Controller
@RequestMapping("/requestMappingController")
public class RequestMappingController {
    @GetMapping("/success")
    public String successGet() {
        return "successget";
    }
    @PostMapping("/success")
    public String successPost() {
        return "successpost";
    }
    @PutMapping("/success")
    public String successPut() {
        return "successput";
    }
    @DeleteMapping("/success")
    public String successDelete() {
        return "successdelete";
    }
}

前台代码

<a th:href="@{/requestMappingController/success}">测试GetMapping</a>
<br/><br/>
<form th:action="@{/requestMappingController/success}" method="post">
    <input type="submit" value="测试PostMapping">
</form>
<br/>
<form th:action="@{/requestMappingController/success}" method="post">
    <input type="hidden" name="_method" value="put"/>
    <input type="submit" value="测试PutMapping">
</form>
<br/>
<form th:action="@{/requestMappingController/success}" method="post">
    <input type="hidden" name="_method" value="delete"/>
    <input type="submit" value="测试DeleteMapping">
</form>

别忘了,新增四个测试页面successget.htmlsuccesspost.htmlsuccessput.htmlsuccessdelete.html,在这个四个不同页面中标注不同的内容以示区分

验证结果:可以看到,GET/POST/PUT/DELETE等请求方式,均能够被正常接收和处理

4)form 表单支持哪些请求方式?

  • 常用的请求方式有GETPOSTPUTDELETE,但是目前浏览器只支持GETPOST(OS:刚才还有点疑惑的,这里好像“水落石出了”)

  • 若在 form 表单提交时,为method设置了其他请求方式的字符串(PUTDELETE),则按照默认的GET请求方式处理

  • 若要发送PUTDELETE请求,则需要通过 Spring 提供的过滤器HiddenHttpMethodFilter,在 restful 部分会讲到(OS:我上面刚自己研究了下,没想到老师这里会讲^_^

如果去除过滤器HiddenHttpMethodFilter的配置,同时注释掉隐藏域的代码,并将methodpost值改成putdelete

<form th:action="@{/requestMappingController/success}" method="put">
    <!--    <input type="hidden" name="_method" value="put"/>-->
    <input type="submit" value="测试PutMapping">
</form>
<br/>
<form th:action="@{/requestMappingController/success}" method="delete">
    <!--    <input type="hidden" name="_method" value="delete"/>-->
    <input type="submit" value="测试DeleteMapping">
</form>

按照上述的说法,会按照默认的GET请求方式处理,这里进行验证

可以发现,本应走PUTDELETE方式的请求,都被GET请求方式的控制器方法处理了。如果这里控制器中连相应GET请求方式都没有定义的话,肯定是报 405 了。这里注释掉@GetMapping的请求方法

//@GetMapping("/success")
//public String successGet() {
//    return "successget";
//}

验证结果:显而易见

5、params 属性

@RequestMapping注解的params属性通过请求的请求参数匹配请求映射

它是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系

  • param:要求请求映射所匹配的请求必须携带param请求参数

  • !param:要求请求映射所匹配的请求必须不能携带param请求参数

  • param=value:要求请求映射所匹配的请求必须携带param请求参数且param=value

  • param!=value:要求请求映射所匹配的请求必须携带param请求参数但是param!=value

若当前请求满足@RequestMapping注解的valuemethod属性,但是不满足params属性,此时页面显示400错误,即请求不匹配

1)param

这里指定params的值指定为username,这就要求请求中必须携带username的请求参数

@RequestMapping(
    value = {"/testParams"},
    params = {"username"}
)
public String testParams() {
    return "success";
}

前台测试代码:分别不加请求参数和加上请求参数,进行测试

<a th:href="@{/requestMappingController/testParams}">测试RequestMapping注解的params属性==>testParams</a><br/>
<a th:href="@{/requestMappingController/testParams?username=admin}">测试RequestMapping注解的params属性==>testParams?username=admin</a>

测试结果

可以发现,当配置了params属性并指定相应的请求参数时,请求中必须要携带相应的请求参数信息,否则前台就会报抛出400的错误信息,符合预期

HTTP Status 400:Parameter conditions "username" not met for actual request parameters

不过在Tymeleaf中使用问号的方式会有错误提示,虽然不影响功能,但不想要错误提示的话,最好通过(...,...)的方式进行包裹,多个参数间通过,隔开

<a th:href="@{/requestMappingController/testParams(username='admin', password=123456)}">测试RequestMapping注解的params属性==>testParams(username='admin', password=123456)</a><br/>

测试验证

可以发现,通过括号包裹的方式,Tymeleaf最终会帮我们将其解析成?username=admin&password=123456的格式

存疑点:实测发现,``testParams(username='admin', password=123456)改成testParams(username=admin, password=123456),即admin`不加单引号也是可以的,这与课堂上所讲的并不一致,此点存疑

2)!param

这里将params = {"username"}username前加上!即可,即params = {"!username"},这就要求请求中的请求参数中不能携带username请求参数

@RequestMapping(
    value = {"/testParams"},
    params = {"!username"}
)
public String testParams() {
    return "success";
}

测试结果

可以发现,没有携带username请求参数的请求变得能够正常访问,而携带了username请求参数的请求反而出现了400的异常信息,符合预期

HTTP Status 400:Parameter conditions "!username" not met for actual request parameters: username={admin}, password={123456}

3)param=value

这里params的值指定为username=admin的形式,即要求请求中不仅要携带username的请求参数,且值为admin

@RequestMapping(
    value = {"/testParams"},
    params = {"username=admin"}
)
public String testParams() {
    return "success";
}

测试结果

可以发现,不携带username请求参数的请求和携带username请求参数但不为admin的请求,均提示400的请求错误,符合预期

4)param!=value

这里将params的值指定为username!=admin,即要求请求中不仅要携带username的请求参数,且值不能为admin

@RequestMapping(
    value = {"/testParams"},
    params = {"username!=admin"}
)
public String testParams() {
    return "success";
}

测试结果

实际测试结果发现:不携带username请求参数的请求和携带username请求参数但值不为admin的请求,可以正常访问;而携带username请求参数但值为admin的请求,不能正常访问,不完全符合预期

存疑点:不携带username请求参数的请求能够正常访问,这一点不符合课程中讲解的内容,此点存疑

6、headers 属性

@RequestMapping注解的headers属性通过请求的请求头信息匹配请求映射

它是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系

  • header:要求请求映射所匹配的请求必须携带header请求头信息

  • header:要求请求映射所匹配的请求必须不能携带header请求头信息

  • header=value:要求请求映射所匹配的请求必须携带header请求头信息且header=value

  • header!=value:要求请求映射所匹配的请求必须携带header请求头信息且header!=value

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

测试代码

@RequestMapping(
    value = {"/testHeaders"},
    headers = {"Host=localhost:8081"}
)
public String testHeaders() {
    return "success";
}

测试结果

因为我本地tomcat启动端口是8080,所以是匹配不成功的,此时显示404错误,符合预期

再将端口号修改为8080

@RequestMapping(
    value = {"/testHeaders"},
    headers = {"Host=localhost:8080"}
)
public String testHeaders() {
    return "success";
}

测试结果

这一次,因为端口号一致,所以成功跳转,符合预期

7、Ant 风格路径

  • ?:表示任意的单个字符

  • *:表示任意的0个或多个字符

  • **:表示任意的一层或多层目录。注意:在使用**时,只能使用/**/xxx的方式

探子来报:**经实测,0 层目录也可以,这里严谨来说,应该是“表示任意层目录”

1)?

后台测试代码

//ant风格路径
@RequestMapping("/a?a/testAnt")
public String testAnt() {
    return "success";
}

前台测试代码

Ant风格路径——?:<br/>
<a th:href="@{/requestMappingController/testAnt}">测试ant风格路径_/a?a/testAnt==>/testAnt</a><br/>
<a th:href="@{/requestMappingController/a1a/testAnt}">测试ant风格路径_/a?a/testAnt==>/a1a/testAnt</a><br/>
<a th:href="@{/requestMappingController/aaa/testAnt}">测试ant风格路径_/a?a/testAnt==>/aaa/testAnt</a><br/>
<a th:href="@{/requestMappingController/aaaa/testAnt}">测试ant风格路径_/a?a/testAnt==>/aaaa/testAnt</a><br/>
<a th:href="@{/requestMappingController/a/a/testAnt}">测试ant风格路径_/a?a/testAnt==>/a/a/testAnt</a><br/>
<a th:href="@{/requestMappingController/a?a/testAnt}">测试ant风格路径_/a?a/testAnt==>/a?a/testAnt</a><br/>

测试结果

可以发现,/a?a/testAnt能够匹配的路径有

  • /a1a/testAnt

  • /aaa/testAnt

不能匹配的路径有

  • /testAnt

  • /aaaa/testAnt

  • /a/a/testAnt

  • /a?a/testAnt

即证明,?修饰的路径,有且必须有一个字符代替?的位置,即只能匹配单个字符,且不能为/?这两种特殊字符(因为/?在 url 路径中比较特殊,除此之外其他单个字符均可),符合预期

2)*

后台测试代码

//ant风格路径
@RequestMapping("/a*a/testAnt")
public String testAnt() {
    return "success";
}

前台测试代码

Ant风格路径——*:<br/>
<a th:href="@{/requestMappingController/aa/testAnt}">测试ant风格路径_/a*a/testAnt==>/aa/testAnt</a><br/>
<a th:href="@{/requestMappingController/a1a/testAnt}">测试ant风格路径_/a*a/testAnt==>/a1a/testAnt</a><br/>
<a th:href="@{/requestMappingController/aaaaa/testAnt}">测试ant风格路径_/a*a/testAnt==>/aaaaa/testAnt</a><br/>

测试结果

可以发现,/a*a/testAnt能够匹配的路径有

  • /aa/testAnt

  • /a1a/testAnt

  • /aaaaa/testAnt

即证明,*修饰的路径,允许 0 个或多个字符代替*的位置,符合预期

3)**

上面说到,在使用**时,只能使用/**/xxx的方式,这里对其进行验证

后台测试代码

//ant风格路径
@RequestMapping("/a**a/testAnt")
public String testAnt() {
    return "success";
}

前台测试代码

Ant风格路径——**:<br/>
<a th:href="@{/requestMappingController/aa/testAnt}">测试ant风格路径_/a**a/testAnt==>/aa/testAnt</a><br/>
<a th:href="@{/requestMappingController/a1a/testAnt}">测试ant风格路径_/a**a/testAnt==>/a1a/testAnt</a><br/>
<a th:href="@{/requestMappingController/a1a/testAnt}">测试ant风格路径_/a**a/testAnt==>/a11a/testAnt</a><br/>
<a th:href="@{/requestMappingController/a**a/testAnt}">测试ant风格路径_/a**a/testAnt==>/a**a/testAnt</a><br/>
<a th:href="@{/requestMappingController/aaaaa/testAnt}">测试ant风格路径_/a**a/testAnt==>/aaaaa/testAnt</a><br/>
<a th:href="@{/requestMappingController/a/a/testAnt}">测试ant风格路径_/a**a/testAnt==>/a/a/testAnt</a><br/>
<a th:href="@{/requestMappingController/a/d/e/a/testAnt}">测试ant风格路径_/a**a/testAnt==>/a/d/e/a/testAnt</a><br/>

测试结果

可以发现,/a**a/testAnt能够匹配的路径有

  • /aa/testAnt

  • /a1a/testAnt

  • /a11a/testAnt

  • /a**a/testAnt

  • /aaaaa/testAnt

不能匹配的路径有

  • /a/a/testAnt

  • /a/d/e/a/testAnt

不符合预期

存疑点:这里/a**a/多层路径不能匹配,而 0 个或多个字符能够匹配,这与课程中的“两颗星真的就是两颗星”不符,其匹配规则与/a*a/一致,即/a**a/ <==> /a*a/,两颗星与一颗星作用相同,此点存疑

上述只是对**的错误用法时的匹配规则,下面才是真正对**的正确用法验证,请看

后台测试代码

//ant风格路径
@RequestMapping("/**/testAnt")
public String testAnt() {
    return "success";
}

前台测试代码

Ant风格路径——**:<br/>
<a th:href="@{/requestMappingController/testAnt}">测试ant风格路径_/a**a/testAnt==>/testAnt</a><br/>
<a th:href="@{/requestMappingController/a/testAnt}">测试ant风格路径_/a**a/testAnt==>/a/testAnt</a><br/>
<a th:href="@{/requestMappingController/a/a/a/a/testAnt}">测试ant风格路径_/a**a/testAnt==>/a/a/a/a/testAnt</a><br/>

测试结果

可以发现,不管中间添加多少层路径都是能够匹配成功的,符合预期

8、路径中的占位符

  • 原始方式:/deleteUser?id=1

  • rest 方式:/deleteuser/11

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

无注解形参

  • 测试条件:①只使用{xxx}占位符而不使用@PathVariable注解;②形参名称与请求中的占位符名称同名

  • 测试目的:①请求能否匹配成功;②同名形参是否能够接收到请求路径中的占位符

后台测试代码

@RequestMapping("/testRest/{id}/{username}")
public String testRest(String id, String username) {
    System.out.println("id=" + id + ", username=" + username);
    return "success";
}

前台测试代码

路径中的占位符:<br/>
<a th:href="@{/requestMappingController/testRest/1/admin}">测试路径中的占位符==>/testRest/1/admin</a><br/>

测试结果

后台日志

id=null, username=null

可以发现,请求能够匹配成功,但是同名形参无法接收到占位符的值

带注解形参

查看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";
}

测试结果

后台日志

id=1, username=admin

可以发现,请求能够匹配成功,形参通过@PathVariable注解接收到了占位符的值

不设置占位符

<a th:href="@{/requestMappingController/testRest}">测试路径中的占位符==>/testRest</a><br/>

测试结果

可以看到,没有占位符时,直接显示了404错误,即表示路径中存在占位符的控制器方法不能匹配未设置占位符的请求

也就是说,路径中存在占位符的控制器方法,只能接收带了对应占位符的请求

占位符为空值或空格

<a th:href="@{/requestMappingController/testRest///}">测试路径中的占位符_空值==>/testRest///</a><br/>
<a th:href="@{/requestMappingController/testRest/ / /}">测试路径中的占位符_空格==>/testRest/ / /</a><br/>

测试结果

同时占位符为空格的情况是,后台打印了日志:id= , username=

可以看到,

  • 空值匹配失败,报了404错误

  • 空格匹配成功,路劲中对其解析成了对应的URL编码,即%20

小结

由以上情况测试结果可以得出

  • SpringMVC 支持路径中含有占位符的形式

  • 占位符只能通过@PathVariable注解获取(就目前所学知识而言)

  • 占位符可以匹配特殊字符——空格,但不能匹配空字符

总结

@RequestMapping注解

  • 功能:将请求和处理请求的控制器方法关联起来,建立映射关系

  • 位置:作用在类上(请求路径的初始信息);作用在方法上(请求路径的具体信息)

  • value属性:可以匹配多个请求路径,匹配失败报404

  • method属性:支持GETPOSTPUTDELETE,默认不限制,匹配失败报405

  • params属性:四种方式,param!paramparam==valueparam!=value,匹配失败报400

  • headers属性:四种方式,header!headerheader==valueheader!=value,匹配失败报400

  • 支持 Ant 风格路径:?(单个字符)、*(0 或多个字符)和**(0 或多层路径)

  • 支持路径中的占位符:{xxx}占位符、@PathVariable赋值形参

以下导图仅供参考

03、SpringMVC 获取请求参数

1、通过 Servlet API 获取

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

后台测试代码

@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request) {
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    System.out.println("username=" + username + ",password=" + password);
    return "success";
}

前台测试代码

<a th:href="@{/paramController/testServletAPI(username='admin',password='123456')}">通过 Servlet API 获取</a><br/>

测试结果

后台日志信息

username=admin,password=123456

Q:为何将HttpServletRequest request传入 testServletAPI() 方法中就可以使用?

A:SpringMVC 的 IOC 容器帮我们注入了HttpServletRequest请求对象,同时DispatherServlet为我们调用 testServletAPI() 方法时自动给request参数赋了值,因此可以在方法形参位置传入请求对象HttpServletRequest就可以直接使用其getParameter()方法获取参数

尽管上述 Servlet API 原生方式可以获取请求参数,但是这样做就没有必要了。因为 SpringMVC 中帮我们封装好了更加便捷的方式获取请求参数

2、通过控制器方法形参获取

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

注意:在@RequestMapping注解的“路径中的占位符”一节中,我们测试过了 restful 风格在不使用@PathVariable转而通过同名形参的方式,试图获取占位符的值,不过 SpringMVC 并没有很智能地给我们为同名参数赋值。但是这里 SpringMVC 允许我们使用同名形参为请求参数赋值。这是占位符请求参数的一个区别,需要注意区分!!!

2.1、同名形参

后台测试代码

@RequestMapping("/testParam")
public String testParam(String username, String password) {
    System.out.println("username=" + username + ",password=" + password);
    return "success";
}

前台测试代码

<a th:href="@{/paramController/testParam(username='admin',password='123456')}">通过控制器方法形参获取</a><br/>

测试结果

后台日志信息

username=admin,password=123456

2.2、同名形参多值

若请求所传输的请求参数中有多值情况,此时可以在控制器方法的形参中设置字符串数组或者字符串类型的形参接收此请求参数

  • 若使用字符串数组类型的形参,此参数的数组中包含了每一个数据

  • 若使用字符串类型的形参,此参数的值为每个数据中间使用逗号拼接的结果

当某个请求参数有多个值时,比如前台含有复选框的情况,还能否使用这种方式呢?“实践出真知”,现在就进行测试

后台测试代码

@RequestMapping("/testParam2")
public String testParam2(String username, String password, String hobby) {
    System.out.println("username=" + username + ", password=" + password + ", hobby=" + hobby);
    return "success";
}

前台测试代码

<!--为了更直观地在地址栏中看到请求参数,这里使用get类型请求方式-->
<form th:action="@{paramController/testParam2}" method="get">
    用户名:<input type="text" name="username"><br/>
    密码:<input type="password" name="password"><br/>
    爱好:<input type="checkbox" name="hobby" value="Spring">Spring
    <input type="checkbox" name="hobby" value="SpringMVC">SpringMVC
    <input type="checkbox" name="hobby" value="SpringBoot">SpringBoot
    <br/><input type="submit" value="测试请求参数">
</form>

测试结果

前台请求路径中复选框的值为hobby=Spring&hobby=SpringMVC&hobby=SpringBoot,即出现了多个hobby=value的情况

后台日志信息

username=hah, password=111111, hobby=Spring,SpringMVC,SpringBoot

可见 SpringMVC 的控制器方法,对多个hobby值使用了,进行拼接并赋值给同名形参

扩展:如果这里使用 Servlet API 进行获取请求参数,就不能使用getParameter()方法获取 hobby 值了,而要使用getParameterValues()方法

后台代码测试

后台日志信息:通过getParameter()只能获取到 hobby 的第一个值,而getParameterValues()可以以数组的形式返回 hobby 的所有值

当然还是那句话:不建议在 SpringMVC 中使用原生 Servlet API 方法!!!这里稍作回顾和了解即可

@RequestMapping("/testServletAPI2")
public String testServletAPI2(HttpServletRequest request) {
 String hobby = request.getParameter("hobby");
 String[] hobby2 = request.getParameterValues("hobby");
 System.out.println("hobby=" + hobby + ", hobby2=" + Arrays.toString(hobby2));
 return "success";
}
username=sdfg, password=sdfg, hobby=Spring, hobby2=[Spring, SpringMVC, SpringBoot]

另外,控制器方法中使用String类型的数组接收 hobby 值也是可以的

@RequestMapping("/testParam3")
public String testParam3(String username, String password, String[] hobby) {
    System.out.println("username=" + username + ", password=" + password + ", hobby=" + Arrays.toString(hobby));
    return "success";
}

后台日志信息

username=aaaaaaaaa, password=aaaaaaaa, hobby=[Spring, SpringMVC, SpringBoot]

3、@RequestParam

@RequestParam是将请求参数和控制器方法的形参创建映射关系

一共有三个属性:

  • value:指定为形参赋值的请求参数的参数名

  • required:设置是否必须传输此请求参数,默认值为true

  • 若设置为true,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400:Required String parameter'xxx'is not present

  • 若设置为false,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标识的形参的值为null

  • defaultValue:不管required属性值为truefalse,当value所指定的请求参数没有传输或传输的值为空值时,则使用默认值为形参赋值

实际开发中,请求参数与控制器方法形参未必一致,一旦出现这种情况,还能否接收到请求参数了呢?

这里简单地将前台name="username"改为name="user_name"进行测试,看下后台日志信息,果然没有接收到 user_name 这个请求参数

username=null, password=aaaaaaaa, hobby=[Spring, SpringMVC, SpringBoot]

扩展思考:这里也侧面证明一件事,SpringMVC 中对请求参数的赋值是根据是否同名来决定的,而不会根据参数在方法上的第几个位置决定,也就是说 SpringMVC 没有考虑将请求参数个数、类型与顺序控制器方法形参个数、类型与顺序进行绑定。如果我们来设计 SpringMVC,应该考虑这种方案么?

个人觉得,这种方案虽然可以实现与 Java 重载方法的一一绑定关系,但实际操作起来有一定难度:

  • 比如数字类型可以当作 String 处理,也可以当作 Integer 处理,不好区分

  • 退一步来讲,如果考虑重载方法,SpringMVC 底层势必要对类中所有重载方法进行循环,判断是否满足个数、类型和顺序的要求,性能上一定有所影响

而限制请求路径和请求方式不能完全相同的话,就没有这种苦恼了。即使是重载方法,通过不同请求路径或请求方法来界定到底访问哪个方法就可以了

SpringMVC 借助注解的方式,将请求参数与控制器方法形参关系绑定的决定权,交到开发者的手中。这种开发思维启发我们,如果有些功能不能很好地在底层进行实现,甚至可能会留下很多隐患时,还不如交给实际使用者,由他们去决定,否则很容易被使用者诟病(没有,我没有暗示某语言啊(●'◡'●))

此时使用@RequestParam注解就可以实现请求参数与控制器方法形参的绑定

后台测试代码

@RequestMapping("/testParam3")
public String testParam3(@RequestParam("user_name") String username, String password, String[] hobby) {
    System.out.println("username=" + username + ", password=" + password + ", hobby=" + Arrays.toString(hobby));
    return "success";
}

后台日志信息

username=ss, password=aaaaa, hobby=[Spring, SpringMVC, SpringBoot]

关于@RequestParam怎么使用,可以看下源码

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default ValueConstants.DEFAULT_NONE;
}
  • namevalue:绑定的请求参数名,互为别名,用哪个都一样

  • required属性:默认为true,表示必须要有此参数,不传则报错;不确定是否传参又不想报错,赋值为false即可

  • defaultValue属性:不管requiredtrue还是false,只要请求参数值为空(""null),就为形参附上此值

3.1、required

required默认为true,即要求该请求参数不能为空。因为是默认值,所以添加required="true"与不写required属性是一样的

这里先测试下默认情况下不传对应请求参数时系统的反应如何,只需要将user_name一行注释即可,或直接在浏览器地址栏删除该请求参数也一样

测试结果

报错信息:400错误的请求,必须的请求参数'user_name'...不存在

HTTP Status 400 - Required request parameter 'user_name' for method parameter type String is not present

经测试,不论是为 username 传空值还是不传值,都是400错误

/testParam3?user_name=&password=11&hobby=Spring&hobby=SpringMVC
/testParam3?password=11&hobby=Spring&hobby=SpringMVC

如果将required设置为false,还会报错吗?

后台测试代码:只需要对@RequestParam("user_name")稍作改动,修改为@RequestParam(value = "user_name", required = false)即可

@RequestMapping("/testParam3")
public String testParam3(@RequestParam(value = "user_name", required = false) String username, String password, String[] hobby) {
    System.out.println("username=" + username + ", password=" + password + ", hobby=" + Arrays.toString(hobby));
    return "success";
}

测试结果:可以发现,这次并没有报400错误

后台日志信息

username=null, password=1111, hobby=[Spring, SpringMVC]

这是不传 user_name 的情况,如果是传空值呢?

测试结果:同样访问成功,没有报400错误

后台日志信息

username=, password=111, hobby=[Spring, SpringMVC]

Q:不是说默认是true吗?为什么在没有使用@RequestParam注解时,也能正常访问呢?

A:这个默认值本身就是在使用@RequestParam注解时生效的,如果都没有使用到@RequestParam,就没有相应限制了

3.2、defaultValue

后台测试代码

@RequestMapping("/testParam3")
public String testParam3(
    @RequestParam(value = "user_name", required = false, defaultValue = "heh") String username,
    String password, String[] hobby) {
    System.out.println("username=" + username + ", password=" + password + ", hobby=" + Arrays.toString(hobby));
    return "success";
}

请求路径:传空值和不传值两种情况

/testParam3?user_name=&password=asdf&hobby=Spring&hobby=SpringMVC
/testParam3?password=asdf&hobby=Spring&hobby=SpringMVC

后台日志信息

username=heh, password=asdf, hobby=[Spring, SpringMVC]

可以发现,不管是为 username 传空值还是不传值,最终都会被赋上默认值

这里将required修改为true,即默认值的情况,发现也是可以请求成功的

注意required一节测试中,在required的默认值情况下,没有为请求参数赋值传值或传空值,会产生400的错误。

而只要为请求参数设置默认值,即使用@RequestParam注解的defaultValue属性赋上值,就不会有400错误了。

换句话说,只要设置了defaultValue属性值,required属性就失效形同虚设了

4、@RequestHeader

@RequestHeader是将请求头信息和控制器方法的形参创建映射关系

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

因为@RequestHeader@RequestParam别无二致,所以这里我们简单测试下效果

后台测试代码

@RequestMapping("/testHeader")
public String testHeader(
    @RequestHeader(value = "Host") String host,
    @RequestHeader(value = "Test", required = false, defaultValue = "RequestHeader") String test) {
    System.out.println("Host=" + host + ", test=" + test);
    return "success";
}

请求路径

http://localhost:8080/SpringMVC/paramController/testParam4

后台日志信息

Host=localhost:8080, test=RequestHeader

5、@CookieValue

@CookieValue是将 Cookie 数据和控制器方法的形参创建映射关系

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

注意

  • JSP中,Session依赖于CookieSession是服务器端的会话技术,Cookie是客户端的会话技术。

  • 会话技术默认的生命周期是浏览器开启和浏览器关闭,只要浏览器不关闭,Cookie将一直存在。

  • 调用getSession()方法时,首先会检测请求报文中是否有携带JSESSIONIDCookie。如果没有,说明当前会话是第一次创建Session对象,则

  • 在服务端创建一个Cookie,以键值对形式存储。键是固定的JSESSIONID,值是一个 UUID 随机序列

  • 在服务端创建一个HttpSession对象,并放在服务器所维护的 Map 集合中。Map 的键是JSESSIONID的值,值就是HttpSession对象

  • 最后把Cookie相应给浏览器客户端,此时JSESSIONIDCookie存在于响应报文中。每次浏览器向服务器发送请求都会携带Cookie,此后JSESSIONIDCookie将存在于请求报文中

为了能获取到Cookie值,需要先调用下getSession()方法。我们直接在之前的 testServletAPI() 方法中稍作修改

@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request) {
    HttpSession session = request.getSession();
    // ...
}

首次发送请求后,F12 查看前台该请求的响应报文信息

会发现在Set-Cookie属性中存在JSESSIONID=xxx的信息

Set-Cookie: JSESSIONID=C3DFF845C38BF655C02DDA0BD2DD5638; Path=/SpringMVC; HttpOnly

后面每次发送请求,JSESSIONIDCookie将会放在请求报文信息

会发现在Cookie属性中存在JSESSIONID=xxx的信息

Cookie: JSESSIONID=C3DFF845C38BF655C02DDA0BD2DD5638

经过上面的折腾,我们产生了Cookie数据,现在我们就可以使用@CookieValue注解进行操作了。正片开始~

后台测试代码

@RequestMapping("/testCookie")
public String testCookie(
    @CookieValue(value = "JSESSIONID") String jSessionId,
    @CookieValue(value = "Test", required = false, defaultValue = "CookieValue") String test) {
    System.out.println("jSessionId=" + jSessionId + ", test=" + test);
    return "success";
}

前台请求报文信息

后台日志信息

jSessionId=C3DFF845C38BF655C02DDA0BD2DD5638, test=CookieValue

6、通过实体类获取

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

前台测试代码

<form th:action="@{/paramController/testBean}" method="post">
    用户名:<input type="text" name="username"><br/>
    密码:<input type="password" name="password"><br/>
    性别:<input type="radio" name="gender" value="男人">男
    <input type="radio" name="gender" value="女人">女<br/>
    年龄:<input type="text" name="age"><br/>
    邮箱:<input type="text" name="email"><br/>
    <input type="submit" value="测试请求参数">
</form>

后台测试代码

@RequestMapping("/testBean")
public String testBean(User user) {
    System.out.println(user);
    return "success";
}

User 类:要求属性名与请求参数名一致

public class User {
    private String username;
    private String password;
    private String gender;
    private String age;
    private String email;
    // Setter、Getter方法略

    @Override
    public String toString() {
        return "User{" +"username='" + username + '\'' + ", password='" + password + '\'' + ", gender='" + gender + '\'' +", age='" + age + '\'' + ", email='" + email + '\'' +'}';
    }
}

测试结果

后台日志信息

User{username='aa', password='11', gender='女人', age='12', email='[email protected]'}
User{username='aa', password='11', gender='男人', age='12', email='[email protected]'}

貌似基本成功了,但却出现了乱码的情况,什么原因呢?

7、处理乱码问题

注意:在 Servlet 阶段,是通过request.setCharacterEncoding("UTF-8");的方式解决乱码问题的。虽然 SpringMVC 中可以使用HttpServletRequest对象,但是没有效果。原因也很简单,是因为请求参数获取在前,设置编码格式在后

事实胜于雄辩,简单测试下

后台测试代码

@RequestMapping("/testServletAPI3")
public String testServletAPI3(HttpServletRequest request) throws UnsupportedEncodingException {
    request.setCharacterEncoding("UTF-8");
    String username = request.getParameter("username");
    System.out.println("username=" + username);
    return "success";
}

前台测试代码

<form th:action="@{/paramController/testServletAPI3}" method="post">
    用户名:<input type="text" name="username"><br/>
    <input type="submit" value="测试请求参数">
</form>

后台日志信息

username=张三

可能你会说,上面的测试都是post请求,如果是get请求呢?问得好,下次不要问了

<a th:href="@{/paramController/testServletAPI3(username='张三')}">通过setCharacterEncoding设置编码</a><br/>

后台日志信息

username=张三

Q:这是为什么呢?怎么get请求还搞特殊?

A:这是因为 Tomcat 的 conf 目录下的 server.xml中配置了URIEncoding="UTF-8"的原因。这样get请求的乱码问题就可以一次性解决了

如果一开始就没有配置,那get请求也会乱码,所以拜托不是get请求搞特殊了喂!

Q:既然在server.xml配置下编码格式就行了,为什么只支持get请求啊?还说不是搞特殊?

A:...你赢了

Q:退一步来说,post请求能不能在请求参数获取之后再去处理也可以吧,只要知道其本身的编码

A:试一下咯

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/>

我们先通过 在线乱码恢复 看下,乱码的文本实际编码是什么

很显然,乱码本身为ISO-8859-1格式,我们转换为UTF-8编码格式即可

后台测试代码

// 对其进行iso-8859-1解码并重新UTF-8编码
username = new String(username.getBytes("ISO-8859-1"), "UTF-8");
System.out.println("username=" + username);

后台日志信息

username=张三

有上述测试可知,要想处理乱码问题,思路有二:

  1. 获取请求参数之后,手动解码编码。但是这种方式要求每次处理post请求的请求参数都要手动处理,太不人性化了吧。你嫌烦,我还嫌烦呢(❌)

  2. 获取请求参数之前“做手脚”:发送请求之前,也就是在Servlet处理请求之前(

    标签:return,String,SpringMVC,视图,public,请求
    From: https://www.cnblogs.com/zhaostudy/p/16895585.html

相关文章