一、简介
SpringMVC 通过一套 MVC 注解,让 POJO 成为处理请求的控制器,而无需实现任何接口。支持 REST 风格的 URL 请求
二、搭建环境及HelloWorld
2.1 加入架包
- commons-logging-1.2.jar
- spring-aop-5.0.7.RELEASE.jar
- spring-beans-5.0.7.RELEASE.jar
- spring-context-5.0.7.RELEASE.jar
- spring-core-5.0.7.RELEASE.jar
- spring-expression-5.0.7.RELEASE.jar
- spring-web-5.0.7.RELEASE.jar
- spring-webmvc-5.0.7.RELEASE.jar
2.2 配置web.xml
配置 DispatcherServlet:默认加载 /WEB-INF/<servlet-name>-servlet.xml
的 Spring 配置文件来启动 WEB 层的容器。同时也可以通过 contextConfigLocation
初始化参数自定义配置文件的位置和名称
<!-- 配置 dispatcherServlet -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置 DispatcherServlet 的一个初始化参数: 配置 SpringMVC 配置文件的位置和名称 -->
<!--
实际上也可以不通过 contextConfigLocation 来配置 SpringMVC 的配置文件, 而使用默认的
默认的配置文件为: /WEB-INF/<servlet-name>-servlet.xml
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 当前 Servlet 在 WEB 应用被加载的时候就被创建, 而不是在第一次请求的时候创建 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
DispatcherServlet 的任务就是拦截请求发送给 Spring MVC 控制器
2.3 创建 SpringMVC 视图解析器
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="edu.just.springmvc"></context:component-scan>
<!-- 配置视图解析器: 如何把 handler 方法的返回值解析为实际的物理视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
将视图逻辑名解析为:/WEB-INF/views/<viewName>.jsp
2.4 创建请求处理器类
- 使用 @RequestMapping 注解来映射请求的 URL
- 返回值会通过视图解析器解析为实际的物理视图,对于 InternalResourceViewResolver 视图解析器,会做如下的解析:
prefix + returnVal + suffix
这样的方式得到实际的物理视图,然后会做转发操作
@Controller //请求处理器
public class HelloWorld {
@RequestMapping("/helloworld")
public String hello() {
System.out.println("hello world");
return "success";
}
}
以上转发到 /WEB-INF/views/success.jsp
这个页面
三、具体使用
3.1 使用@RequestMapping映射请求
3.1.1
@RequestMapping 注解为控制器指定可以处理哪些 URL 请求,在类和方法都可以标注
- 类定义:提供初步的请求映射信息。相对于 WEB 应用的根目录
- 方法处:进一步细分映射信息。相对于类定义处的 URL,若类上面没有 @RequestMapping 注解,则方法处标记的 URL 相对于 WEB 应用的根目录
DispatcherServlet 截取请求后,就通过 @RequestMapping 提供的映射信息确认请求所对应的处理方法
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
@RequestMapping("/testRequestMapping")
public String testRequestMapping() {
System.out.println("testRequestMapping");
return SUCCESS;
}
}
访问到此方法的地址是:WEB容器的根路径/springmvc/testRequestMapping
,同时由于配置了视图解析器,因此直接转到 /WEB-INF/views/
下的 success.jsp
页面
@RequestMapping 的 value、method、params 以及 heads 分别标识请求 URL、请求方法、请求参数以及请求头的映射条件,他们之间是与的关系
@RequestMapping(..., method=RequestMethod.POST) //使用 method 属性来指定请求方式
@RequestMapping(..., params={"username", "age!=10"}, headers="Content-Type=text/html") //使用 params 和 headers
PS:用的比较少
3.1.2 使用 Ant 风格来写地址
?:匹配文件名中的一个字符
*:匹配文件名中的任意字符
**:匹配多层路径
/springmvc/test??? ——> /springmvc/testaaa
/springmvc/*/test ——> /springmvc/asv/test
/springmvc/**/test ——> /springmvc/aaa/bbb/test
PS:用的比较少
3.1.3 ⭐使用@PathVariable映射URL绑定的占位符
通过 @PathVariable
可以将 URL 占位符参数绑定到控制器处理方法的入参中
@RequestMapping("/testPathVariable/id={id123}")
public String testPathVariable(@PathVariable("id123") String id) {
System.out.println(id);
return SUCCESS;
}
URL 中的 {xxx}
占位符可以通过 @PathVariable("xxx")
绑定到操作方法的入参中。这个时候 @PathVariable
中的参数必须与占位符 {}
中的参数相同
@RequestMapping("/testPathVariable/id={id12}")
public String testPathVariable(@PathVariable String id12) {
System.out.println(id12);
return SUCCESS;
}
当然,也可以不用指定 @PathVariable
后面的参数,此时 {xxx}
占位符里面的参数可以直接绑定到控制器处理方法的入参中,但是两个参数必须相同
3.2 映射请求参数和请求参数
3.2.1 使用@RequestParam绑定请求参数值
@RequestParam 可以在后台获取从前台传过来的数据。
value
:请求参数的参数名
required
:该参数是否必须。默认为 true
defaultValue
:请求参数的默认值
@RequestMapping(value="/testRequestParam")
public String testRequestParam(@RequestParam(value="username") String un,
@RequestParam(value="age", required=false, defaultValue="0") Integer age) {
System.out.println("testRequestParam: " + un + ",age: " + age);
return SUCCESS;
}
上图获取从前台传过来的参数名为 username
和 age
的值,其中 age
是可选的,如前台的地址是 ...?username=5&age=5
如果参数为 int 类型,且没有加默认的值,则会报错。两种修改的方式:1. 将 int 类型改为 Integer;2. 如果参数类型必须为 int,则添加默认值
3.2.2 使用POJO对象绑定请求参数值
SpringMvc 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值,支持级联属性。
前台代码
<form action="springmvc/testpojo" method="post">
username: <input type="text" name="username"/>
<br>
password: <input type="password" name="password"/>
<br>
email: <input type="text" name="email"/>
<br>
city: <input type="text" name="address.city"/>
<br>
<input type="submit" value="Submit"/>
</form>
从后台获取前台传过来的数据
@RequestMapping("/testpojo")
public String testPojo(User user) {
System.out.println("^^" + user);
return SUCCESS;
}
3.2.3 使用Servlet API作为入参
包括HttpServletRequest
、HttpServletResponse
、HttpSession
、Principal
、Locale
、InputStream
、OutputStream
、Reader
、Writer
@RequestMapping("/testServletAPI")
public void testServletAPI(HttpServletRequest request, HttpServletResponse response, Writer out) throws IOException {
System.out.println("testServletAPI, " + request + ", " + response);
out.write("hello qorld");
}
3.3 处理模型数据(常用于回显操作)
3.3.1 ModelAndView
1.
springmvc 会把 ModelAndView 的 model 中数据放在 request 域对象中
/**
* 目标方法的返回值可以是 ModelAndView 类型
* 其中包含视图和模型信息
* SpringMVC 会把 ModelAndView 的 model 中的数据放入到 request 域对象中
*/
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
String viewName = SUCCESS;
ModelAndView modelAndView = new ModelAndView(viewName);
//添加模型数据到 ModelAndView 中
modelAndView.addObject("time", new Date());
return modelAndView;
}
success.jsp
time: ${requestScope.time }
这里从 request
作用域中获取参数 time
对应的值
3.3.2 Map和Model
- SpringMVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器
- 如果方法的入参为 Map 或 Model 类型,SpringMVC 会将隐含模型的引用传递给这些入参
Map 方式
/**
* 目标方法可以添加 Map(实际上也可以是 Model 类型或者 ModelMap 类型) 类型的参数
*/
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map) {
map.put("names", Arrays.asList("Tom", "Jerry", "Mike"));
return SUCCESS;
}
success.jsp
map: ${requestScope.names }
Model 方式:比较常用
@RequestMapping("value")
public String test(Model model) {
model.addAttribute("time", "hello");
return SUCCESS;
}
success.jsp
time: ${requestScope.time }
3.3.3 @SessionAttributes
将模型中的某个属性暂存到 HttpSession 中,以便多个请求之间可以共享这个属性
@SessionAttribute 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中
-
@SessionAttributes(types={User.class})
会将隐含模型中类型为User.class
的属性放到会话中 -
@SessionAttributes(value={"user"})
将名为 user 的模型属性添加到会话中 @SessionAttribute(values={"user1", "user2"})
@SessionAttributes(value={"user"}, types={User.class})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
@RequestMapping("/testSessionAttributes")
public String testSessionAttributes(Map<String, Object> map) {
User user = new User(null, "luwenhe", "lwh011305", "123@126.com");
map.put("user", user);
map.put("user1", "ni hao shi jie");
return SUCCESS;
}
}
PS:该注解只能放在类的上面,而不能修饰方法
session: ${sessionScope.user }
session1: ${sessionScope.user1 }
3.3.4 @ModelAttribute
在方法上使用 @ModelAttribute
注解,SpringMVC 在调用目标方法之前,会先逐个调用在方法上标注了 @ModelAttribute
的方法
index.jsp:用于从表单中输入数据,模拟从数据库中获取数据
<form action="springmvc/testModelAttribute" method="post">
<input type="hidden" name="id" value="1"/>
username: <input type="text" name="username" value="Tom"/>
<br>
email: <input type="text" name="email" value="tom@126.com"/>
<br>
<input type="submit" value="Submit"/>
</form>
如果没用 @ModelAttribute
注解,则会输出 User [Id=1, username=Tom, password=null, email=tom@126.com]
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
System.out.println("修改: " + user);
return SUCCESS;
}
如果在上述代码之前加入标注了 @ModelAttribute
注解的方法,模拟修改了对象 User
的信息
@ModelAttribute
public void getUser(@RequestParam(value="id", required=false) Integer id,
Map<String, Object> map) { //从后台获取前台参数名字为 id 的元素的值
System.out.println("id: " + id);
if(id != null) {
User user = new User(1, "Tom", "123", "tom@126.com");
System.out.println("从数据库中获取一个对象: " + user);
map.put("user", user);
}
}
最终的输出是:
id: 1
从数据库中获取一个对象: User [Id=1, username=Tom, password=123, email=tom@126.com]
修改: User [Id=1, username=Tom, password=123, email=tom@126.com]
可见 SpringMVC 从 Map
中取出 User
对象,并把对象传入了目标方法的参数中
注意:在 @ModelAttribute 修饰的方法中, 放入到 Map 时的键需要和目标方法参数的第一个字母小写的字符串一致, 如参数为 User, 则 Map 的键为 user
SpringMVC 确定目标方法 POJO 类型入参的过程:
1. 确定一个 key:
1). 若目标方法的 POJO 类型的参数没有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母的小写
2). 若使用了 @ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的属性值
2. 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入
1). 若在 @ModelAttribute 标记的方法在 Map 中保存过, 且 key 和 1 确定的 key 一致, 则会获取到.
3. 若 implicitModel 不存在 key 对应的对象, 则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰, 若使用了该注解,
且 @SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所对应的 value 值,
若存在则直接传到目标方法的入参中, 若不存在则将抛出异常
4. 若 Handler 没有标识 @SessionAttribute 注解或 @SessionAttribute 注解的 value
值中不包含 key, 则会通过反射来创建 POJO 类型的参数, 传入为目标方法的参数, 此时只能获取从前台传过来的值
5. SpringMVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中, 进而保存到 request 中
3.4 视图和视图解析器
- 请求方法执行完成后,最后返回一个 ModelAndView 对象。对于那些返回 String,View 或者 ModelMap 类型的方法,SpringMVC 会在内部将他们装配成一个 ModelAndView 对象
- SpringMVC 接主视图解析器(ViewResolver)得到最终的视图对象 View,最终的视图可以是 JSP,也可以是其他类型
视图
视图的作用是渲染模型数据,将模型数据以某种形式呈现给客户。常见的视图实现类有:
-
InternalResourceView
:将 JSP 或其他资源封装成一个视图,是 InternalResouceViewResolver 默认使用的视图实现类 -
JstlView
:如果 JSP 文件中使用了 JSTL 国际化标签的功能,则需要使用该试图类 -
AbstractExcelView
:Excel 文档视图的抽象类,用于构造 Excel 文档
视图解析器
所有视图解析器必须实现 ViewResovler
接口。常见的视图解析器类有:
-
BeanNameViewResolver
:将逻辑视图名解析为一个 Bean,Bean 的 id 等于逻辑视图名 -
InternalResourceViewResolver
:将视图名解析为一个 URL 文件
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="edu.just.springmvc"></context:component-scan>
<!-- 配置视图解析器: 如何把 handler 方法的返回值解析为实际的无力视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
重定向
如果字符串中带有 forward:
或者 redirect
,SpringMVC 会对他们做特殊处理,前者为转发操作,后者
为重定向
@RequestMapping("/testRedirect")
public String testRedirect() {
System.out.println("testRedirect...");
return "redirect:/index.jsp";
}
此代码会重定向到根目录下的 index.jsp
页面
3.5 使用RESTFul
REST 即 Representational State Tranfer,即表现层状态转化。
表现层(Representation):把资源具体呈现出来的形式。
状态转化(State Tranfer):每发出一个请求,就代表了客户端和服务器的一次交互。
如何发送 PUT 请求和 DELETE 请求?
- 需要配置
HiddenHttpMethodFilter
- 需要发送 POST 请求
- 需要在发送 POST 请求时携带一个
name="_method"
的隐藏域, 值为 DELETE 或 PUT
配置文件
<!--
配置 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>
HiddenHttpMethodFilter
:由于浏览器只支持 GET 和 POST,不支持 DELETE 和 PUT,因此此过滤器可以将请求转换为支持四种请求
前台页面:
<form action="springmvc/testRest/1" method="post">
<input type="hidden" name="_method" value="DELETE"/>
<input type="submit" value="TestRest DELETE"/>
</form>
从后台获取
@RequestMapping(value="/testRest/{id}", method=RequestMethod.DELETE) //delete 方法
@ResponseBody
public String testRestDelete(@PathVariable Integer id) {
System.out.println("testRest DELETE: " + id);
return SUCCESS;
}
@ResponseBody
一开始没有加,会报错,百度之后由此加上
3.5 处理静态资源
若将 DispatcherServlet 请求映射配置为 /,则 SpringMVC 将捕获 WEB 容器的所有请求,包括静态资源的请求,SpringMVC 会将他们当成一个普通请求来处理,因此会找不到对应处理器而报错
解决方案:可以在 SpringMVC 的配置文件中配置 <mvc:default-servlet-handler></mvc:default-servlet-handler>
的方式
原理:
<mvc:default-servlet-handler/>
将在 SpringMVC 上下文定义一个 DefaultServletHttpRequestHandler
,他会对进入 DispatcherServlet
的请求进行筛选,如果发现是没有经过映射的请求(@RequestMapping),就将请求交给 WEB 应用服务器的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet
继续处理
此时还需要在配置文件中加入 <mvc:annotation-driven/>
才可以运行
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!-- 标准配置 -->
<mvc:annotation-driven></mvc:annotation-driven>
⭐关于<mvc:annotation-driven>
<mvc:annotation-driven/>
会自带注册RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
与ExceptionHandlerExceptionResolver
三个 Bean同时提供以下支持:
①. 支持使用ConversionService
实例对表单进行类型转换
②. 支持使用@NumberFormat annotation
、@DateTimeFormat
注解完成数据类型的格式化
③. 支持使用@Valid
注解对 JavaBean 实例进行 JSR 303 验证
④. 支持使用@RequestBody
和@ResponseBody
注解
PS:一般情况下,都会配置 <mvc:annotation-driven/>
这个说明
3.6 数据绑定
核心是 DataBinder
-
DataBinder
:调用装配在 SpringMVC 上下文的ConversionService
组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中 -
Validator
:对已经绑定了请求信息的入参对象进行数据合法性校验 -
BindingResult
:SpringMVC 抽取BindingResult
中的入参对象和校验错误对象,将它们赋给处理方法的响应入参
@InitBinder注解
- 由
@InitBinder
标识的方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的字类,用于完成由表单字段到 JavaBean 属性的绑定 -
@InitBinder
方法不能有返回值,必须声明为 void -
@InitBinder
方法的参数通常是 WebDataBinder
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("lastName");
}
以上代码表示不能绑定对象中的名为 lastName
的属性,造成的结果就是最后不能输出属性名为 lastName
字段的值
格式化操作
可以在定义的属性上面使用 @NumberFormat
和 @DateTimeFormat
注解进行数据格式化
对数字进行格式化
@NumberFormat(pattern="###.#")
private Float salary;
对日期进行格式化
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
最后别忘了加上注解
<mvc:annotation-driven/>