0%

Spring MVC 设计与实现

本文会对 Spring MVC 设计进行大致的描述,比如 web 服务怎么进行启动、DispatcherServlet 注入 ServletContext 流程、DispatcherServlet 是怎么处理请求,但不会涉及具体的实现细节,更多细节还需读者自行探究。

HandlerMapping

下面是 HandlerMapping 接口的定义,HandlerMapping 提供一个 getHandler 方法,可以 根据请求来获取一个 HandlerExecutionChain 对象。在 HandlerExecutionChain 中包含两个重要行为,一个是请求的拦截器(HandlerInterceptor),另外一个是请求 Handler。因为我们在配置拦截器时可以指定某个路径来进行配置,将拦截器放在 HandlerExecutionChain 中可以进行解耦的作用。

1
2
3
public interface HandlerMapping { 
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

接下来看到一个基于注解的控制器,我们定义了一个路径为 /hello 路由,客户端通过 GET 请求进行访问时,就向客户端返回 hello world

hello() 方法是请求的 Handler

1
2
3
4
5
6
7
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello wolrd";
}
}

HandlerMapping 则管理着请求的 Handler,在 HandlerMapping#getHandler 需要传入 request 就可以获取该请求的 HandlerExecutionChain。这里不妨思考一下,我们如何去找到与之对应的 Handler 呢?其实可以根据请求的条件来进行查找,在上述代码中,@GetMapping 代表着我们处理 GET 请求,而 /hello 则为请求的路径,如果请求符合这两个条件那么就返回该 Handler。

在 Web Mvc 中我们定义控制器方法有很多种,不同的方法代表着 HandlerMapping 实现不同。

  1. RequestMappingHandlerMapping:注解实现
  2. RouterFunctionMapping: RouterFunction Bean 实现
  3. SimpleUrlHandlerMapping
  4. BeanNameUrlHandlerMapping

DispatcherServlet

DispatcherServlet 作为请求的入口点,用于将请求路由到对应的 Handler上。然而在项目中,我们定义控制器方式有很多种,也就意味着我们可能有多个 HandlerMapping 实现。DispatcherServlet 则管理这些 HandlerMapping ,用于帮助请求找到对应的 Handler 并进行调用。

doDispatch

doDispatch 作为 DispatcherServlet 核心方法,有很多事情都是在该方法中进行完成。首先会根据请求来找到对应的 Handler,如果未找到那么就向客户端返回 404 (未发现)错误。找到则对 Handler 进行适配,得到一个 HandlerAdapter 对象(Handler 所处的 HandlerMapping 不同,HandlerAdapter 实现也不同),之后调用该 HandlerAdapter#handle 方法用于处理该请求,该方法返回 ModelAndView 对象。如果返回是空的 ModelAndView 对象,那么说明我们不需要对该页面进行渲染,反之需要拿到 view 名称交由渲染引擎进行渲染之后返回给客户端。

请求的拦截器操作也是在该方法中完成,分别在 HandlerAdapter#handle 方法调用前后来调用拦截器,如果该拦截器对请求进行处理(preHandler、postHandler 返回false),那么直接 return doDispatch 方法即可。

上述提到 HandlerAdapter 会对 Handler 进行适配(适配器模式),在 HandlerAdapter 中会对 request/response 进行一些增强的操作,比如对 request参数解析、response 消息转换等等,具体的细节还需读者自行探究,此处不在赘述。

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
// org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest); // 找到与之对应的 Handler
if (mappedHandler == null) {
noHandlerFound(processedRequest, response); // 未找到 Handler 返回 404
return;
}

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 对 handler 进行适配

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}

if (!mappedHandler.applyPreHandle(processedRequest, response)) { // 调用前置拦截器
return;
}

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 处理请求

if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

applyDefaultViewName(processedRequest, mv); // mv 不为null,则对页面进行渲染
mappedHandler.applyPostHandle(processedRequest, response, mv); // postHandler 拦截器
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理收尾工作,调用拦截器的 afterCompletion 方法
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

Web服务如何与Spring容器接轨

容器 onRefresh 方法

AbstractApplicationContext#onRefresh 方法给子类提供了一个扩展点,用于去初始化其他 Bean。web服务本质是一个 Spring 容器,所以会去继承 AbstractApplicationContext,并重写 onRefresh 方法,之后在该方法中来启动 web 服务。

启动 web Server

AnnotationConfigServletWebServerApplicationContext 是基于注解实现的 Servlet web 容器

之后我们一起去探究 Servlet Web 服务是如何进行启动的,首先来看到 AnnotationConfigServletWebServerApplicationContext@onRefresh() 方法,在该方法中会去调用 createWebServer() 来创建 Web 服务,我们这里只需关注该方法即可。

首先会去获取本地的 webServer,如果当前 Web 服务并未初始化,那么通过 ServletWebServerFactory 方法传递 ServletContextInitializer 来创建一个 web 服务。之后向 Spring 容器中注入 WebServerGracefulShutdownLifecycle(web容器销毁时收尾工作)、WebServerStartStopLifecycle (用于启动web Server),这两个 Bean 都是依赖 spring 生命周期来进行实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}

Web Server 创建细节我们不进行探究,主要是对 Web Server 启动来进行讲解,在 WebServerStartStopLifecycle 中,该对象实现了 SmartLifecycle 接口,该接口会在spring 容器启动之后进行回调,在这回调期间会将 Web Server 进行启动。Web Server 启动后通过一个标志位(running)标识该 Web Server 已经启动,然后广播一个 ServletWebServerInitializedEvent 事件用于在其它地方做一些额外工作。

1
2
3
4
5
6
7
// org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle#start
public void start() {
this.webServer.start();
this.running = true;
this.applicationContext
.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}

将组件融合到ServletContext中

ServletContextInitializer#onStartup 会在 TomcatStarter#onStartup 进行调用,对于具体实现细节请读者执行探究

在 Web Server 启动时,Spring 还给我们留下了一个扩展点(ServletContextInitializer),该接口会在Web容器启动时进行回调,而我们声明的 Filter、Servlet 组件也是在此时注册到 ServletContext 中。

Filter 、Servlet、Listener 原生组件注册

Servlet 提供 @WebFilter@WebServlet@WebListener 三个注解,用于注册 Servlet 原生组件。通过注解来注册Servlet原生组件需要通过 @ServletComponentScan 注解来扫描指定包,在该注解中会引入一个 ServletComponentScanRegistrar 组件来向Spring 容器注册一个 ServletComponentRegisteringPostProcessor 实例用于,该实例是 BeanFactoryPostProcessor 接口的实现,之后对接口进行回调时,会扫描指定包下组件封装成 *RegistrationBean 注入到 Spring 容器中 。*RegistrationBean 类型的接口可以在生命周期回调时进行组件的注册,如果我们需要基于组件来进行扩展,那么可以参考 *RegistrationBean 实现。

下面通过讲解 Filter 组件注册,来实现举一反三的目的。通过 WebFilterHandler#doHandle 方法将组件封装成一个 FilterRegistrationBean 对象,该对象 是 ServletContextInitializer 接口实现,会在 Web 容器进行初始化时回调该接口,之后通过 ServletContext#addFilter 来添加 Filter 组件。具体细节还请读者自行探究,此处不在赘述。

1
2
3
4
5
6
7
8
9
10
11
//org.springframework.boot.web.servlet.ServletComponentRegisteringPostProcessor#scanPackage
private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan) {
for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {
if (candidate instanceof AnnotatedBeanDefinition) {
for (ServletComponentHandler handler : HANDLERS) {
handler.handle(((AnnotatedBeanDefinition) candidate),
(BeanDefinitionRegistry) this.applicationContext);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// org.springframework.boot.web.servlet.ServletComponentHandler#handle
void handle(AnnotatedBeanDefinition beanDefinition, BeanDefinitionRegistry registry) {
Map<String, Object> attributes = beanDefinition.getMetadata()
.getAnnotationAttributes(this.annotationType.getName());
if (attributes != null) {
doHandle(attributes, beanDefinition, registry);
}
}

// org.springframework.boot.web.servlet.WebFilterHandler#doHandle
public void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,
BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterRegistrationBean.class);
builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
builder.addPropertyValue("dispatcherTypes", extractDispatcherTypes(attributes));
builder.addPropertyValue("filter", beanDefinition);
builder.addPropertyValue("initParameters", extractInitParameters(attributes));
String name = determineName(attributes, beanDefinition);
builder.addPropertyValue("name", name);
builder.addPropertyValue("servletNames", attributes.get("servletNames"));
builder.addPropertyValue("urlPatterns", extractUrlPatterns(attributes));
registry.registerBeanDefinition(name, builder.getBeanDefinition());
}

DispatcherServlet 注册

上文描述了基于注解方式来注册 Servlet 原生组件,DispatcherServlet 作为所有请求入口点,那么也需要注册到 ServletContext中。DispatcherServlet 与基于注解注册实现类似,都是通过 *RegistrationBean 来进行的。

首先声明一个 DispatcherServlet Bean,然后将该 Bean 封装成一个 DispatcherServletRegistrationBean Bean,最后依靠 ServletContextInitializer 回调将该 DispatcherServlet 注册到 ServletContext 中。

此时 MVC 就与 Tomcat 进行接轨了, Tomcat 接收到请求时,请求通过 Filter Chain ,之后通过 DispatchServlet 对请求进行处理。

1
2
3
4
5
6
7
8
9
10
11
// org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.DispatcherServletConfiguration#dispatcherServlet
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
1
2
3
4
5
6
7
8
9
10
11
12
// org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration#dispatcherServletRegistration
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}