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 工作流程
-
用户通过视图层发送请求到服务器,在服务器中请求被Controller 接收
-
Controller 调用相应的 Model 层处理请求
-
Model 层处理完毕将结果返回到 Controller
-
Controller 再根据请求处理的结果找到相应的 View 视图
-
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.xml
、web.xml
、springMVC.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
中有TYPE
和METHOD
两个属性值,表示其可以作用在类或方法上
也就是说@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
属性有哪些用途呢?
- 通过请求的请求方式(
GET
或POST
)匹配请求映射
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
属性时,可以接收GET
和POST
的请求,那么应该是默认不对请求方式限制了
本着严谨的态度,再测试下是否在不添加method
属性时,默认也支持PUT
和DELETE
请求
不过,PUT
和DELETE
请求比较特殊,需要使用到隐藏域,且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>
测试结果也是成功的
存疑点:本来也想测试下HEAD
、PTACH
、OPTIONS
和TRACE
这几种不常用的请求方式的,但是发现 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
改成POST
、PUT
和DELETE
对应进行验证即可,这里就不一一验证了
虽然无关紧要,但就是好奇为嘛我的是中文提示(灬°ω°灬)
发现这个版本 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.html
、successpost.html
、successput.html
和successdelete.html
,在这个四个不同页面中标注不同的内容以示区分
验证结果:可以看到,GET
/POST
/PUT
/DELETE
等请求方式,均能够被正常接收和处理
4)form 表单支持哪些请求方式?
-
常用的请求方式有
GET
,POST
,PUT
,DELETE
,但是目前浏览器只支持GET
和POST
(OS:刚才还有点疑惑的,这里好像“水落石出了”) -
若在 form 表单提交时,为
method
设置了其他请求方式的字符串(PUT
或DELETE
),则按照默认的GET
请求方式处理 -
若要发送
PUT
和DELETE
请求,则需要通过 Spring 提供的过滤器HiddenHttpMethodFilter
,在 restful 部分会讲到(OS:我上面刚自己研究了下,没想到老师这里会讲^_^
)
如果去除过滤器HiddenHttpMethodFilter
的配置,同时注释掉隐藏域的代码,并将method
为post
值改成put
和delete
<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
请求方式处理,这里进行验证
可以发现,本应走PUT
和DELETE
方式的请求,都被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
注解的value
和method
属性,但是不满足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
注解的value
和method
属性,但是不满足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
属性:支持GET
、POST
、PUT
、DELETE
,默认不限制,匹配失败报405
-
params
属性:四种方式,param
、!param
、param==value
、param!=value
,匹配失败报400
-
headers
属性:四种方式,header
、!header
、header==value
、header!=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
属性值为true
或false
,当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;
}
-
name
和value
:绑定的请求参数名,互为别名,用哪个都一样 -
required
属性:默认为true
,表示必须要有此参数,不传则报错;不确定是否传参又不想报错,赋值为false
即可 -
defaultValue
属性:不管required
是true
还是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
是将请求头信息和控制器方法的形参创建映射关系
一共有三个属性:value
、required
、defaultValue
,用法同@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 数据和控制器方法的形参创建映射关系
一共有三个属性:value
、required
、defaultValue
,用法同@RequestParam
注意:
在
JSP
中,Session
依赖于Cookie
,Session
是服务器端的会话技术,Cookie
是客户端的会话技术。会话技术默认的生命周期是浏览器开启和浏览器关闭,只要浏览器不关闭,
Cookie
将一直存在。调用
getSession()
方法时,首先会检测请求报文中是否有携带JSESSIONID
的Cookie
。如果没有,说明当前会话是第一次创建Session
对象,则在服务端创建一个
Cookie
,以键值对形式存储。键是固定的JSESSIONID
,值是一个 UUID 随机序列在服务端创建一个
HttpSession
对象,并放在服务器所维护的 Map 集合中。Map 的键是JSESSIONID
的值,值就是HttpSession
对象最后把
Cookie
相应给浏览器客户端,此时JSESSIONID
的Cookie
存在于响应报文中。每次浏览器向服务器发送请求都会携带Cookie
,此后JSESSIONID
的Cookie
将存在于请求报文中
为了能获取到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
后面每次发送请求,JSESSIONID
的Cookie
将会放在请求报文信息
会发现在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=张三
有上述测试可知,要想处理乱码问题,思路有二: