详解springMVC容器加载源码分析

网友投稿 199 2023-05-12


详解springMVC容器加载源码分析

springmvc是一个基于servlet容器的轻量灵活的mvc框架,在它整个请求过程中,为了能够灵活定制各种需求,所以提供了一系列的组件完成整个请求的映射,响应等等处理。这里我们来分析下springMVC的源码。

首先,spring提供了一个处理所有请求的servlet,这个servlet实现了servlet的接口,就是DispatcherServlet。把它配置在web.xml中,并且处理我们在整个mvc中需要处理的请求,一般如下配置:

spring-servlet

org.springframework.web.servlet.DispatcherServlet

contextConfigLocation

classpath:springmvc-servlet.xml

1

spring-servlet

/

DispatcherServlet也继承了FrameworkServlet抽象类,这个抽象类为整个servlet的处理提供了spring容器的支持,让原本servlet容器的DispatcherServlet拥有能够利用spring容器组件的能力。上面servlet的初始化参数contextConfigLocation就是DispatcherServlet获取spring容器的配置环境。FrameworkServlet继承自org.springframework.web.servlet.HttpServletBean。HttpServletBean实现了servlet容器初始化会调用的init函数。这个init函数会调用FrameworkServlet的初始化加载spring容器方法。方法源码:

@Override

protected final void initServletBean() throws ServletException {

getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");

if (this.logger.isInfoEnabled()) {

this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");

}

long startTime = System.currentTimeMillis();

try {

// 初始化spring-servlet容器

this.webApplicationContext = initWebApplicationContext();

initFrameworkServlet();

}

catch (ServletException ex) {

this.logger.error("Context initialization failed", ex);

throw ex;

}

catch (RuntimeException ex) {

this.logger.error("Context initialization failed", ex);

throw ex;

}

if (this.logger.isInfoEnabled()) {

long elapsedTime = System.currentTimeMillis() - startTime;

this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +

elapsedTime + " ms");

}

}

可以看到这里就触发了spring的对web支持的容器初始化,这里使用的容器为WebApplicationContext.接下来我们就来分析一下整个容器的初始化过程:

protected WebApplicationContext initWebApplicationContext() {

// 查看是否在servlet上下文有所有容器需要继承的根Web容器

WebApplicationContext rootContext =

WebApplicationContextUtils.getWebApplicationContext(getServletContext());

WebApplicationContext wac = null;

// 如果容器已经加载

if (this.webApplicationContext != null) {

// A context instance was injected at construction time -> use it

wac = this.webApplicationContext;

if (wac instanceof ConfigurableWebApplicationContext) {

ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;

if (!cwac.isActive()) {

// The context has not yet been refreshed -> provide services such as

// setting the parent context, setting the application context id, etc

if (cwac.getParent() == null) {

// The context instance was injected without an explicit parent -> set

// the root application context (if any; may be null) as the parent

cwac.setParent(rootContext);

}

configureAndRefreshWebApplicationContext(cwac);

}

}

}

if (wac == null) {

// No context instance was injected at construction time -> see if one

// has been registered in the servlet context. If one exists, it is assumed

// that the parent context (if any) has already been set and that the

// user has performed any initialization such as setting the context id

wac = findWebApplicationContext();

}

if (wac == null) {

// 创建容器

wac = createWebApplicationContext(rootContext);

}

if (!this.refreshEventReceived) {

// Either the context is not a ConfigurableApplicationContext with refresh

// support or the context injected at construction time had already been

// refreshed -> trigger initial onRefresh manually here.

onRefresh(wac);

}

if (this.publishContext) {

// Publish the context as a servlet context attribute.

String attrName = getServletContextAttributeName();

getServletContext().setAttribute(attrName, wac);

if (this.logger.isDebugEnabled()) {

this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +

"' as ServletContext attribute with name [" + attrName + "]");

}

}

return wac;

}

如果已经有容器被创建那就初始化。否则创建容器,创建逻辑:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {

Class> contextClass = getContextClass();

if (this.logger.isDebugEnabled()) {

this.logger.debug("Servlet with name '" + getServletName() +

"' will try to create custom WebApplicationContext context of class '" +

contextClass.getName() + "'" + ", using parent context [" + parent + "]");

}

// 判断是否是可配置的容器

if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {

throw new ApplicationContextException(

"Fatal initialization error in servlet with name '" + getServletName() +

"': custom WebApplicationContext class [" + contextClass.getName() +

"] is not of type ConfigurableWebApplicationContext");

}

// 如果找到目标容器,并且可配置,然后就开始获取配置文件并开始初始化

ConfigurableWebApplicationContext wac =

(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

wac.setEnvironment(getEnvironment());

wac.setParent(parent);

wac.setConfigLocation(getContextConfigLocation());

// 这里是获取配置文件和完成初始化web容器的入口

configureAndRefreshWebApplicationContext(wac);

return wac;

}

这里完成了 web容器的相关准备工作,开始正式读取配置文件加载和初始化容器。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {

// 给容器设置一个id

if (ObjectUtils.identityToString(wac).equals(wac.getId())) {

// The application context id is still set to its original default value

// -> assign a more useful id based on available information

if (this.contextId != null) {

wac.setId(this.contextId);

}

else {

// Generate default id...

wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +

ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());

}

}

wac.setServletContext(getServletContext());

wac.setServletConfig(getServletConfig());

wac.setNamespace(getNamespace());

wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

// The wac environment's #initPropertySources will be called in any case when the context

// is refreshed; do it eagerly here to ensure servlet property sources are in place for

// use in any post-processing or initialization that occurs below prior to #refresh

ConfigurableEnvironment env = wac.getEnvironment();

if (env instanceof ConfigurableWebEnvironment) {

((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());

}

/* 这里在容器被激活后,

并且容器还没完成初始化之前可以对容器的相关配置做一些修改,

默认给了空实现,

这里子类可以去重写,能够获得在容器初始化之前做 一些处理*/

postProcessWebApplicationContext(wac);

// 这里讲容器的初始化信息放到一个列表

applyInitializers(wac);

// 这里就开始web容器的初始化

wac.refresh();

}

容器的初始化在AbstractApplicationContext,无论是其他的容器,最终都会调用到refresh()函数,这个函数基本定义了整个容器初始化的整个脉络,这里不展开讲,本博客之后应该会详细分析这块的逻辑,这里大概的注释一下每一个函数完成的操作:

public void refresh() throws BeansException, IllegalStateException {

synchronized (this.startupShutdownMonitor) {

// 这里主要加载了容器当中一些从其他配置文件读取的变量

prepareRefresh();

// 获取容器本身

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 这里完成一些基础组件的依赖

prepareBeanFactory(beanFactory);

try {

// 添加 容器初始化之前的前置处理

postProcessBeanFactory(beanFactory);

// 调用 前置处理器,其中包含invokeBeanDefinitionRegistryPostProcessors与invokeBeanFactoryPostProcessors两类前置处理器的调用

invokeBeanFactoryPostProcessors(beanFactory);

// 注册bean被创建之前的前置处理器

registerBeanPostProcessors(beanFactory);

// 初始化容器的编码源

initMessageSource();

// 初始化一些事件监听器

initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.

onRefresh();

// 注册容器监听器

registerListeners();

// 初始化所有非懒加载的beans

finishBeanFactoryInitialization(beanFactory);

// Last step: 事件通知关心容器加载的相关组件

finishRefresh();

}

// 部分代码省略

}

}

}

自此加载完毕核心容器,然后回到FramewordServlet的initWebApplicationContext函数,在调用createWebApplicationContext完成一系列上面的操作之后,需要mvc servlet组件,入口就是onRefresh(ApplocationContext context)方法。它会调用另一个方法initStrategies(ApplicationContext context)。该方法如下:

protected void initStrategies(ApplicationContext context) {

initMultipartResolver(context);

initLocaleResolver(context);

initThemeResolver(context);

// 获取所有的RequestMappings

initHandlerMappings(context);

// 不同handler的适配器

initHandlerAdapters(context);

// 异常解析器

initHandlerExceptionResolvers(context);

initRequestToViewNameTranslator(context);

initViewResolvers(context);

initFlashMapManager(context);

}

这里我们重点讲解initHandlerMappings与initHandlerAdapters函数,因为这两个是处理servlet请求的入口。

在spring mvc中任何类都可以处理request请求,因为DispacherServlet也是实现了HttpServlet的接口,所以处理请求也是doService里。doService会将请求交给doDispatcher函数处理。然后doDispacher的源码:

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);

// 这里获取当前请求的处理handler

http:// mappedHandler = getHandler(processedRequest, false);

if (mappedHandler == null || mappedHandler.getHandler() == null) {

noHandlerFound(processedRequest, response);

return;

}

/* 因为handler可以是任何类,

但是我们的DispacherServlet需要一个统一的处理接口,这个接口就是HandlerAdapter,

不同的HandlerMapping可以获取到各种各样的Handler,

这个handler最后都必须得到HandlerAdapter的支持才能被DispacherServlet所调用。

扩充一个新的HandlerMapping只需要添加一个新的HandlerAdatper即可,有点抽象工厂模式的味道*/

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// 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 (logger.isDebugEnabled()) {

logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);

}

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(request, mv);

mappedHandler.applyPostHandle(processedRequest, response, mv);

}

catch (Exception ex) {

dispatchException = ex;

}

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

}

catch (Exception ex) {

triggerAfterCompletion(processedRequest, response, mappedHandler, ex);

}

catch (Error err) {

triggerAfterCompletionWithError(processedRequest, response, mappedHandler, 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);

}

}

}

}

但是我们发现获取到的handler并不是Object而是一个HandlerExecutionChain,这个类可以进去查看发现是一堆拦截器和一个handler,主要就是给每一个请求一个前置处理的机会,这里值得一提的是一般来说拦截器和过滤器的区别就是拦截器可以终止后续执行流程,而过滤器一般不终止。过滤器一般是容器级别的,这个handler前置拦截器可以做到更细级别的控制,例如过滤器只定义了init和doFilter,但是这个handler拦截器定义了preHandle和postHandle还有afterCompletion函数,不难理解分别对应不同请求阶段的拦截粒度,更加灵活。

获取处理handler的getHandler代码:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {

// 这里获取多个映射方式,一旦找到合适的处理请求的handler即返回

for (HandlerMapping hm : this.handlerMappings) {

if (logger.isTraceEnabled()) {

logger.trace(

"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");

}

// HandlerExecutionChain包含了

HandlerExecutionChain handler = hm.getHandler(request);

if (handler != null) {

return handler;

}

}

return null;

}

HandlerAdapter处理后返回统一被封装成ModelAndView对象,这个就是包含试图和数据的对象,在经过适当的处理:

processDispatchResult(HttpServletRequest request, HttpServletResponse response,

HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)

将页面与返回的数据返回给浏览器完成整个请求过程。以上就是springMVC大概的启动过程和请求的处理过程,之后本博客还会陆续分析核心的spring源码,博主一直认为精读著名框架源码是在短时间提升代码能力的捷径,因为这些轮子包含太多设计思想和细小的代码规范,这些读多了都会潜移默化的形成一种本能的东西。设计模式也不断贯穿其中。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:利用Spring Boot操作MongoDB的方法教程
下一篇:Java 驼峰命名法详解(必看篇)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~