Spring MVC 概述

Spring MVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于 SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 中。

Spring MVC 开发步骤

导入 Spring MVC 相关坐标

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>

配置Spring MVC 核心控制器 DispathcerServlet

<!--配置 SpringMVC 的前端控制器-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

创建 Controller 类和视图页面

@Controller
public class UserController {
@RequestMapping("/quick")
public String save(){
System.out.println("quick");
return "success.jsp";
}
}

配置 Spring MVC 核心文件 spring-mvc.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 组件扫描-->
<context:component-scan base-package="com.vsneko.controller"/>
</beans>

Spring MVC 执行流程

  1. 用户发送请求至前端控制器 DispatcherServlet。
  2. DispatcherServlet 收到请求调用 HandleMapping 处理器映射器。
  3. 处理器映射器找到具体的处理器 (可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器 (如果有则生成) 一并返回给 DispatcherServlet。
  4. DispatcherServlet 调用 HandleAdapter 处理器适配器。
  5. HandlerAdapter 经过适配调用具体的处理器 (Controller,也叫后端控制器)。
  6. Controller 执行完成返回 ModelAndView。
  7. HandlerAdapter 将 Controller 执行结果 ModelAndView 返回给 DispatcherServlet。
  8. DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
  9. ViewReslover 解析后返回具体 View。
  10. DispatcherServlet 根据 View 进行渲染视图 (即将模型数据填充至视图中)。DispatcherServlet 响应用户。

Spring MVC 流程图

Spring MVC 的数据响应

数据响应方式

  1. 页面跳转
    • 直接返回字符串
    • 通过 ModelAndView 对象返回
  2. 回写数据
    • 直接返回字符串
    • 返回对象或集合

页面跳转

  1. 返回字符串形式。

直接返回字符串形式,此种方式会将返回的字符串与视图解析器的前后缀进行拼接然后跳转。

转发: “forward: success.jsp”, “forward: “可以省略不写。

重定向: “redirect: success.jsp”

@RequestMapping("/test")
public String test() {
return "forward: /success.jsp";
}
  1. 返回 ModelAndView。
// 最普通的 ModelAndView
@RequestMapping("/test2")
public ModelAndView test2(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("username", "abc");
modelAndView.setViewName("/success.jsp");
return modelAndView;
}
// Spring MVC 容器在调用方法时,会帮你对方法的参数进行相应的注入。
@RequestMapping("/test2")
public ModelAndView test2(ModelAndView modelAndView){
modelAndView.addObject("username", "abc");
modelAndView.setViewName("/success.jsp");
return modelAndView;
}
// 将 Model 和 View 拆分,单独操作。
@RequestMapping("/test2")
public String test2(Model model){
model.addAttribute("username", "abc");
return "/success.jsp";
}

如果要使用原生的 HttpServletRequest对象,也可以利用 SpringMVC 容器来自动注入。(不推荐)

// Spring MVC 容器在调用方法时,会帮你对方法的参数进行相应的注入。
@RequestMapping("/test2")
public String test2(HttpServletRequest request){
request.setAttribute("username", "abc");
return "/success.jsp";
}

回写数据(直接返回字符串)

在 Web基础 阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用 response.getWriter().print("Hello World") 即可。(不推荐)

@RequestMapping("/test3")
public void test3(HttpServletResponse response){
try {
response.getWriter().println("Hello World");
} catch (IOException e) {
e.printStackTrace();
}
}

在 SpringMVC 中,更好的做法是在方法的上方添加 @ResponseBody 注解,告诉 Spring MVC 容器直接回写数据。

@RequestMapping("/test3")
@ResponseBody
public String test3(){
return "Hello World";
}

使用 Json 的转换工具

使用 jackson 工具可以将对象转换成 json 格式数据来返回。

  1. 导入相关依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.11.0</version>
</dependency>
  1. 使用
@RequestMapping("/test3")
@ResponseBody
public String test3() throws JsonProcessingException {
User u = new User();
u.setName("张三");
u.setAge(19);
// 使用 jackson 将 User 对象转换成 json
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(u);
return json;
}

回写数据(返回对象或集合)

我们可以通过配置来实现让 Spring MVC 容器自动帮我们将对象转换成 Json 格式数据,此时返回值可以直接是对象或者集合。

@RequestMapping("/test3")
@ResponseBody
public User test3() {
User u = new User();
u.setName("张三");
u.setAge(19);
return u;
}

只需要在 spring-mvc.xml 配置文件下配置如下代码即可。

<!-- 配置处理器映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</list>
</property>
</bean>

使用 MVC 注解驱动

在方法上添加 @ResponseBody 就可以返回 json 格式字符串,但是这样配置比较麻烦,配置的代码比较多,因此我们可以使用 mvc 的注解驱动来代替上述配置。

只需要在 spring-mvc.xml 文件内配置如下代码即可。

<mvc:annotation-driven />

在 Spring MVC 的各个组件中,处理器映射器、处理器适配器、视图解析器被称为 Spring MVC 的三大组件。

使用 <mvc:annotation-driven /> 会自动加载 RequestMappingHandlerMapping (处理映射器) 和 RequestMappingHandlerAdapter (处理适配器)。同时使用此注解,默认底层会集成 jackson 进行对象或集合的 json 格式字符串的转换。

Spring MVC 获取请求数据

获取基本类型参数

Controller 中的业务方法的参数名称要与请求参数的 name 一致,参数值会自动映射匹配。

// http://localhost:8080/user/test4?username=Zhangsan&age=21
@RequestMapping("/test4")
@ResponseBody
public void test4(String username, int age) {
System.out.println(username);
System.out.println(age);
}

Spring MVC 容器会自动进行数据类型转换。

获取POJO类型参数

Controller 中的业务方法的POJO参数的属性名称要与请求参数的 name 一致,参数值会自动映射匹配。

// http://localhost:8080/user/test4?name=Zhangsan&age=19
@RequestMapping("/test4")
@ResponseBody
public void test4(User user) {
System.out.println(user.getName());
System.out.println(user.getAge());
}

获取数组类型参数

Controller 中的业务方法的数组名称要与请求参数的 name 一致,参数值会自动映射匹配。

// http://localhost:8080/user/test4?strs=aaa&strs=bbb&strs=ccc
@RequestMapping("/test4")
@ResponseBody
public void test4(String[] strs) {
System.out.println(Arrays.asList(strs));
}

获取集合类型参数

目前并不能直接映射集合参数,需要将集合封装到一个VO对象中。

package com.vsneko.domain;
import java.util.List;
public class VO {

private List<User> userList;
public List<User> getUserList() {
return userList;
}
public void setUserList(List<User> userList) {
this.userList = userList;
}
@Override
public String toString() {
return "VO{" +
"userList=" + userList +
'}';
}
}
@RequestMapping("/test4")
@ResponseBody
public void test4(VO vo) {
System.out.println(vo);
}
<form action="/test4" method="post">
<input type="text" name="userList[0].name" />
<input type="text" name="userList[0].age" />
<br />
<input type="text" name="userList[1].name" />
<input type="text" name="userList[1].age" />
<br />
<input type="submit" value="提交" />
</form>

获取集合类型参数2

当使用 ajax 提交数据时,可以指定 ContentType 为 application/json 形式,那么在方法参数位置使用 @RequestBody 注解,就可以直接接受集合数据而无需使用 POJO 进行包装。

@RequestMapping("/test4")
@ResponseBody
public void test4(@RequestBody List<User> userList) {
System.out.println(userList);
}

开放静态资源的访问权限

<!-- 配置哪些目录的请求为资源目录 -->
<mvc:resources mapping="/js/**" location="/js/" />

或者

<!-- 如果 Spring MVC 找不到请求目标,则交由 Tomcat 来处理 -->
<mvc:default-servlet-handler />

配置全局过滤 filter

<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>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

参数绑定注解

当请求的参数名称与 Controller 的业务方法参数名称不一致时,就要通过 @RequestParam 注解来显式绑定。

@RequestMapping("/test4")
@ResponseBody
public void test4(@RequestParam(value = "name") String username) {
System.out.println(username);
}

注解 @RequestParam 还有如下参数可以使用:

  • value: 请求参数名称
  • required: 请求参数是否必须,默认为 true ,提交时没有此参数则报错。
  • defaultValue: 没有指定参数时的默认值

获取 Restful 风格的参数

Restful 是一种软件架构风格,设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。

Restful 风格的请求是使用 “URL + 请求方式” 表示一次请求目的的,HTTP协议里面四个表示操作的动词如下:

  • GET: 用于获取资源
  • POST: 用于新建资源
  • PUT: 用于更新资源
  • DELETE: 用于删除资源
// http://localhost:8080/user/test4/Zhangsan
@RequestMapping(value = "/test4/{name}", method = RequestMethod.GET)
@ResponseBody
public void test4(@PathVariable(value = "name") String username) {
System.out.println(username);
}

自定义类型转换器

Spring MVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成 int 型进行参数设置。

但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如日期类型的数据。

开发步骤:

  1. 定义转换器类,实现 Converter 接口
package com.vsneko.converter;

import org.springframework.core.convert.converter.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateConverter implements Converter<String, Date> {
public Date convert(String dateStr) {
// 将日期字符串转换成日期对象
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = format.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
  1. 在 spring-mvc.xml 配置文件中声明转换器
<!--声明转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.vsneko.converter.DateConverter" />
</list>
</property>
</bean>
  1. <annotation-driven> 中引用转换器
<mvc:annotation-driven conversion-service="conversionService"/>

获取请求头

使用 @RequestHeader 注解可以获取请求头信息,相当于 request.getHeader(name)

@RequestHeader 注解的属性如下:

  • value: 请求头的名称
  • required: 是否必须携带此请求头
@RequestMapping(value = "/test5")
@ResponseBody
public void test5(@RequestHeader(value = "User-Agent") String user_agent) {
System.out.println(user_agent);
}

使用 @CookieValue 注解可以获取请求头里面的 Cookie 信息。

@CookieValue 注解的属性如下:

  • value: 指定 Cookie 的名称
  • required: 是否必须携带此 Cookie
@RequestMapping(value = "/test5")
@ResponseBody
public void test5(@CookieValue(value = "JSESSIONID") String jsessionid) {
System.out.println(jsessionid);
}

文件上传

单文件上传

  1. 导入 fileuploadio 的坐标
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
  1. 配置文件上传解析器
<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
<property name="maxUploadSize" value="500000" />
</bean>
  1. 编写文件上传代码
// Spring MVC 会自动把上传的文件封装成一个 MultipartFile 对象
// uploadFile 参数名必须与表单上的 name 属性一致才能自动绑定
@RequestMapping(value = "/test6")
@ResponseBody
public void test6(String name, MultipartFile uploadFile) throws IOException {
String originalFilename = uploadFile.getOriginalFilename();
uploadFile.transferTo(new File("C:\\upload\\" + originalFilename));
}

多文件上传

与单文件上传类似,如果表单的每个文件的 name 属性一致,则在服务端用 MultipartFile[] 接收,然后循环遍历即可。

Spring MVC 拦截器

拦截器的作用

Spring MVC 的拦截器 类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。

将拦截器按一定的顺序联结成一条链,这条链称为 拦截器链(Interceptor Chain) ,在访问被拦截的方法或字段时,拦截器链中的拦截器就会按照之前定义的顺序被调用,拦截器也是 AOP 思想的具体实现。

拦截器与过滤器的区别

区别 过滤器 拦截器
使用范围 是 Servlet 规范中的一部分,任何 Java Web工程都可以使用 是 Spring MVC 框架自己的,只有使用了 Spring MVC 框架的工程才能使用
拦截范围 在 url-pattern 中配置了 /* 之后可以对所有资源进行拦截 只会拦截访问的控制器方法,不会拦截 jsp 和其他静态资源

拦截器快速入门

自定义拦截器步骤:

  1. 创建拦截器类,实现 HandlerInterceptor 接口
package com.vsneko.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor1 implements HandlerInterceptor {

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}


public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

}

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}
}
  1. 在 spring-mvc.xml 配置拦截器
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.vsneko.interceptor.MyInterceptor1" />
</mvc:interceptor>
</mvc:interceptors>
  1. 此时已经对相关方法进行拦截

拦截器方法说明

方法名
preHandle() 方法将在请求处理之前调用,该方法的返回值是 Boolean 类型的,返回 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会执行;返回 true 时,会继续调用下一个 Interceptor 的 preHandle 方法。
postHandle() 该方法是在当前请求进行处理之后被调用,前提是 preHandle 方法的返回值为 true,而且它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。
afterCompletion() 该方法将在整个请求结束之后,也就是 DispatcherServlet 渲染了对应的视图之后执行,前提是 preHandle 方法的返回值是 true。

Spring MVC 异常处理

异常处理的两种方式

  • 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver
  • 使用 Spring 的异常处理接口 HandlerExceptionResolver 自定义自己的异常处理器。

简单异常处理器

Spring MVC 已经定义好了 SimpleMappingExceptionResolver 类型的转换器,在使用时可以根据项目情况进行相应异常与视图的映射配置。

<!--配置简单异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="/error.jsp" />
<property name="exceptionMappings">
<map>
<entry key="java.lang.ClassCastException" value="/error.jsp" />
<entry key="java.lang.NullPointerException" value="/error.jsp" />
</map>
</property>
</bean>

自定义异常处理器

  1. 创建异常处理类,实现 HandlerExceptionResolver
package com.vsneko.resolver;

import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyExceptionResolver implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
if (e instanceof ClassCastException) {
modelAndView.addObject("info", "类转换异常");
}
modelAndView.setViewName("/error.jsp");
return modelAndView;
}
}
  1. 配置异常处理器
<!--自定义异常处理器-->
<bean class="com.vsneko.resolver.MyExceptionResolver" />