0%

SpringMVC一览

特点

  • Spring家庭原生产品,与IOC容器等基础设施无缝对接
  • 基于原生Servlet,通过了功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理
  • 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
  • 代码清新简洁,大幅度提升开发效率
  • 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
  • 性能卓著,尤其适合现代大型、超大型互联网项目要求

一次开发配置过程案例

1、导入依赖包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring和thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.15.RELEASE</version>
</dependency>

2、配置web.xml

  1. 默认配置作用下,SpringMVC的配置文件默认位于WEB-INF下,默认名称为-servlet.xml,如SpringMVC-servlet.xml,为便于配置文件使用,可使用init-param标签进行配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <web-app>
    <!-- 配置前端控制器 -->
    <servlet>
    <servlet-name>SpringMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 配置SpringMVC配置文件位置和名称 -->
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <!-- classpath表示类路径 -->
    <param-value>classpath:springMVC.xml</param-value>
    </init-param>
    <!-- 设置该Servlet不要使用默认的在第一次访问时初始化,而是提前到服务器启动时 -->
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>SpringMVC</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    </web-app>

3、创建请求控制器

1
2
3
4
5
6
7
8
@Controller
public class HelloController {
@RequestMapping("/")
public String hello(){
// 返回视图名称
return "index";
}
}

4、创建SpringMVC配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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.zephon.mvc"/>

<!-- 配置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>
</beans>

@RequestMapping注解

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

注解的位置

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

例如:在一个类上注解为@RequestMapping("/test"),在这个类中某个方法上注解为@RequestMapping("/abc"),则其对应的请求路径就是:/test/abc

注解的属性

value属性

  • value属性表示根据请求地址进行匹配
  • value属性是一个字符串类型的数组,表示该请求映射能匹配多个请求地址对应的请求,例如:
    1
    2
    3
    4
    @RequestMapping(
    value = {"/abc", "test"}
    )
    public String test(){}
    表示test()方法同时对应"/abc"请求和"/test"请求

method属性

  • 通过请求方式匹配请求映射

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

    1
    2
    3
    4
    5
    @RequestMapping(
    value = {"/test"},
    method = {RequestMethod.GET, RequestMethod.POST}
    )
    public String test(){}
    表示test()方法对应的请求地址是"/test"并且请求方式为GET或POST的请求

  • 若当前请求的请求地址满足映射的value属性,但请求方式不满足method属性,则浏览器报错405 > 注:对于处理指定请求方式的控制器,SpringMVC提供了@RequestMapping的派生注解: > 处理GET请求的:@GetMapping > 处理POST请求的:@PostMapping > 处理PUT请求的:@PutMapping > 处理DELETE请求的:@DeleteMapping > 但目前浏览器只支持get和post,如果在form表单提交时,为method属性设置了其它请求方式的字符串(put或delete),则按照默认的请求方式get处理 > 如果要发送put或delete请求,则需要通过Spring提供的过滤器HiddenHttpMethodFilter

params属性

  • params属性通过请求的参数匹配请求映射
  • params属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系:
    • "param":要求请求映射所匹配的请求必须携带param请求参数
    • "!param":要求请求映射所匹配的请求必须不能携带param请求参数
    • "param=value":要求请求映射所匹配的请求必须携带param请求参数且param=value
    • "param!=value":要求请求映射所匹配的请求必须携带param请求参数但是param!=value

如:

1
2
3
4
@RequestMapping(value = "/target", params = {
"username","password!=123456"
})
public String target(){}
表示请求中必须包含username参数并且password参数值不为123456

headers属性

  • headers属性通过请求的请求头信息匹配请求映射
  • headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系
    • "header":要求请求映射所匹配的请求必须携带header请求头信息
    • "!header":要求请求映射所匹配的请求必须不能携带header请求头信息
    • "header=value":要求请求映射所匹配的请求必须携带header请求头信息且header=value
    • "header!=value":要求请求映射所匹配的请求携带header请求头信息且header!=value
  • 若当前请求满足@RequestMapping注解的value和method属性,但不满足headers属性,此时页面显示404错误,即资源未找到

例:

1
2
3
4
5
6
@RequestMapping(value = "/target", headers = {
"Host=localhost:8080"
})
public String target(){
return "target";
}
表示请求头中必须有Host并且值为"localhost:8080"

SpringMVC支持ant风格的路径

  • ?:表示任意单个字符
  • *:表示任意0个或多个字符
  • :表示任意的一层或多层目录 > 注:在使用时,只能使用/**/xxx的方式

SpringMVC支持路径中占位符

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

例:

1
2
@RequestMapping("/test/{id}/{username}")
public String target(@PathVariable("id") String id, @PathVariable("username")String username){

SpringMVC获取请求参数

通过servlet API获取

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

1
2
3
4
@RequestMapping("/test")
public String test(HttpServletRequest req){
String username = req.getParameter("username");
}

通过控制器方法的形参获取请求参数

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

1
2
3
4
5
@RequestMapping("/test")
// 如果请求参数中出现多个同名的请求参数,可以在形参处设置字符串类型或字符串数组接收此参数
// 如果使用的是字符串数组,则数组中每个元素对应每个数据
// 如果使用的是字符串,则参数值是每个数据中间使用逗号拼接的结果
public String test(String username, String password, String[] hobby){}

@RequestParam

@RequestParam用在参数上,表示处理请求参数与控制器方法上的形参的映射关系,例如请求参数是user_name,形参名为username,则:

1
2
@RequestMapping("/test")
public String test(@RequestParam("user_name") String username){
其它属性:

  • name:等价于value,即映射关系对应的名称
  • required:表示是否必须有,如果为true,则表示对应的请求参数必须有传输,否则就报错400,默认为true
  • defaultValue:表示默认值,即当请求参数没有传输时对应的参数值或者传输的请求参数值为空值"" ## @RequestHeader

@RequestHeader是将请求头信息和控制器方法的形参创建映射关系 对应的属性也是value、required和defaultValue 例:

1
2
@RequestMapping("/test")
public String test(@RequestHeader("Host") String host){
## @CookieValue

@CookieValue是将cookie数据和控制器方法的形参创建映射关系 对应的属性也是value、required和defaultValue 例:

1
2
@RequestMapping("/test")
public String test(@CookieValue("JSESSIONID") String jsessionId){}

通过POJO获取请求参数

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

1
2
@RequestMapping("/test")
public String test(User user){}

解决获取请求参数的乱码问题

使用CharacterEncodingFilter处理请求参数乱码问题: GET请求不会存在乱码问题,因为URI编码默认是"UTF-8" 方法: 在web.xml中配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 配置过滤器处理乱码问题 -->
<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>

域对象共享数据

使用ServletAPI向request域对象共享数据

1
2
@RequestMapping("/test")
public String test(HttpServletRequest request){}

使用ModelAndView向request域对象共享数据

SpringMVC建议使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("/test")
public ModelAndView test(){
/**
* ModelAndView有Model和View功能
* Model主要用于向请求域共享数据
* View主要用于设置视图,实现页面跳转
*/
ModelAndView mav = new ModelAndView();
// 向请求域共享数据
mav.addObject("key", "value");
// 设置视图,实现页面跳转
mav.setViewName("success");
return mav;
}

使用Model向request域对象共享数据

1
2
3
4
5
@RequestMapping("/test")
public String test(Model model){
model.addAttribute("key", "value");
return "success";
}

使用Map向request域对象共享数据

1
2
3
4
5
@RequestMapping("/test")
public String test(Map<String, Object> map){
map.put("key", "value");
return "success";
}

使用ModelMap向request域对象共享数据

1
2
3
4
5
@RequestMapping("/test")
public String test(ModelMap modelMap){
modelMap.put("key", "value");
return "success";
}

Model、ModelMap、Map的关系

Model、ModelMap、Map类型的参数其实本质上都是BindingAwareModelMap类型的

1
2
3
4
public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}

向Session域共享数据

1
2
3
4
5
@RequestMapping("/test")
public String test(HttpSession session){
session.setAttribute("key", "value");
return "success";
}

向application域共享数据

1
2
3
4
5
6
@RequestMapping("/test")
public String test(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("key", "value");
return "success";
}

SpringMVC的视图

SpringMVC中的视图是View接口,视图的作用是渲染数据,将模型的Model中的数据展示给用户 SpringMVC视图的种类很多,默认有转发视图和重定向视图 当工程引入jstl的依赖,转发视图会自动转换为JstlView 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是ThymeleafView

ThymeleafView

当控制器方法所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转

1
2
3
4
@RequestMapping("/test")
public String test(){
return "success";
}

转发视图

SpringMVC中默认的转发视图是InternalResourceView 当控制器方法中所设置的视图名称以"forward:"为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转

1
2
3
4
@RequestMapping("/test")
public String test(){
return "forward:/success";
}

重定向视图

SpringMVC中默认的重定向视图是RedirectView 当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件所配置的视图解析器解析 ,而是会将前缀"redirect:"去掉,剩余部分作为最终路径通过重定向的方式实现跳转

1
2
3
4
@RequestMapping("/test")
public String test(){
return "redirect:/success";
}
> 注:重定向视图在解析时,会先将redirect:前缀去掉,然后会判断剩余部分是否以/开头,若是则会自动拼接上下文路径

视图控制器view-controller

当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示

1
2
3
<!-- path:设置处理的请求地址 -->
<!-- view-name:设置请求地址所对应的视图名称-->
<mvc:view-controller path="/test" view-name="success"/>
> 注:当SpringMVC中设置任何一个View-Controller时,其它控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签

RESTFul

REST:Representational State Transfer,表现层资源状态转移

RESTFul的实现

REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为URL地址的一部分,以保证整体风格的一致性。

操作 传统方式 REST风格
查询 getUserById?id=1 user/1 --> get请求方式
保存 saveUser user --> post请求方式
删除 deleteUser?id=1 user/1 -->delete请求方式
更新 updateUser user --> put请求方式

HiddenHttpMethodFilter处理PUT和DELETE

  1. 如果要用前端form提交PUT或DELETE请求,则需要将方法设置为POST,并且添加:

    1
    2
    <form th:action="@{target}" method="post">
    <input hidden name="_method" value="PUT">

  2. web.xml中需要配置过滤器(为防止乱码,要将过滤器放在CharacterEncodingFilter下面)

    1
    2
    3
    4
    5
    6
    7
    8
    <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配置文件中配置:

1
<mvc:default-servlet-handler/>

HttpMessageConverter

HttpMessageConverter,报文信息转换器,将请求报文转换成Java对象,或将Java对象转换成响应报文 HttpMessageConverter提供了两个注解和两个类型:@RequestBody,@ResponseBody,RequestEntity,ResponseEntity

@RequestBody

@RequestBody可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识 ,当前请求请求体就会为当前注解所标识的形参赋值

1
2
3
4
5
6
@RequestMapping("/test")
public String test(@RequestBody String body){
System.out.println(body);
}
// 输出前端提交的数据,不同参数之间用&隔开
// 如:username=root&password=123456

RequestEntity

RequestEntity封闭请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息

1
2
3
4
5
6
@RequestMapping("/test")
public String test(RequestEntity<String> requestEntity){
System.out.println(requestEntity.getHeaders());
System.out.println(requestEntity.getBody());
return "target";
}

@ResponseBody

@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器

1
2
3
@RequestMapping("/test")
@ResponseBody
public String test(){}

处理json

  1. 导入jackson的依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
    </dependency>

  2. 在SpringMVC配置文件中开启MVC注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串

    1
    <mvc:annotation-driven/>

  3. 在处理器方法上使用@ResponseBody注解进行标识

  4. 将Java对象直接作为控制器方法的返回值返回,就会自动转换成Json格式的字符串

    1
    2
    3
    4
    5
    6
    @RequestMapping("/test")
    @ResponseBody
    public User test(){
    // 返回视图名称
    return new User("用户", "123456");
    }
    ## @RestController

@RestController注解是SpringMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并为其中的每个方法添加了@ResponseBody注解

ResponseEntity

ResponseEntity用于控制器的返回值类型,该控制器方法的返回值就是响应浏览器的响应报文 作用:文件下载

文件下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping("/download")
public ResponseEntity<byte[]> download(HttpSession session) throws IOException {
ServletContext context = session.getServletContext();
String path = context.getRealPath("/static/img/1.jpg");
InputStream is = Files.newInputStream(Paths.get(path));
byte[] buf = new byte[is.available()];
is.read(buf);
// 创建HttpHeaders对象响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
// 设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=1.jpg");
HttpStatus statusCode = HttpStatus.OK;
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(buf, headers, statusCode);
is.close();
return responseEntity;
}

文件上传

1
2
3
4
<form th:action="@{/upload}" method="post" enctype="multipart/form-data">
头像:<input type="file" name="photo"><br/>
<input type="submit" value="上传">
</form>

导入依赖:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
配置SpringMVC配置文件:
1
2
<!-- 配置文件上传解析器,将上传的文件封装为MultipartFile,id必须是multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
编写文件上传类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RequestMapping("/upload")
public String upload(MultipartFile photo, HttpSession session) throws IOException {
String filename = photo.getOriginalFilename();
// 防止文件名冲突
String suffix = filename.substring(filename.lastIndexOf("."));
// UUID作为文件名
String uuid = UUID.randomUUID().toString();
filename = uuid + suffix;

// 获取文件目录
String path = session.getServletContext().getRealPath("/static/files");
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
photo.transferTo(new File(path + File.separator + filename));
return "target";
}

拦截器

拦截器配置

  • 拦截器用于拦截控制器方法的执行

  • 拦截器需要实现HandlerInterceptor或者继承HandlerInterceptorAdaptor类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Component
    public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("preHandle");
    return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("postHandler");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("afterCompletion");
    }
    }

  • 拦截器必须在SpringMVC的配置文件中进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 配置默认拦截所有请求 -->
<!-- 通过bean配置 -->
<!-- <bean class="com.zephon.mvc.interceptor.MyInterceptor"/>-->
<!-- 通过ref引用已有的bean -->
<!-- <ref bean="myInterceptor"/>-->
<!-- 指定拦截规则 -->
<mvc:interceptor>
<!-- /*:表示/下的一层目录;/**表示所有请求 -->
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/test"/>
<ref bean="myInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

拦截器的三个抽象方法

  • preHandle:控制器执行之前执行,返回true表示放行,返回false表示拦截
  • postHandle:控制器执行之后执行
  • afterCompletion:视图渲染之后执行

多个拦截器的执行顺序

  • 若每个拦截器的preHandle()都返回true,此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件和配置顺序有关
    • preHandle()会按照配置的顺序执行,而postHandle()和afterCompletion()会按照配置的反序执行
  • 若某个拦截器的preHandle()返回了false,则preHandle()返回false和它之前的拦截器的preHandle()会执行postHandle()都不执行,返回false的拦截器之前的拦截器的afterCompletion()会执行
  • 执行顺序按配置顺序进行,和多个过滤器类似:
    • A->preHandle
    • B->preHandle
    • B->postHandle
    • A->postHandle
    • B->afterCompletion
    • A->afterCompletion

异常处理器

基于配置的异常处理

SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver,使用方式:

1
2
3
4
5
6
7
8
9
10
11
<!-- 异常处理配置 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!-- 配置异常映射,当获取key对应异常时,跳转值对应视图页面 -->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!-- 使用Thymeleaf时将异常信息共享在请求域中,key为ex,值为异常信息 -->
<property name="exceptionAttribute" value="ex"/>
</bean>

基于注解的异常处理

1
2
3
4
5
6
7
8
9
10
// @ControllerAdvice将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController {
// @ExceptionHandler表示用于设置所标识方法要处理的异常,可以对应多个
@ExceptionHandler({ArithmeticException.class})
public String handlerArithmeticException(Exception e, Model model){
model.addAttribute("ex", e);
return "error";
}
}

注解配置SpringMVC

使用配置类和注解代替web.xml和SpringMVC配置文件的功能

1、创建初始化类代替web.xml

在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到就用它来配置Servlet容器。Spring中提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为AbstractAnnotationConfigDispatcherServletInitializer,当自定义类继承了AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器时,容器会自动发现它,并用来配置Servlet上下文

  1. 编写类继承AbstractAnnotationConfigDispatcherServletInitializer代替web.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    // 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};
    }

    /**
    * 指定DispatcherServlet的映射规则,即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};
    }
    }

2、配置SpringMVC配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
* 代替SpringMVC配置文件,可配置内容:
* 1. 扫描组件 2. 视图解析器 3. view-controller 4. default-servlet-handler(接口:WebMvcConfigurer)
* 5. mvc注解驱动 6. 文件上传解析器 7. 异常处理 8. 拦截器(接口:WebMvcConfigurer)
*/
@Configuration
@ComponentScan("com.zephon.mvc.controller") // 1. 扫描组件
@EnableWebMvc // 5. 开启MVC注解驱动
public class WebConfig implements WebMvcConfigurer {
/**
* 2. 配置视图解析器-配置生成模板解析器
*
* @return
*/
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}

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

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

/**
* 3. view-controller
*
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("hello");
}

/**
* 4. 配置default-servlet-handler(静态资源访问)
*
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

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

/**
* 7. 异常处理 (或者直接配置成bean)
* @param resolvers
*/
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("java.lang.ArithmeticException", "error");
simpleMappingExceptionResolver.setExceptionMappings(mappings);
simpleMappingExceptionResolver.setExceptionAttribute("ex");
resolvers.add(simpleMappingExceptionResolver);
}

/**
* 8. 配置拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/test");
}
}

SprinMVC执行流程

SpringMVC常用组件

  • DispatcherServlet:前端控制器,不需要工程师开发,由框架提供
    • 作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
  • HandleMapping:处理器映射器,不需要工程师开发,由框架提供
    • 作用:根据请求的url、method等信息查找Handler,即控制器方法
  • Handler:处理器,需要工程师开发(实际就是控制器Controller)
    • 作用:在DispatchServlet的控制下Handler对具体用户请求进行处理
  • HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供
    • 作用:通过HandlerAdapter对处理器(控制器方法)进行执行
  • ViewResolver:视图解析器,不需要工程师开发,由框架提供
    • 作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView
  • View:视图,不需要工程师开发,由框架或视图技术提供
    • 作用:将模型数据通过页面展示给用户

SpringMVC的执行流程

  1. 用户向服务器发送请求,请求被SpringMVC前端控制器DispatcherServlet捕获
  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
    1. 不存在:
      1. 再判断是否配置了mvc:default-servlet-handler,
        1. 如果没配置,则控制台报映射查找不到,浏览器展示404错误;
        2. 如果有配置,则访问目标资源(一般为静态资源 ,如:JS、CSS、HTML),找不到客户端也会展示404错误
    2. 存在:
      1. 根据该URI,调用HandlerMapping获得该Handler配置的所有相关对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回
      2. DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter
      3. 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(...)方法【正向】
      4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据配置,Spring将做一些额外的工作:
        1. HttpMessageConverter:将请求消息(如JSON、xml等数据)转换成一个对象,将对象转换为指定的响应信息
        2. 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
        3. 数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等
        4. 数据验证:验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
      5. Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象
      6. 此时将开始执行拦截器的postHandler(...)方法【逆向】
      7. 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View来渲染视图。
      8. 渲染视图完毕,执行拦截器的afterCompletion(..)方法【逆向】
      9. 将渲染结果返回给客户端