spring源码------spring什么时候以及何时初始化web应用相关上下文的(FrameworkServlet,DispatcherServlet)


 spring的web应用的上下文并不是在容器启动的时候就进行初始化的,而是在第一次请求的时候进行初始化的。

1.从Servlet规范到Spring
1.1 Servlet规范部分说明

 spring的web应用(不包含web-flux)是基于Servlet规范进行的搭建的,这里需要先讲一下Servlet规范的部分内容,因为spring的web上下文的初始化也是要从这里开始的。
Servlet是按照一个严格定义的生命周期被管理,该生命周期规定了Servlet如何被加载、实例化、初始化、处理客户端请求,以及何时结束服务。该声明周期可以通过 javax.servlet.Servlet 接口中的initservicedestroy这些 API 来表示,所有 Servlet必须直接或间接的实现 GenericServletHttpServlet 抽象类。
servlet 对象实例化后,容器必须初始化 servlet之后才能处理客户端的请求。初始化的目的是以便 Servlet能读取持久化配置数据,初始化一些代价高的资源(比如JDBC™ API 连接),或者执行一些一次性的动作。容器通过调用 Servlet实例的 init 方法完成初始化,init方法定义在Servlet接口中,并且提供一个唯一的 ServletConfig接口实现的对象作为参数,该对象每个 Servlet 实例一个。

1.2 分析初始化的入口

 从上面的规范说明中知道了,spring会实现GenericServlet 抽象类的init方法来初始化相关的信息,
通过查看init方法的实现类,发现在spring的所有的这个方法的实现中只有HttpServletBean是符合需求的。因此我们进入到这个方法。

2. 初始化上下文的入口HttpServletBean

 这里直接进入到HttpServletBean实现的init方法进行分析。

	public final void init() throws ServletException {

		// Set bean properties from init parameters.
		//获取ServletConfig,从servlet中获取对应的初始化参数,ServletConfig是servlet规范中的接口类
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		//如果初始化参数不为空,则需要将
		if (!pvs.isEmpty()) {
			try {
				//获取HttpServletBean的BeanWrapper
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				//讲ServletContext转化为一个ResourceLoader
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				//创建ResourceEditor并保存到HttpServletBean中
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				//这里是空方法,可以子类实现
				initBeanWrapper(bw);
				//将ServletConfig对应的属性保存到HttpServletBean
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}
		//进行初始化,这个是一个交给子类实现的方法,可以自定义初始化的逻辑
		initServletBean();
	}

 可以看到实现的逻辑也是比较简单的,就是从ServletConfig获取servlet容器中的相关的初始化的参数,如果属性不为空的时候,可以定义自己的属性处理逻辑,这里会调用。处理完容器中相关的初始化参数之后就是进行初始化了。这个初始化的方法initServletBean是交给子类去自由实现初始化逻辑的。
 这里补充说明ServletConfigServletContext这两个类都是servlet规范中的接口类。这里进行简单的说明一下

说明
ServletConfigServletConfig是每个 Servlet 实例一个,配置对象允许 Servlet 访问由 Web 应用配置信息提供的键-值对的初始化参数
ServletContextServletContext 接口定义了 servlet 运行在的 Web 应用的视图,servlet 可以使用 ServletContext 对象记录事件,获取 URL 引用的资源,存取当前上下文的其他 servlet 可以访问的属性。默认的 ServletContext 是非分布式的且仅存在于一个 JVM 中。
3. 初始化web应用上下文的FrameworkServlet

 在上面提到的initServletBean方法,其实现在spring中只有一个类实现了那就是FrameworkServlet。这个类是spring对spring应用上下文的集成类。作用很多:

  1. 为每个servlet应用管理一个WebApplicationContext对象。
  2. 在请求处理时发布事件,不管是否成功处理了请求

 这个类也提供了很多扩展,其子类必须实现这个类的doService方法。同时也可以重载initFrameworkServlet来自定义初始化。
 现在看看FrameworkServlet如何进行应用的初始化的,直接进入到initServletBean进行查看。

	protected final void initServletBean() throws ServletException {
		//获取ServletContext,打印servlet应用的名称
		getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("Initializing Servlet '" + getServletName() + "'");
		}
		//获取当前系统时间,用于计算初始化的时长
		long startTime = System.currentTimeMillis();

		try {
			//初始化web上下文WebApplicationContext
			this.webApplicationContext = initWebApplicationContext();
			//框架其他的初始化,这个方法是个空方法,可以子类进行实现
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isDebugEnabled()) {
			String value = this.enableLoggingRequestDetails ?
					"shown which may lead to unsafe logging of potentially sensitive data" :
					"masked to prevent unsafe logging of potentially sensitive data";
			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
					"': request parameters and headers will be " + value);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}

 可以看到这里只是简单的几个方法的调用,就分别完成了spring的web上下文初始化,以及其他的初始化,其中后面的初始化时可以自己定义逻辑的,默认是空的。这里主要就看上下文的初始化。

	protected WebApplicationContext initWebApplicationContext() {
		//如果用户指定了WebApplicationContext情况下,从ServletContext获取WebApplicationContext,这里获取的属性名是WebApplicationContext.class.getName() + ".ROOT"
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
		//如果webApplicationContext不是null,说明已经初始化过了,在spring容器初始化的时候,会设置这个值得,因此这里判断是true
		if (this.webApplicationContext != null) {
		
			//设置已经存在的上下文
			wac = this.webApplicationContext;
			//如果是ConfigurableWebApplicationContext类型的,需要先设置WebApplicationContext为根上下文
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				//检查当前上下文是否是活跃的,既可以用的,如果不是的则需要刷新
				if (!cwac.isActive()) {
				//如果还没有设置根上下文则进行设置
					if (cwac.getParent() == null) {
					
						cwac.setParent(rootContext);
					}
					//配置并刷新web上下文
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		//如果wac是null,则书名web上下文是null,则在servlet中寻找是否存在web上下文,这里寻找的是属性名为FrameworkServlet.class.getName() + ".CONTEXT."+当前servlet的名称的上下文
		if (wac == null) {
  			wac = findWebApplicationContext();
		}
		//如果servlet中也不存在上下文,则创建一个
		if (wac == null) {
			
			wac = createWebApplicationContext(rootContext);
		}
		//检查onRefresh是否已经调用了
		if (!this.refreshEventReceived) {
		
			synchronized (this.onRefreshMonitor) {
				//模板方法,可以覆盖该方法以添加特定于servlet的刷新工作。成功刷新上下文后调用
				onRefresh(wac);
			}
		}
		//我们应该将上下文作为ServletContext属性发布吗,默认为true
		if (this.publishContext) {
			
			//获取要设置的属性名 FrameworkServlet.class.getName() + ".CONTEXT."+当前servlet的名称
			String attrName = getServletContextAttributeName();
			//讲当前上下文设置到servlet上下文中
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

 这里在逻辑主要分文两点,寻找可以用的web应用上下文,然后进行时间的发布跟其他刷新工作。这里需要说明几点的是:

  1. 实例化跟初始化是有区别的,在这里方法这里是初始化而不是实例化
  2. 在正常情况下进入到这个方法的时候,是在spring的容器已经初始化完毕以后了,因为这个时候整个服务已经起来了,容器里面会包含对应的web应用上下文,因此上面方法中的webApplicationContext不会为null
  3. 对应的web的服务也是活跃状态,因此上面方法中的isActive的判断是true。

 对于其中的第二点,这里说明一下webApplicationContext是什么时候被设置进来的,因为FrameworkServlet实现了ApplicationContextAware接口,因此在FrameworkServlet实例化之后初始化之前会调用实现的setApplicationContext方法


	public void setApplicationContext(ApplicationContext applicationContext) {
		if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
			this.webApplicationContext = (WebApplicationContext) applicationContext;
			this.webApplicationContextInjected = true;
		}
	}

 这里关于ApplicationContextAware可以看看对应的前面的spring的生命周期的文章Spring源码----Spring的Bean生命周期流程图及代码解释

4. 初始化spring相关web相关类的DispatcherServlet
4.1 初始化servlet的常用策略

 我们注意到在FrameworkServlet中的initWebApplicationContext方法中有这么的一个步骤,在找到了web应用上下文之后,会有一个检查onRefresh方法是否已经调用了的步骤,如果没有调用就会调用对应的onRefresh方法,如果调用了就不用处理,那么还有哪里会调用呢。现在要分析就跟这个有关。
onRefresh方法在FrameworkServlet中是空逻辑的,其主要逻辑在DispatcherServlet这个类中进行的。接下来就进入到里面

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {
		//初始化解析文件相关的MultipartResolver,寻找对应的bean,然后保存
		initMultipartResolver(context);
		//初始化区域解析用的LocaleResolver,寻找对应的bean,然后保存
		initLocaleResolver(context);
		//初始化解析主题用的ThemeResolver,寻找对应的bean,然后保存
		initThemeResolver(context);
		//初始化handlerMapping,逻辑就是找到容器中所有的HandlerMapping类型的bean然后放到一个集合中
		initHandlerMappings(context);
		//初始化HandlerAdapter,逻辑就是找到容器中所有的HandlerAdapter类型的bean然后放到一个集合中
		initHandlerAdapters(context);
		//初始化HandlerExceptionResolver,逻辑跟上面的一样寻找HandlerExceptionResolver类型的bean,然后保存
		initHandlerExceptionResolvers(context);
		//初始化请求视图(url)转化为本地试图(url)的RequestToViewNameTranslator,寻找对应的bean,然后保存
		initRequestToViewNameTranslator(context);
		//初始化ViewResolver试图解析器,逻辑跟上面的一样寻找ViewResolver类型的bean,然后保存
		initViewResolvers(context);
		//初始化FlashMapManager,用来管理FlashMap(保存两个视图url之间的关系,在用direct的时候会用到)
		initFlashMapManager(context);
	}

 是不是感觉很多熟悉的东西出现了,没错这就是整个springmvc会用到的东西会在这里进行装载保存,在后面进行处理的时候用到。
 到这里spring的web应用会用的东西在这里就已经初始化完成了。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值