视图解析流程

视图解析的地方主要是从返回值开始的,上一篇已经分析了返回值的处理流程,其中用于处理视图的有下面几种:

1
2
3
String
ModelAndView
View

重定向 redirect

我们以常用的 ViewNameMethodReturnValueHandler 来说(就是处理返回值为 String 的):

先将视图名放到 ModelAndViewContainer 中,然后判断是不是重定向视图(看有没有 forward: 开头),经过各种数据获取之后,将 ModelAndViewContainer 里面的东西抽取并返回一个 ModelAndView 对象。

有了 ModelAndView 对象之后,也就是有了数据和视图名了,就可以去处理派发结果了调用 processDispatchResult() -> render() 来渲染我们要得到的页面:

遍历所有的视图解析器(ViewResolver),通过视图名来找到可以处理当前视图的 ViewResolver,找到之后,就通过 ViewResolver 解析视图名来得到视图 View:

(比如能处理的 redirect:/XXXX 的 ViewResolver 就是 ContentNegotiatingViewResolver 这个解析器。它通过内部的一系列解析之后,返回了一个 RedirectView 对象。

只需要调用 RedirectView 对象的 render() 方法,它就会获取目标的 URL 地址,然后调用 response.sendRedirect(encodeedURL); 来跳转页面。

页面转发 forward

当然还有另外一个常用的,那就是转发到其他的页面的时候,它是使用 InternalResourceView 来完成的,而核心功能还是 Servlet 的提供的原生 request.getRequestDispatcher(path).forward(request,response);

模板页面 字符串

如果是 Thymeleaf 模板引擎的话,就会获得一个 ThymeleafView 来渲染视图,渲染过程和 JSP 差不多:

先拿到 Model 里面的所有数据,然后明确页面的所有设置,剩下的就是在 Writer 输出流中不断的填充数据,输出代码了。

拦截器系统

拦截器系统只需要实现 HandlerInterceptor 接口,具体的原理和过程可以参考原 SpringMVC 的文章。至于将自己实现的 Interceptor 放入 IOC 容器中,还是要依靠重写 WebMvcConfigurer 的配置方法。可以说所有关于 SpringMVC 的配置,都是在这个 WebMvcConfigurer 中的方法完成的。

我们只需要对这个配置类设置 Interceptor 进行重写就好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class Config {
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() { //返回一个修改了某些默认实现的 WebMvcConfigurer 到 IOC 容器中。
@Override //重写添加 Interceptor 的方法
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index.html");
} //注意要拦截的范围
};
}
}

文件上传功能

具体的使用方法还是看 SpringMVC,毕竟用的还是 MVC 的那一套东西,这里就分析一下源码。

关于文件上传的使用,因为 MultipartAutoConfiguration 运行的时候,从 MultipartProperties 中读取并将 StandardServletMultipartResolver 放到了 IOC 中。

当请求过来的时候,会用 StandardServletMultipartResolver 来判断当前请求是否为文件上传请求,依据就是 multipart 这个关键词。

判断成功之后会对请求进行二次包装,变成一个 StandardServletMultipartRequest,之后就是和参数解析流程一样的了,

不同的就是对于 StandardServletMultipartRequest 类型的请求,能解析它的是 RequestPartMethodArgumentResolver 。它会将请求里面的内容封装为 MultipartFile,然后传给处理方法的参数。

异常处理

基本异常响应

SpringBoot 有自动的异常处理机制,只要抛了异常,对于浏览器用户,就会返回错误白页:

325.jpg

对于非浏览器用户,就会返回错误的 Json 数据,因为异常信息太多了,就改成 404 了:

326.jpg

自定义错误页面的话,只需要在静态资源或者模板资源目录下面创造 /error 文件夹,然后将状态码作为响应文件的文件名(可以使用掩码,不确定的用 x 来代替,比如 5xx.html 表示所有 5 开头的状态码)

放了之后一旦出现不正常的响应码,就会去 /error 下面找对应文件名,然后返回页面。

源码分析

这个源码在前面 SpringMVC 就分析过了,不过唯一不同的是当出现所有的 ExceptionResolver 都无法处理的错误的时候,SpringBoot 并不会抛给容器 Tomcat,而是转发请求到 /error 页面,这个页面有一个默认的 BasicErrorController 来处理。

这个 Controller 从请求域拿到各种数据,然后去找 ViewResolver 来处理这些数据,经过遍历之后,获得了一个 DefaultErrorViewResolver 来处理,它唯一干的事情就是根据响应码从 /error 静态页面或者模板页面里面找对应响应码的错误页面。

找到页面之后将视图名(错误页面文件名)封装到 ModelAndView 中,然后返回,开始视图 解析流程。

自定义处理规则

先说一下具体的流程,抛出异常之后就会遍历所有的 Resolver,找到能够解析此异常的解析器。找到解析器之后,会遍历解析器内部的所有 Handler 处理器,依靠这些处理器对异常进行具体的处理。

以解析器 ExceptionHandlerExceptionResolver 来说,自定义其内部处理器方式和规则,和 SpringMVC 是一模一样的。也就是使用注解:@ExceptionHandler 来定义处理的异常类型和处理方法。

而要自定义 Resolver 只需要实现接口 HandlerExceptionResolver 接口,然后加上 @Component 注册 IOC 容器就好了。

原生 Servlet API

对于注册原生的 Servlet Filter Listener 来说,我们有两种方法,一种是基于注解扫描添加,一种是在 IOC 中通过修改 RegistrationBean 的行为来注册 Serlvet。

Servlet

原生 Serlvet 使用方法和以前一样,不同的是自定义 Serlvet 完之后,需要使用 @WebServlet("/path") 来注册 Servlet 和定义映射的路径。然后在 @SpringBootApplication 的启动类中加上注解 @ServletComponentScan("space.xorex") ,来在定义扫描的包路径。


或者使用 ServletRegistrationBean 来注册:

1
2
3
4
5
6
7
8
9
10
@Configuration
public class Config {
@Bean
public ServletRegistrationBean servlet() {
return new ServletRegistrationBean(new Servlet(),"/servlet");
}
@Bean ServletRegistrationBean servlet01() {
return new ServletRegistrationBean(new Servlet01(), "/servlet01");
}
}

Filter

Filter 同样也是,使用 @WebFilter 定义路径,@ServletComponentScan("space.xorex") 定义扫描路径。


还是可以依靠 FilterRegistrationBean 来注册 Filter,格式是和上面的一样的。

Listener

Listener 只需要用 @WebListener 来注册一下,然后 @ServletComponentScan("space.xorex") 来扫描路径。


同样是可以通过 ListenerRegistrationBean 来注册 Listener,格式是和上面一样的。

内嵌服务器

内嵌原理

Tomcat 是 SpringBoot 默认的内嵌服务器,在启动的时候会通过 TomcatServletWebServerFactory 来获取 TomcatWebServer 实例,并启动服务器。

而要修改内嵌的服务器,方式就是通过切换导入的 WebServer 的 Jar 包,来决定加载的服务器类型。如果要讲默认的 Tomcat 修改为 Jetty。应该将默认的 Tomcat 排除配置,然后加入 Jetty 的 starter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

然后启动之后就是使用 Jetty 作为容器和服务器了:

1
2021-08-08 00:26:08.499  INFO 13260 --- [  restartedMain] o.s.b.web.embedded.jetty.JettyWebServer  : Jetty started on port(s) 8080 (http/1.1) with context path '/'

定制 Serlvet 容器

这个主要就是修改 server 下面的配置了。