SpringMVC简介
⭐简单介绍一下SpringMVC?
- 框架技术层面:SpringMVC是一个基于Java的Web应用开发框架,也是Spring框架的一部分,SpringMVC提供了一个MVC模式来协调用户请求和应用程序的响应,其中提供了丰富的功能执行请求参数绑定,数据验证.国际化,文件上传等常简的web开发需求.
- MVC模式层面:在SpringMVC中,模型model表示应用程序的数据和业务逻辑,视图view负责展示数据给用户,控制器Controller用于处理用户请求并协调模型和视图之间的交互.
- 核心功能层面:SpringMVC的核心是DispatcherServlet,它是前端控制器,用于拦截所有客户请求并分发给对应的处理器handler.处理器执行业务逻辑并产生模型数据,然后将其传递给视图渲染,最终生成响应返回给客户端.
⭐SpringMVC如何快速上手?
- 在pom文件中导入依赖,在配置DispatcherServlet的时候会报一个版本不兼容的错误,实际上不影响功能,但是影响观感,这时候加上javax.servlet-api的依赖就能解决
- 在web.xml中配置DispatcherServlet,并指定spring-mvc文件地址,以及DispatcherServlet的映射范围.
- 编写controller,执行逻辑产生数据并返回视图渲染生成响应,需要注意的是SpringMVC默认返回String字符串为视图名,如果找不到则会报500
- 500异常
导入SpringMVCpom依赖
<packaging>war</packaging>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.7</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
配置DispatcherServlet
<!--配置DispatcherServlet-->
<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>
⭐DispatcherServlet配置解析
⭐配置映射范围的目的
由于SpringMVC的需要拦截web应用程序中客户端的所有请求,所以DispatcherServlet的映射范围是/表示全部拦截,但是这样导致了静态资源无法访问的问题,原因是在Tomcat的web.xml文件中配置了一个DefaultServlet用于处理访问静态资源,映射范围是/,被DispatcherServlet覆盖了,可以重新配置DefaultServlet的映射范围,也可以在spring-mvc中用resource标签指定静态资源地址,也可以使用mvc注解驱动标签向SpringMVC容器中注册一个DefaultHandlerServlet
⭐指定spring-mvc文件的目的
- 定义支持不同类型的处理器映射器和处理器适配器,例如基于XML方式的处理器和基于注解方式的处理器,还可以配置视图解析器解析实际视图对象和魔板页面以供DispatcherServlet渲染,值得注意的是在不配置任何spring-mvc文件的情况下,DispatcherServlet会调用初始化默认策略方法来加载spring-mvc包下的DispatcherServlet.properties文件中的默认组件
- 设置消息转换器,用于将请求数据和响应数据转换为合适的格式方便交互,比如Javabean对象和Json格式字符串之间的转换
- 配置拦截器,用于在请求处理前后做一些额外的操作,例如权限验证,日志记录,跨域处理等等
- 定义异常处理器,可以统一处理web应用程序中的异常,返回错误页面或错误信息
⭐500异常是什么,产生的原因以及如何解决
500异常是指在服务器端发生了意外的内部错误,导致无法完成客户端的请求.它是一种http状态码,表示服务器无法处理请求
500异常产生的原因有几种:
- 代码层面:服务器端的代码中存在bug或者逻辑错误,导致web程序在执行过程中发生异常
- 配置文件层面:服务器的配置文件出现错误,比如数据库连接配置错误,文件路径配置错误等等
- 外部资源层面:服务器依赖的外部资源如数据库等不可用或者无法访问
- 内存溢出:服务器端的内存不足,无法处理请求
解决500异常通常
查看日志文件:通过查看服务器端的日志文件提供的错误信息寻找错误源,比如代码bug,配置文件是否正确,依赖的外部资源是否可以访问,服务器内存是否不足等
⭐SpringMVC执行流程
客户端发出HTTP请求,被DispatcherServlet拦截后转发给对应的处理器映射器,处理器映射器会根据请求来匹配映射路径对应的Handler处理器(Handler处理器代表了一个处理请求的方法,通常是一个带有@RequestMapping注解的方法,用于处理特定的请求,是对一个方法的抽象),并返回处理器执行链对象HandlerExecutionChain,其中包含了一个Handler处理器和一系列拦截器对象(拦截器处理器作用在处理器执行前后或渲染视图时进行一些额外的操作),DispatcherServlet接收到HandlerExecutionChain后会给Handler匹配一个对应的HandlerAdapter处理器适配器,将不同的处理器适配成一个统一的处理器接口,并负责调用Handler处理器的处理方法来处理实际请求,执行业务逻辑并返回数据给适配器,同时还会触发已经注册的拦截器的预处理和后处理方法进行额外的操作,最后将产的解惑数据返回给DispatcherServlet,拿到数据之后DispatcherServlet根据视图信息调用ViewResolver视图解析器解析视图并返回视图对象,最终由DispatcherServlet产生Http响应返回给回客户端
SpringMVC的请求处理
客户端请求映射路径的配置的目的
配置客户端请求映射路径是为了定义web应用程序中不同URL路径与对应处理器Handler之间的关系,有以下几点:
- 组织和管理请求处理:客户端请求映射路径允许将不同的url路径映射到不同的处理器上,这样就可以按照特定的规则分发到相应的处理器上,实现细腻的请求处理
- 支持RESTful风格的URL:RESTful风格的URL表达了资源与资源之间的关系,通过路径的不同部分来表示不同的资源操作,通过配置请求映射路径,定义不同的URL模式和路径变量,那么web应用程序得以支持RESTful风格的URL结构
- 实现重定向和转发:客户端请求映射路径不仅可以将请求映射到对应的Handler处理器上还可以将某个请求重定向到另一个URL上
@getmapping只是对@requestmapping的一个封装
⭐客户端请求数据的呈现方式的区别以及如何接受
GET请求
- 数据传递方式:GET请求将请求参数附在URL的末尾,以键值对的形式发送给服务器
- 数据长度显示:GET请求对发送的数据长度有限制,一般不超过浏览器或服务器的最大限制.由于GET请求的数据是暴露在URl中的,容易收到URL长度和安全性限制
- 数据缓存:GET请求的响应可以被缓存,通过缓存来提高性能
- 使用场景:GET请求通常在URL中传递少量的非敏感数据,如搜索查询,获取资源等等
POST请求
- 数据传递方式:POST请求将请求参数放在请求体中,通过HTTP报文的body部分发送的服务器端,不会暴露在URL中
- 数据长度限制:理论上来说POST请求没有数据长度限制,可以发送大量数据.实际上收到服务器和网络环境的影响会有些限制
- 数据缓存:POST请求的响应默认不能被缓存,因为POST请求可能对资源进行更改操作
- 使用场景:POST请求适用于需要传递大量数据,敏感数据以及需要对服务器资源修改或提交的场景,例如表单提交和文件上传
//http://localhost:8080/param6?username=zhangsan&age=18
@GetMapping("/param1")
public String param1(String username, Integer age){
return "/index.jsp";
}
@RequestParam @RequestBody @PathVariable三者区别和应用
区别
- @RequestParam用于接受url地址传参或者表单传参
- @RequestBody用于接受json数据
- @PathVariable用于接受路径参数,使用{参数名称}描述路径参数
应用
- 后期开发中,发送请求参数超过1个时一般以json格式为主,@RequestBody应用广泛
- 如果是非json格式,则使用@RequestParam
- 采用RESTful进行开发时,参数量较少时使用@PathVariable,其余使用@RequestBody
- 接受多个参数时,springmvc默认会创建对象接受,这时候可以使用数组但不能使用集合,因为集合是创建不了对象的,需要使用@RequestParam指定springmvc只是接受参数不需要创建对象@RequestParam List<String> hobby
springmvc默认接受参数方式
SpringMVC提供了直接将请求中的数据转化为Javabean对象的功能.当请求达到控制器方法时,SpringMVC利用数据绑定机制,自动进行数据绑定.将请求中的参数与JavaBean的属性进行匹配和自动转化,前提是请求中的参数名和JavaBean的属性名一致
//http://localhost:8080/param6?username=zhangsan&age=18&hobbies=zq&
//hobbies=lq&birthday=2002/11/11&address.city=chengdu&address.area=zhongguo
@GetMapping("/param6")
public String param6(User user){
System.out.println("user = " + user);
return "/index.jsp";
}
public class User {
private String username;
private Integer age;
private String[] hobbies;
private Date birthday;
private Address address;
}
public class Address {
private String city;
private String area;
}
JSON格式转换器
- 技术依赖:SpringMVC的JSON\格式转换器的底层技术默认使用的是Jackson库,所以需要引入jackson-databind依赖
- 指定JSON格式转换器:可以在配置文件中手动配置RequestMappingHandlerAdapter,其中有个属性messageConverters,是一个list集合,然后指定MappingJackson2HttpMessageConverter组件,即Jackson库的JSON格式转化器,之后请求体中的数据就可以直接转化为JavaBean.或者配置SpringMVC的注解驱动来帮我们注册一个JSON格式转换器组件.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
@GetMapping("/param8")
public String param8(@RequestBody String body) throws JsonProcessingException {
//使用jackson进行转换,将json格式的字符串转化成User对象
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(body, User.class);
System.out.println("user = " + user);
return "/index.jsp";
}
<!--配置HandlerAdapter-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<!--指定一个json格式转换的转换器-->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></bean>
</list>
</property>
</bean>
Restful风格
1.1. REST风格简介
REST表现形式状态转换,是一种资源描述风格
RESTful指的是按照REST风格来访问资源
基于HTTP,URL,XML,JSON等标准和协议,支持轻量级,跨平台,跨语言的架构的架构设计,它强调了一组约束和原则,以实现高度可扩展,可靠,可维护,灵活的系统.
1.2. REST风格和传统对比
- 传统资源描述形式:
- REST风格描述形式:
REST风格的优点:
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
1.3. REST风格访问资源
按照REST风格访问资源时使用请求方式区分对资源进行何种操作
REST风格是约定而不是规范,是可以打破的
描述模块的名称通常使用负数,也就是加s的格式描述而非单个资源,例如:users、books
1.4. REST风格返回结果
用HTTP相应状态码表示结果,包括三部分:状态码,状态信息,响应数据,对应的响应实体类为Result
1.5. REST风格设定http请求动作
1.6. REST风格设定请求参数
@PathVariable,形参注解,定义在控制器方法形参前,作用是绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一致
⭐接受上传的文件
文件上传form表单的要求
- 表单提交的方式必须是POST
- 表单的enctype属性必须是multipart/form-data,否则表示为普通数据而不是文件
- 文件上传必须有name属性且接受的参数要与之对应
springmvc接受文件数据的要求
@PostMapping("/param10")
public String param10(@RequestBody MultipartFile myFile) {
System.out.println("myFile = " + myFile);
return "/index.jsp";
}
- 既然是POST请求方式,那么文件就存在于请求体,需要@RequestBody标识接受POST数据
- 接受的文件类必须为MultipartFile
- 默认情况下springmvc的文件上传解析器是不开启的,需要手动去配置
配置文件上传解析器的要求
<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/><!--文件的编码格式 默认是ISO8859-1-->
<property name="maxUploadSize" value="3145728"/><!--上传文件的总大小-->
<property name="maxInMemorySize" value="1038576"/><!--上传文件的缓存大小-->
<property name="maxUploadSizePerFile" value="1038576"/><!--上传的每个文件限制大小 单位字节-->
</bean>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
- 配置的时候id名必须叫multipartResolver
- 因为CommonsMultipartResolver底层使用的是Apache工具API的文件上传,所以需要引入依赖
文件上传代码实现
@PostMapping("/param10")
public String param10(@RequestBody MultipartFile[] myFile) throws IOException {
//将上传的文件进行保存
Arrays.stream(myFile).forEach((file)->{
try {
//1.获得当前上传的文件的输入流
InputStream inputStream = file.getInputStream();
//2.获得上传文件位置的输出流
OutputStream outputStream = new FileOutputStream("D:\\"+file.getOriginalFilename());
//3.执行文件拷贝
IOUtils.copy(inputStream,outputStream);
//4.关闭流资源
inputStream.close();
outputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
return "/index.jsp";
}
获得header头信息和cookie信息
请求头信息一般是客户端自己携带的头信息,服务端可以通过头名字获取头信息
@RequestHeader用于指定头信息,没有参数则全部获取
@CookieValue用于指定cookie信息
@GetMapping("/param13")
public String param13(@CookieValue("JSESSIONID") String cookie) {
System.out.println(cookie);
return "/index.jsp";
}
@GetMapping("/param12")
public String param12(@RequestHeader Map headerValue) {
headerValue.forEach((k,v)->{
System.out.println(k+"==>"+v);
});
return "/index.jsp";
}
@GetMapping("/param11")
public String param11(@RequestHeader("Accept-Encoding") String headerValue) {
System.out.println(headerValue);
return "/index.jsp";
}
获取Request和Session域中的数据
@RequestAttribute和@SessionAttribute直接获取
@GetMapping("/request2")
public String request2(@RequestAttribute("user") String user) {
//想Request域中存储数据
System.out.println("user = " + user);
return "/index.jsp";
}
@GetMapping("/request1")
public String request1(HttpServletRequest request) {
//想Request域中存储数据
request.setAttribute("user","zhangsan");
return "/request2";
}
Javaweb常用对象获取
Request和Response对象可以在自定义方法参数中直接加入,springmvc会帮我们直接传入,如上代码块
⭐请求静态资源
Tomcat自带的DefaultServlet全局配置,url-pattern是/,当我们访问静态资源时Tomcat会先把静态资源名当做Servlet去访问,但是访问不到,这时候会交给DefaultServlet帮我们解析访问静态资源,但是在springmvc中我们会配置一个前端控制器DispatcherServlet的url-pattern也是/,会覆盖掉原来的DefaultServlet的url-pattern,但是DispatcherServlet没办法解析静态资源,有三种方式可以解决:
- 可以激活Tomcat的DefaultServlet,Servlet的url-pattern的匹配优先级是:精确匹配>目录匹配>扩展名匹配>缺省匹配,所以可以指定某个目录下或某个扩展名的资源使用DefaultServlet进行解析,这样就可以访问静态资源了
<!--扩展名匹配-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<!--目录匹配-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/img/*</url-pattern>
</servlet-mapping>
- 在spring-mvc.xml中去配置静态资源映射,匹配映射路径的请求到指定的位置去配置资源
<!--mapping是映射资源路径,location是对应资源所在位置-->
<!--如果访问的地址是/image/ 就会在img下面找资源-->
<mvc:resources mapping="/img/*" location="/img/"/>
<mvc:resources mapping="/css/*" location="/css/"/>
<mvc:resources mapping="/css/*" location="/js/"/>
<mvc:resources mapping="/html/*" location="/html/"/>
- 在spring-mvc.xml中去配置<mvc:default-servlet-handler/>,该方式是注册了一个DefaultServletHttpRequestHandler处理器,静态资源都由该处理器去处理,开发中运用的最多
在配置了<mvc:default-servlet-handler>或者<mvc:resources mapping="/img/*" location="/img/">之后,我们会访问不到controller映射地址,究其原因是,在自定义命名空间处理器的标签解析器parse方法中向容器注入了一个SimpleUrlHandlerMapping,这样spring容器就不会加载默认策略从而帮我们注册RequestMappingHandlerMapping解析@RequestMapping标签的处理器我们饿可以有两种解决方法:
- 手动配置一个RequestMappingHandlerMapping
- 添加<mvc:annotation-driven>,在次标签的解析器parse方法中回想spring容器注册一个RequestMappingHandlerMapping,也会达到同样的效果
注解驱动<mvc:annotation-dirvern>标签
配置
- @RequestMapping正常映射到资源方法中
- 静态资源正常访问
- 将请求中json格式字符串和Javabean之间自由切换
需要以下繁琐的配置
<!--配置handlerMapping-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
<!--配置HandlerAdapter-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<!--指定一个json格式转换的转换器-->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></bean>
</list>
</property>
</bean>
<!--向容器中注册静态资源处理器-->
<mvc:default-servlet-handler/>
但是在<mvc:annotation-dirvern>标签的解析器中向spring容器注册了RequestMappingHandlerAdapter和RequestMappingHandlerMapping等等还有其他的处理器这样我们的配置可以简化为
<!--向容器中注册静态资源处理器-->
<mvc:default-servlet-handler/>
<!--开启注解驱动-->
<mvc:annotation-driven/>
SpringMVC的响应处理
传统同步业务数据响应
转发和重定向
@GetMapping("/show")
public String show(){
System.out.println("show ..."+quickService);
return "forward:/index.jsp";
}
请求转发最后的url还是本次的url
重定向会直接定向到目标资源url
响应模型数据ModelAndView
@GetMapping("/res3")
public ModelAndView res3(ModelAndView modelAndView){
//modelAndView封装模型数据和视图名
//设置模型数据
User user = new User();
user.setUsername("zhangsan");
user.setAge(18);
modelAndView.addObject("user",user);
//设置视图名称
modelAndView.setViewName("/index.jsp");
return modelAndView;
}
@ResponseBody以响应体的方式返回数据
@ResponseBody可以作用在方法上也可以作用在类上
// 直接回写字符串
@GetMapping("/res4")
@ResponseBody //响应体,告诉springmvc返回的字符串不是视图名,是以响应体方式响应的数据
public String res4(){
return "hello springmvc";
}
前后端分离异步业务数据响应
跟同步方法的区别
异步回写数据跟同步方式语法一样不过有些区别:
- 响应对象不同:同步方式回写数据是将数据响应给浏览器进行页面展示的,而异步方式回写数据一般事回写给Ajax引擎的,即谁访问服务器端,就将数据响应给谁
- 字符串格式不同:同步方式一般事无特定格式的字符串,异步方式回写的数据大多是Json格式字符串
返回类型不同,前端响应的类型也不同:
- 返回String字符串,前端响应头的Content-Type是Text
- 返回实体类型,springmvc的<mvc:annotation-dirvern>会注册一个json转换器帮我们转换成json数据,前端响应头的Content-Type是Json
SpringMVC的拦截器
拦截器Interceptor简介
Springmvc的拦截器是一个接口规范,作用是对Controller资源访问进行拦截操作的技术,可以在访问方法资源之前将一些通用的功能执行,做一些增强操作以及权限控制.类似于javaweb的Filter
Filter和Interceptor的区别
- 技术范畴上:Filter属于Javaweb原生技术,而Interceptor是SpringMVC框架技术
- 拦截过滤资源上:Filter对所有的请求都可以过滤,包括Servlet,Jsp,其他资源等,而Interceptor只对进行了SpringMVC管辖范围的才拦截,主要是拦截Controller请求
- 执行时机上:Filter早于任何Servlet执行,而Interceptor晚于DispatcherServlet执行
HandlerInterceptor接口方法的作用机器参数
拦截器快速入门
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor...preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor...postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor...afterCompletion");
}
}
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--对哪些请求路径进行拦截-->
<mvc:mapping path="/**"/>
<bean class="com.heima.interceptors.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
拦截器执行顺序
拦截器的执行顺序就是在xml文件中配置的顺序
内部方法顺序如下:
拦截器执行原理
在DispatherServlet的doDispatch方法中会注册一个HandlerExecutionChain通过RequestMappingHandlerMapping对客户端的请求进行处理,执行哪些拦截器以及要将请求转发给哪个Controller,之后返回HandlerExecutionChain,其中封装了一个Handler目标资源和一个Interceptor集合会依次执行applyPreHandle,handle,applyPostHandle方法,在其中对Interceptor集合进行遍历,执行对应的方法
SpringMVC的全注解开发
替代spring-mvc.xml
spring-mvc.xml文件主要包含了以下部分及其解决方式:
- 组件扫描器
- 非自定义bean(文件上传解析器)
- 功能性Bean的配置(默认Servlet处理器,Interceptor拦截器)
- SpringMVC组件的配置
使用Spring提供的注解可以完成替代spring-mvc.xml的配置:
- @ComponentScan
- @Bean工厂构造方法
@Configuration
@ComponentScan("com.heima.controller")
@EnableWebMvc
public class SpringMVCConfig {
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setDefaultEncoding("UTF-8");
resolver.setMaxUploadSize(3145728);
resolver.setMaxUploadSizePerFile(1038576);
resolver.setMaxInMemorySize(1038576);
return resolver;
}
}
- @EnableWebMvc
- @EnableWebMvc中引入了DelegatingWebMvcConfiguration类,其中setConfigurers方法需要WebMvcConfigurer类型的Bean作为参数,添加了@Autowire配置之后会在容器中找到所有次类型的Bean进行自动注入,当我们配置一个实现该接口的Bean并实现方法时,容器加载到并自动调用其方法,完成添加Interceptor拦截器,配置默认Servlet处理器等指定.
@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
//开启默认servlet解析器
configurer.enable();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加一个拦截器,并配置拦截路径
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**");
}
}
- @EnableWebMvc中引入了DelegatingWebMvcConfiguration类,继承了WebMvcConfigurationSupport,在其父类中使用@Bean工厂构造方法配置了HandlerMapping,HandlerAdapter,ViewResolver
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
/*...*/
return mapping;
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
/*...*/
return adapter;
}
DispatcherServlet加载核心配置类
利用contextClass指定一个继承了AnnotationConfigWebApplicationContext的实现类,在其register方法中奖SpringMVCConfig核心类配置类注册到web容器中,彻底消除SpringMVC.xml文件
消除web.xml
web.xml文件主要包含了以下部分:
- 配置ContextLoaderListener上下文加载监听器
- 配置Spring的核心配置类
- 配置DispatcherServlet前端控制器(SpringMVC核心注解用于代替传统Servlet)
- 配置DispatcherServlet的拦截映射范围(一般为/,因为要将所有的web请求都拦截并转发)
- 配置SpringMVC核心配置类(对SpringMVC的进一步配置,包含拦截器,消息转换器,文件上传解析器等等)
在Servlet3.0环境中,web容器提供了一个javax.servlet.ServletContainerInitializer接口,实现了该接口的实现类可以通过onStarUp方法做一些初始化工作, 该实现类需要配置在类加载路径下的MATA-INF/services目录中名为javax.servlet.ServletContainerInitializer中,指定实现类的全路径名即可配置
Spring定义SpringServletContainerInitializer了实现了该接口,它被@HandlesTypes(WebApplicationInitializer.class)标注,意味着它会将WebApplicationInitializer接口的所有实现类进行处理,调用它们的onStarUp方法来初始化Servlet容器,其中解决了web.xml的配置:
- 在AbstractContextLoaderInitializer实现类以及其继承体系中会完成对ContextLoaderListener的基本配置
- 在AbstractDispatcherServletInitializer中完成DispatcherServlet的基本配置
- 在AbstractAnnotationConfigDispatcherServletInitializer中提供了getRootConfigClasses用于配置Spring的核心配置类
- getServletConfigClasses用于配置SpringMVC的核心配置类
- getServletMappings用于配置DispatcherServlet的映射路径
public class MyAbstractAnnotationConfigDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
// 提供Spring容器的核心配置类
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
// 提供SpringMVC容器的核心配置类
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMVCConfig.class};
}
@Override
// 提供前端控制器的映射路径
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
SpringMVC的组件原理剖析
前端控制器初始化
前端控制器DispatcherServlet是SpringMVC的核心,主流程工作都是在此完成的,它的本质还是一个Servlet,当配置了load-on-starup时,会在容器启动时就执行创建和执行初始化init方法,每次请求都会执行service方法.
DispatcherServlet的初始化主要做了:
1. 初始化SpringMVC容器
在整个DispatcherServlet的继承体系当中,运用了模板设计模式,共用部分父类抽取,扩展部分留给子类实现,整体流程如下
模板设计模式
- GenericServlet实现了Servlet接口的有参init方法,做了通用操作给ServletConfig赋值之后定义了一个空参模板init方法留给子类扩展
- HttpServletBean实现了其二级父类GenericServlet定义的模板init方法做了一些初始化操作之后又定义了一个空参模板initServletBean方法留给子类扩展
- FrameworkServlet实现了其父类HttpServletBean定义的模板initServletBean方法,在initWebApplicationContext方法中中对SpringMVC容器进行了初始化
- initWebApplicationContext方法中创建了SpringMVC容器,并设置上下文环境,Spring容器为其父容器
由于是全注解开发,定义了AbstractAnnotationConfigDispatcherServletInitializer加载配置文件指定了Spring和SpringMVC的核心配置类,其中的rootContext即为Spring容器,this.webApplicationContext即为SpringMVC容器
如果没有指定WebApplicationContext的类型,Spring容器默认实行XmlWebApplicationContext,此时如果没有xml配置文件,则this.webApplicationContext为空,进入到代码createWebApplicationContext中创建一个SpringMVC容器完成后续操作
SpringMVC容器和Spring容器的关系
当SpringMVC容器在获取Bean的时候先会在自己的容器中获取如果获取不到,则会尝试getParent得到Spring容器再进行获取Bean的操作,但是在Spring容器中是没办法获得SpringMVC容器的
2. 注册SpringMVC九大组件
Spring监听机制+模板设计模式
- 创建完SpringMVC容器之后,最终都会调用configureAndRefreshWebApplicationContext方法,在此方法中会调用refresh方法
- refresh方法中执行一系列的SpringBean生命周期过程,并在最后调用finishRefresh方法
- finishRefresh方法中有一个发布事件publishEvent(new ContextRefreshedEvent(this))方法,用于监听Context容器刷新完毕事件,只要设置了对应事件的监听器,在我发布事件代码执行时,监听器中的方法都会被回调,使用了Spring的监听机制
- FrameworkServlet中定义了一个内部类ContextRefreshListener实现了ApplicationListener<ContextRefreshedEvent>监听器,对应的泛型就是要监听的事件,当发布该事件的代码执行之后,接口的onApplicationEvent会被回调
- onApplicationEvent方法中调用了FrameworkServlet定义的同名方法,其中定义了一个有参模版方法onRefresh留给子类扩展
- DispatcherServlet复写了onRefresh方法,调用了initStrategies初始化策略方法
- initStrategies中注册了SpringMVC的九大组件
3. 处理器映射器初始化细节
- initHandlerMappings方法中首先调用beansOfTypeIncludingAncestors,从SpringMVC容器中寻找有没有对应的HandlerMapping对应的Bean,有则直接注入到容器中,否则加载默认策略,从类加载路径下的Dispatcher.properties文件中加载默认处理器映射器
- @EnableWebMvc和<mvc:annotation-driven/>都可以向SpringMVC容器中注册处理器映射器组件
- initHandlerMappings加载完毕之后,就已经将请求映射存储到其中的mappingRegistry中了,对应的值就是Controller对应的方法
前端控制器执行主流程
doDispatcher方法
- Servlet接口中有service方法,在每次调用Servlet时service方法就会被调用
- HttpServlet实现了service方法,在其中将ServletRequest和ServletResponse参数转化为Http形式,并且调用重载的service方法
- FrameworkServlet复写了service方法并调用processRequest,其中又调用doSevice方法,doService方法是一个抽象方法,所以这里实际上调用的是DispatcherServlet实现的doService方法
- DispatcherServlet实现的doService方法中调用了doDispatch方法,在这里完成主要流程
HandlerExecutionChain验证
- doDispatch方法中首先定义了HandlerExecutionChain,其中包含了拦截器集合和目标对象的方法,再会执行到getHandler方法
- getHandler方法中会循环遍历HandlerMapping集合,在DispatcherServlet初始化时@EnableWebMvc已经向容器中注册了4个HandlerMapping类型的Bean了,其中会调用HandlerMapping接口由AbstractHandlerMapping实现的getHandler方法
- AbstractHandlerMapping实现的getHandler方法中首先获得我们访问Controller方法的一个抽象handler对象,再调用getHandlerExecutionChain方法
- 在getHandlerExecutionChain方法中会将配置好的Interceptor拦截器逐一添加到HandlerExecutionChain中,并最终返回该对象
HandlerAdapter执行目标方法
- doDispatch方法中调用getHandlerAdapter方法获取HandlerAdapter
- HandlerAdapter调用handle方法帮我们执行目标方法,HandlerExecutionChain会分别执行Interceptor的前置后置最终方法
- AbstractHandlerMethodAdapte实现HandlerAdapter接口的handle方法,并调用handleInternal抽象方法返回ModelAndView
- RequestMappingHandlerAdapter实现了handleInternal方法,invokeHandlerMethod执行目标方法
SpringMVC的异常处理机制
异常可以分为编译时异常和运行时异常,编译时异常通过使用try-catch进行捕获,捕获后自行处理,而运行是异常时不可预期的,就需要规范编码来避免,在SpringMVC中,不管是编译异常还是运行异常,最终都可以由SpringMVC提供的异常处理器进行统一处理,这样就避免了随时随地捕获处理的繁琐性.
SpringMVC异常的处理流程
SpringMVC的异常处理方法
简单异常处理器
使用SpringMVC内置的异常处理器处理SimpleMappingExceptionResolver,只能进行视图的响应
//配置一个简单的异常处理器
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
//不管是什么异常我都同意响应一个友好页面
simpleMappingExceptionResolver.setDefaultErrorView("/error1.html");
//区分异常类型.根据不同的异常类型,跳转不同的视图
Properties properties = new Properties();//键值对,key:异常对象的全限定名 value:要跳转的视图名
properties.setProperty("java.lang.RuntimeException","/error1.html");
properties.setProperty("java.io.IOException","/error2.html");
simpleMappingExceptionResolver.setExceptionMappings(properties);
return simpleMappingExceptionResolver;
}
自定义异常处理器
实现HandlerExceptionResolver接口,自定义异常进行处理,,可以在捕获异常之后继续写一写对应的逻辑,相比第一种更加的灵活,但是也比较繁琐
@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//简单的响应一个友好的提示页面
/* ModelAndView modelAndView = new ModelAndView();
if (ex instanceof RuntimeException){
modelAndView.setViewName("/error1.html");
}else if (ex instanceof IOException){
modelAndView.setViewName("/error2.html");
}*/
//前后端分离开发,响应json格式的字符串{"code":0,"message":"","data":""}
String resultJson = "{\"code\":0,\"message\":\"\",\"data\":\"\"}";
try {
response.getWriter().write(resultJson);
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
}
注解方式定义异常处理器
只用@ControllerAdvice+@ExceptionHandler处理,注解方式解决异常更加的灵活,既可以返回视图,也可以返回Json格式字符串
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ModelAndView runTimeExceptionResolverMethod(RuntimeException exception){
exception.printStackTrace();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("/error1.html");
return modelAndView;
}
@ExceptionHandler(IOException.class)
@ResponseBody
public Result ioExceptionResolverMethod(IOException exception){
exception.printStackTrace();
Result result = new Result();
result.setCode(300);
result.setMessage(exception.getMessage());
result.setData(exception);
return result;
}
}
异常处理机制原理剖析
在DispatcherServlet接受到底层抛上来的异常时调用doDispatch方法,进行到processDispatchResult方法,在其中调用processHandlerException对异常的处理方法进行判断,若模型视图对象不为空,直接返回视图,否则调用resolveException方法对异常进行处理
SpringMVC常用的异常解析器
在DispatcherServlet的initStrategies中的initHandlerExceptionResolvers方法中在容器启动时会注册一个HandlerExceptionResolverComposite,其中的就包含了3中异常处理器分别是ExceptionHandlerExceptionResolver,DefaultHandlerExceptionResolver,ResponseStatusExceptionResolver
在ExceptionHandlerExceptionResolver的doResolveHandlerMethodException中解析被标注了@ExceptionHandler的方法,帮我们执行,底层是反射