SpringMVC学习01:请求路径匹配和参数绑定
SpringMVC入门案例
案例程序
-
新建MAVEN工程,在
pom.xml
中导入坐标如下:<!-- 版本锁定:指定Spring版本 --> <properties> <spring.version>5.0.2.RELEASE</spring.version> </properties> <!-- 依赖坐标 --> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> </dependencies>
-
配置SpringMVC组件
-
将SpringMVC组件注入Spring核心容器: 在工程
resource
目录下创建文件bean.xml
如下<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置spring创建容器时扫描的包 --> <context:component-scan base-package="cn.maoritian"></context:component-scan> <!-- 配置视图解析器,用于解析项目跳转到的文件的位置 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean> <!-- 配置spring开启注解mvc的支持 --> <mvc:annotation-driven></mvc:annotation-driven> </beans>
-
配置SpringMVC核心控制器
DispatcherServlet
,并使Spring容器在TOMCAT初始化时创建. 在工程的webapp/WEB_INF
目录下配置web.xml
如下:<web-app> <!-- 配置核心控制器 --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 使核心控制器初始化时读取bean.xml文件创建Spring核心容器 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:bean.xml</param-value> </init-param> <!-- 设置该Servlet的优先级别未最高,使之最早创建 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
-
编写控制器和视图
-
编写主页视图
index.jsp
: 在工程的webapp
目录下创建视图index.jsp
如下<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>主页</title> </head> <body> <h3>入门案例</h3> <a href="hello">入门案例</a> </body>
-
在工程的
java
目录下创建控制器类cn.maoritian.HelloController
如下:@Controller public class HelloController { @RequestMapping(path="/hello") // 指定方法对应的URL public String helloHandler() { System.out.println("Hello SpringMVC!!"); return "success"; // 指定跳转的视图的地址,被ViewResolver解析为 /WEB-INF/pages/success.jsp } }
-
在工程的
webapp/WEB_INF/pages
目录下创建成功跳转视图success.jsp
如下:<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <html> <head> <title>Title</title> </head> <body> <h3>跳转成功</h3> </body> </html>
-
-
配置TOMCAT服务器
-
点击右上角配置,添加TOMCAT服务器
选择本地TOMCAT服务器
-
在
Deployment
选项卡下将当前项目部署到TOMCAT服务器上,并在Application Context
中配置当前项目的URL路径,可以看到我配置的路径是/myProject
-
-
测试: 访问
http://localhost:8080/myProject/
,可以看到我们的项目主页
点击链接,跳转到success.jsp
页面并在控制台输出"Hello SpringMVC!!"
案例执行流程分析
案例的执行流程
- 启动Tomcat服务器时,由于配置了
<load-on-startup>
标签,所以首先创建DispatcherServlet
对象并加载bean.xml
配置文件 - 由于
bean.xml
中开启了注解扫描,HelloController
对象被创建并加入Spring容器中 - 浏览器请求
index.jsp
,请求会先到达DispatcherServlet
核心控制器,根据配置@RequestMapping
注解找到具体要执行的方法helloHandler
- 执行方法
helloHandler
,得到返回值. 的视图解析器解析返回值,查找到对应的JSP文件success.jsp
- Tomcat服务器渲染页面,做出响应
SpringMVC核心组件
- 前端控制器(核心控制器)
DispatcherServlet
:用户请求最先达到的控制器,前端控制器调用其他组件处理请求,是MVC架构中的C,是整个流程控制的核心.其存在降低了组件间的耦合性. - 处理器映射器
HandlerMapping
:负责根据用户请求找到处理器. - 处理器
Handler
:具体的业务方法. - 处理器适配器
HandlAdapter
: 对处理器进行执行.这是一种适配器模式的应用. - 视图解析器
ViewResolver
: 负责将处理结果生成视图.ViewResolver
首先根据逻辑视图名
解析成物理视图名
即具体的页面地址,再生成View
视图对象,最后对View
进行渲染将处理结果通过页面展示给用户. - 视图
View
: 具体的页面
其中处理器映射器HandlerMapping
,处理器适配器HandlAdapter
,视图解析器ViewResolver
称为SpringMVC三大组件.在bean.xml
中声明<mvc:annotation-driven conversion-service="conversionService"/>
标签相当于自动配置了处理器映射器
和处理器适配
请求路径匹配
@RequestMapping
注解: 匹配路径与处理器
@RequestMapping
注解用于建立请求URL路径
和处理器
之间的对应关系.
-
出现位置: 可以出现在类上,也可以出现在方法上.
- 当它既出现在类上也出现在方法上时,类上注解值为请求URL的一级目录,方法上注解值为请求URL的二级目录
- 当它只出现在方法上时,该注解值为请求URL的一级目录
-
其属性如下:
-
path
:value
属性的别名,指定请求的URL,支持Ant风格
表达式,通配符如下:通配符 说明 ?
匹配文件(路径)名中的一个字符 *
匹配文件(路径)名中的任意数量(包括0个)的字符 **
匹配任意数量(包括0个)的路径 例如
- 路径
/project/*.a
匹配项目根路径下
所有在/project
路径下的.a
文件 - 路径
/project/p?ttern
匹配项目根路径下
的/project/pattern
和/project/pXttern
,但不能匹配/project/pttern
- 路径
/**/example
匹配项目根路径下
的/project/example
,/project/foo/example
,和/example
- 路径
/project/**/dir/file.*
匹配项目根路径下
的/project/dir/file.jsp
,/project/foo/dir/file.html
,/project/foo/bar/dir/file.pdf
- 路径
/**/*.jsp
匹配项目根路径
下的所有jsp文件
另外,遵循
最长匹配原则
,若URL请求了/project/dir/file.jsp
,现在存在两个匹配模式:/**/*.jsp
和/project/dir/*.jsp
,那么会根据/project/dir/*.jsp
来匹配. - 路径
-
-
method
: 指定HTTP请求方法(可选RequestMethod.GET
,RequestMethod.HEAD
,RequestMethod.POST
,RequestMethod.PUT
等),多个值之间是或的关系. -
params
: 指定请求参数的限制,支持简单的表达式,如:@RequestMapping(params={"param1"})
,表示请求参数中param1
必须出现@RequestMapping(params={"!param1"})
,表示请求参数中param1
不能出现@RequestMapping(params={"param1=value1"})
,表示请求参数中param1
必须出现且为value1
@RequestMapping(params={"param1!value1"})
,表示请求参数中param1
必须出现且不为value1
多个值之间是与的关系
-
headers
: 限定HTTP请求中必须包含的请求头,同样支持简单的表达式其中
path
和method
属性较常用
@PathVaribale
注解: 绑定URL占位符,支持REST风格URL
REST风格URL
REST风格URL有以下特点:
- 资源
Resources
: 通过一个URI来指定一个具体的资源 - 表现层
Representation
: 把资源具体呈现出来的形式 - 状态转化
State Transfer
: 通过HTTP请求方法来决定状态转化
例如:
REST风格URL | 含义 |
---|---|
GET /accounts | 查找所有用户 |
POST /accounts | 新建用户 |
GET /accounts/{ID} | 查找对应ID的用户 |
PUT /accounts/{ID} | 更新对应ID的用户 |
DELETE /accounts/{ID} | 删除对应ID的用户 |
@PathVaribale
注解的使用
在@RequestMapping
注解的path
属性中用{param}
声明占位符,将展位符@PathVariable
注解的name
属性来修饰处理器参数来将占位符对应的值赋给方法的参数.例如
对于以下的请求和方法:
<a href="account/findAccount/10">查询账户</a>
// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {
@RequestMapping("/findAccount/{id}")
public void findAccount(@PathVariable(name = "id") Integer accountId) {
// accountId = 10
// 方法体...
}
}
访问URLhttp://localhost:8080/myProject/account/findAccount/10
会将10
传给findAccount
方法的accountId
参数.
请求参数的绑定
参数绑定的示例
SpringMVC将请求参数中的param=value
中的value
传递给控制器方法的param
参数,例如:
对于以下请求和方法:
<a href="account/findAccount?accountId=10">查询账户</a>
// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {
@RequestMapping(path = "/findAccount")
public void findAccount(Integer accountId) {
// accountId = 10
// 方法体...
}
}
SpringMVC中会将10
传给findAccount
方法的accountID
参数传递给HandlerAdapter
执行.
@RequestParam
注解: 为处理器方法参数起别名
@RequestParam
注解作用在方法参数上,把请求中指定名称的参数给处理器方法中的形参赋值,相当于给处理器方法起别名.其属性如下:
name
:value
属性的别名,指定请求参数的名称required
: 指定该请求参数是否必须的,默认为true
例如jsp中发送请求如下
<a href="testRequestParam?param1=value">测试requestParam注解</a>
处理器方法中给对应参数加上@RequestParam(name="param1")
注解来接收参数
@RequestMapping("/testRequestParam")
public String handlerMethod(@RequestParam("param1") String username) {
// 方法体...
}
各种类型请求参数的绑定
SpringMVC内置参数绑定类型
SpringMVC支持三种类型的参数绑定
- 基本数据类型和String类型
- JavaBean类型
- 集合类型
数据绑定要求请求参数名和方法中的参数名相同,或使用@RequestParam
为方法参数起别名.
基本数据类型和String类型的参数绑定
对于基本数据类型,只需要以方法参数名作为请求参数名即可.示例如下:
<a href="account/findAccount?accountId=10&accountName=zhangsan">查询账户</a>
// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {
@RequestMapping("/findAccount")
public String findAccount(Integer accountId, String accountName) {
// accountId = 10, accountName = "zhangsan"
// 方法体...
}
}
JavaBean类型的参数绑定
JavaBean类型的参数,要想实现绑定,就必须实现其空参构造函数和所有属性的get,set方法
-
若JavaBean参数的属性中只包含基本数据类型和String类型属性,以属性名作为请求参数名,则SpringMVC会自动将其封装成JavaBean对象.示例如下:
例如JavaBean类的定义如下:
// JavaBean类 public class Account implements Serializable { private String username; private Integer age; // 所有属性的getset方法... }
则其对应的请求参数名如下:
<form action="account/updateAccount" method="post"> <label>名称</label><input type="text" name="username"><br/> <label>年龄</label><input type="text" name="age"><br/> <input type="submit" value="保存"> </form>
-
若JavaBean参数的属性中包含其它JavaBean对象,则以
外层类属性名.内层类属性名
作为请求参数,示例如下:例如JavaBean类的定义如下:
public class Account implements Serializable { private String username; private Intger age; private User user; // 所有属性的getset方法... } public class User implements Serializable{ private String uname; private Double umoney; // 所有属性的getset方法... }
则其对应的请求参数名如下:
<form action="account/updateAccount" method="post"> <label>名称</label><input type="text" name="username"><br/> <label>年龄</label><input type="text" name="age"><br/> <label>用户名</label><input type="text" name="user.uname"><br/> <label>用户余额</label><input type="text" name="user.umoney"><br/> <input type="submit" value="保存"> </form>
集合类型的参数绑定
对JavaBean类中的集合属性进行参数绑定,可以分为List
类型的参数绑定和Set
类型的参数绑定
- 对于
List
类型参数,其请求参数名为集合名[下标]
,List
类型参数可以对List
,Set
,数组
类型成员进行赋值,但是诡异的是对Set
和数组
成员进行赋值时,要在Bean类的构造函数中new出对应的Set
或数组
,然而对于List
就不用,下边的代码就可以直接跑,不知道为什么,希望知道的朋友不吝赐教 - 对于
Set
类型参数,其请求参数名为集合名[键]
,Set
类型参数可以对Set
,Propertis
类型成员进行赋值.
例如JavaBean类的定义如下:
public class Account implements Serializable {
private String username;
private Intger age;
private List<User> list; // List集合属性
private Map<String, User> map; // Map集合属性
// 所有属性的getset方法...
}
public class User implements Serializable{
private String uname;
private Double umoney;
// 所有属性的getset方法...
}
则其对应的请求参数名如下:
<form action="account/updateAccount" method="post">
用户名称:<input type="text" name="username"><br/>
用户密码:<input type="password" name="password"><br/>
用户年龄:<input type="text" name="age"><br/>
账户1名称:<input type="text" name="accounts[0].name"><br/>
账户1金额:<input type="text" name="accounts[0].money"><br/>
账户2名称:<input type="text" name="accounts[1].name"><br/>
账户2金额:<input type="text" name="accounts[1].money"><br/>
账户3名称:<input type="text" name="accountMap['one'].name"><br/>
账户3金额:<input type="text" name="accountMap['one'].money"><br/>
账户4名称:<input type="text" name="accountMap['two'].name"><br/>
账户4金额:<input type="text" name="accountMap['two'].money"><br/>
<input type="submit" value="保存">
</form>
另外还有一个比较诡异的地方.我尝试将集合类型的参数绑定到控制器方法的参数上,然而不行
- 若我将方法参数类型设为List<>,则会报500错误,提示List不能初始化
- 若我将方法参数设为ArrayList<>,不会报错,但是不能注入
下面是错误的代码@RequestMapping("/saveAccounts") //public String saveAccounts(List<Account> accounts) { // 若设为List类型,则会报500错误 public String saveAccounts(LinkedList<Account> accounts) { // 若设为LinkedList类型,不报错但是初始化不上 System.out.println(accounts); return "success"; }
上面是错误的代码
不知道为什么会这样,若有知道时怎么回事的朋友,还望不吝赐教
自定义数据类型参数绑定
表单提交的任何数据类型都是字符串类型,SpringMVC定义了转换器,将字符串转化为我们方法参数的各种类型.我们也可以实现自定义的转换器以实现自定义的参数类型转换
自定义的类型转换器要实现Converter<String, T>
接口,并在Spring容器配置bean.xml
中配置该实现类. 示例如下:
在工程的java
目录下创建控制器类cn.maoritian.util.StringToDateConverter
类,实现Converter<String, Date>
接口,完成从String
类到Date
类的转换:
// 自定义的类型转换器,完成从String类到Date类的转换
public class StringToDateConverter implements Converter<String, Date> {
public Date convert(String source) {
try {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(source);
return date;
} catch (Exception e) {
throw new RuntimeException("类型转换错误");
}
}
}
在Spring容器配置bean.xml
中加入如下配置:
<!-- 配置的类型转换器工厂 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<!-- 诸如我们自定义的类型转换器 -->
<bean class="cn.maoritian.utils.StringToDateConverter"/>
</set>
</property>
</bean>
通过原始ServletAPI对象处理请求
SpringMVC支持使用原始ServletAPI作为控制器方法的参数,包括HttpServletRequest
,HttpServletResponse
,HttpSession
对象,他们都可以直接用做控制器方法的参数.示例如下:
@RequestMapping("/path")
public void myHandler(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println(request.getParameter("param1"));
System.out.println(request.getParameter("param1"));
response.getWriter().println("<h3>操作成功</h3>");
}
解决请求参数绑定中文乱码问题
在web.xml
中配置编码转换过滤器,即可解决请求参数中文乱码的问题.
<!-- 配置编码转换过滤器 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filterclass>
<!-- 指定字符集 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- 过滤所有请求 -->
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>