Spring源码学习23

接下来分析Spring源码的最后一个部分SpringMVC的实现。先来两篇文章回顾下SpringMVC和Servlet

https://blog.youkuaiyun.com/qq_23536449/article/details/98957647

https://blog.youkuaiyun.com/qq_23536449/article/details/98955020

1.XmlWebApplicationContext的UML图

本篇章中的新加的类如下:

WebApplicationContext:用于为Web应用程序提供配置的界面。 这在应用程序运行时是只读的,但如果实现支持,则可以重新加载;此接口将{@code getServletContext()}方法添加到通用ApplicationContext接口,并添加一些大家都知道的 * 根上下文必须在引导过程中必须绑定值到的属性名称

ConfigurableWebApplicationContext:由可配置的Web应用程序上下文实现的接口。由{@link ContextLoader}和{@link org.springframework.web.servlet.FrameworkServlet}提供支持.注意:在调用继承自{@link org.springframework.context.ConfigurableApplicationContext} 的{@link #refresh}方法之前,需要调用此接口的setter。它们不会导致自己初始化上下文。该方法的set方法有setServletContext(ServletContext)、setServletConfig(ServletConfig servletConfig)、setNamespace(String namespace)、 setConfigLocation(String configLocation)。

AbstractRefreshableWebApplicationContext:用于为Web环境实现{@link org.springframework.web.context.ConfigurableWebApplicationContext}接口。 * 提供“configLocations”属性,通过Web应用程序启动时的ConfigurableWebApplicationContext接口填充;此类与AbstractRefreshableApplicationContext一样易于子类化:您需要实现的只是{@link #loadBeanDefinitions}方法;实现应该从{@link #getConfigLocations}方法返回的位置指定的文件中加载bean定义。该类中最引人入胜的方法

@Override
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
		beanFactory.ignoreDependencyInterface(ServletContextAware.class);
		beanFactory.ignoreDependencyInterface(ServletConfigAware.class);

		WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
	}

/**
	 * Register web-specific scopes ("request", "session", "globalSession", "application")
	 * with the given BeanFactory, as used by the WebApplicationContext.
	 * @param beanFactory the BeanFactory to configure
	 * @param sc the ServletContext that we're running within
	 */
	public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) {
		beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
		beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
		beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));
		if (sc != null) {
			ServletContextScope appScope = new ServletContextScope(sc);
			beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
			// Register as ServletContext attribute, for ContextCleanupListener to detect it.
			sc.setAttribute(ServletContextScope.class.getName(), appScope);
		}

		beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
		beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
		beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
		beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
		if (jsfPresent) {
			FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
		}
	}
/**
	 * Register web-specific environment beans ("contextParameters", "contextAttributes")
	 * with the given BeanFactory, as used by the WebApplicationContext.
	 * @param bf the BeanFactory to configure
	 * @param servletContext the ServletContext that we're running within
	 * @param servletConfig the ServletConfig of the containing Portlet
	 */
	public static void registerEnvironmentBeans(
			ConfigurableListableBeanFactory bf, ServletContext servletContext, ServletConfig servletConfig) {

		if (servletContext != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) {
			bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, servletContext);
		}

		if (servletConfig != null && !bf.containsBean(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME)) {
			bf.registerSingleton(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME, servletConfig);
		}

		if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) {
			Map<String, String> parameterMap = new HashMap<String, String>();
			if (servletContext != null) {
				Enumeration<?> paramNameEnum = servletContext.getInitParameterNames();
				while (paramNameEnum.hasMoreElements()) {
					String paramName = (String) paramNameEnum.nextElement();
					parameterMap.put(paramName, servletContext.getInitParameter(paramName));
				}
			}
			if (servletConfig != null) {
				Enumeration<?> paramNameEnum = servletConfig.getInitParameterNames();
				while (paramNameEnum.hasMoreElements()) {
					String paramName = (String) paramNameEnum.nextElement();
					parameterMap.put(paramName, servletConfig.getInitParameter(paramName));
				}
			}
			bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME,
					Collections.unmodifiableMap(parameterMap));
		}

		if (!bf.containsBean(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME)) {
			Map<String, Object> attributeMap = new HashMap<String, Object>();
			if (servletContext != null) {
				Enumeration<?> attrNameEnum = servletContext.getAttributeNames();
				while (attrNameEnum.hasMoreElements()) {
					String attrName = (String) attrNameEnum.nextElement();
					attributeMap.put(attrName, servletContext.getAttribute(attrName));
				}
			}
			bf.registerSingleton(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME,
					Collections.unmodifiableMap(attributeMap));
		}
	}

该方法是AbstractApplicationContext提供的模板方法如下图的实现

 

/**
	 *在标准初始化之后修改应用程序上下文的内部bean工厂。 将加载所有bean定义,但尚未实例化任何bean。
	 * 这允许在某些ApplicationContext实现中注册特殊的BeanPostProcessors等
	 * @param beanFactory the bean factory used by the application context
	 */
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	}

2.我们知道了ServletContextListener的使用方法后,下面就来分析ContextLoaderListener.contextInitialized(ServletContextEvent event)

/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

该方法的实现在其父类ContextLoader类中

/**
	 * 使用构造时提供的应用程序上下文初始化Spring的给定servlet上下文的Web应用程序上下文,
	 * 或者根据“{@link #CONTEXT_CLASS_PARAM contextClass}”和
	 * “{@link #CONFIG_LOCATION_PARAM contextConfigLocation}”
	 * context-params创建一个新的Web应用程序上下文
	 * @param servletContext current servlet context
	 * @return the new WebApplicationContext
	 * @see #ContextLoader(WebApplicationContext)
	 * @see #CONTEXT_CLASS_PARAM
	 * @see #CONFIG_LOCATION_PARAM
	 */
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		//web.xml存在多个applicationContext配置,抛出异常
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}
		Log logger = LogFactory.getLog(ContextLoader.class);
		servletContext.log("Initializing Spring root WebApplicationContext");
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();
		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			//将servletContext存储在本地实例变量中,以保证它在ServletContext关闭时可用。
			if (this.context == null) {
				//初始化webApplicationContext
				this.context = createWebApplicationContext(servletContext);
			}
			// 上下文是否为ConfigurableWebApplicationContext的子类
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				//如果还未激活;如果通过构造函数传递的WebApplicationContext可能isActive为true了
				if (!cwac.isActive()) {
					// 上下文尚未刷新 - >提供诸如此类的服务
					//设置父上下文,设置应用程序上下文ID等
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
						//注入上下文实例时没有显式父 - >
						//确定根Web应用程序上下文的父级(如果有)。
						//这里我们不会用到的,所以不分析了
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			//设置webApplicationContext属性
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			//当前线程的类加载器是否为ccl
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				//放到map里了
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
						WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
			}
			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
			}
			//返回WebApplicationContext对象
			return this.context;
		}
		catch (RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
		catch (Error err) {
			logger.error("Context initialization failed", err);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
			throw err;
		}
	}

a.判断servletContext.getAttribute()是否已经有了一个WebApplicationContext对象,有的话抛出异常

b.createWebApplicationContext(servletContext)方法创建一个WebApplicationContext对象;

/**
	 * 实例化此加载器的根WebApplicationContext,或者
	 * 默认上下文类或自定义上下文类(如果已指定)。
	 * <p>此实现期望自定义上下文实现
	 * {@link ConfigurableWebApplicationContext}界面。 可以在子类中重写。
	 * <p>In addition, {@link #customizeContext} gets called prior to refreshing the
	 * context, allowing subclasses to perform custom modifications to the context.
	 * @param sc current servlet context
	 * @return the root WebApplicationContext
	 * @see ConfigurableWebApplicationContext
	 */
	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		Class<?> contextClass = determineContextClass(sc);
		//如果class不是继承ConfigurableWebApplicationContext 类抛出个异常
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		//反射创建ConfigurableWebApplicationContext
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

/**
	 * 返回要使用的WebApplicationContext实现类,如果未指定,则使用默认的XmlWebApplicationContext或自定义上下文类。
	 * @param servletContext current servlet context
	 * @return the WebApplicationContext implementation class to use
	 * @see #CONTEXT_CLASS_PARAM
	 * @see org.springframework.web.context.support.XmlWebApplicationContext
	 */
	protected Class<?> determineContextClass(ServletContext servletContext) {
		// 获取contextClass配置WebApplicationContext
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		//如果不为null;加载className对应的Class
		if (contextClassName != null) {
			try {
				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		else {
			//从ContextLoader.properties配置文件中加载
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try {
				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}
	}

创建真的很简单的呐,首先会从类似如下方式中去获取配置的WebApplicationContext对应的className

<context-param>
        <param-name>contextClass</param-name>
        <param-value>ConfigurableWebApplicationContext的实现类名称</param-value>
    </context-param>

如果他没有配置那么咱们会从一个叫ContextLoader.properties的配置中去拿,Spring给咱们配置了个XmlWebApplicationContext

# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

然后反射创建该className对应的实例对象就好了。

c.创建好了当然就是要配置啦,所以方法来到了configureAndRefreshWebApplicationContext(cwac, servletContext);对于为啥要判断isActive()我在上面已经说了,可以通过构造函数的方式传进来一个WebApplicationContext,如果咱们不调用他的refresh()方法,他就不是isActive()。这时候需要Spring给咱们refresh()

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		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
			//设置ConfigurableWebApplicationContext的id
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}
		//设置ServletContext到wac中哎嘿嘿
		wac.setServletContext(sc);
		//获取contextConfigLocation参数
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}

		// 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
		//在刷新上下文时,无论如何都会调用wac环境的#initPropertySources;
		//为了用于#refresh之前发生的任何后处理或初始化在此急切地确保servlet属性源到位
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		customizeContext(sc, wac);
		wac.refresh();
	}

看到了咱们通过如下代码

String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}

将这种配置方式配置的xml设置到了WebApplicationContext中了

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:**/applicationContext*.xml</param-value>
    </context-param>

然后是customizeContext(sc,wac)方法,该方法提供了一个操作ApplicationContext的扩展点

/**
	 * 配置化该ContextLoader创建的{@link ConfigurableWebApplicationContext},在配置位置已经提供给
	 * 上下文之后,但在上下文调用<em>refreshed<em/>之前
	 *
	 * <p>The default implementation {@linkplain #determineContextInitializerClasses(ServletContext)
	 * determines} what (if any) context initializer classes have been specified through
	 * {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and
	 * {@linkplain ApplicationContextInitializer#initialize invokes each} with the
	 * given web application context.
	 *
	 * 默认的{@linkplain #determineContextInitializerClasses(ServletContext)determines}实现;如果通过
	 * {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters}配置了ApplicationContextInitializer
	 * 那么将会调用每个{@linkplain ApplicationContextInitializer#initialize}使用给定的webApplicationContext
	 *
	 *
	 * <p>Any {@code ApplicationContextInitializers} implementing
	 * {@link org.springframework.core.Ordered Ordered} or marked with @{@link
	 * org.springframework.core.annotation.Order Order} will be sorted appropriately.
	 * @param sc the current servlet context
	 * @param wac the newly created application context
	 * @see #CONTEXT_INITIALIZER_CLASSES_PARAM
	 * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
	 */
	protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
		//获取配置的ContextInitializerClasses类
		List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
				determineContextInitializerClasses(sc);
		//TODO 扩展点
		//这些class类必须是符合ApplicationContextInitializer.class的#initialize标准的才给应用
		for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
			Class<?> initializerContextClass =
					GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
			if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
				throw new ApplicationContextException(String.format(
						"Could not apply context initializer [%s] since its generic parameter [%s] " +
						"is not assignable from the type of application context used by this " +
						"context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
						wac.getClass().getName()));
			}
			this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
		}
		//排序下
		AnnotationAwareOrderComparator.sort(this.contextInitializers);
		//按照顺序挨个应用initializer到WebApplicationContext
		for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
			initializer.initialize(wac);
		}
	}

最后refresh();该方法是AbstractApplicationContext实现的,具体的就不解析了,前面咱们已经解析过了。

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			//准备此上下文以进行刷新,其实无非就是设置启动时间,是否活动状态
			prepareRefresh();

			// 告诉子类刷新内部bean工厂。
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// 准备bean工厂以在此上下文中使用
			prepareBeanFactory(beanFactory);

			try {
				//TODO 扩展点 允许在上下文子类中对bean工厂进行后处理。
				//我们拿到了beanFactory想做什么就做什么了,但是不要
				//先把bean创建出来
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				// 调用注册的BeanFactoryPostProcessor的BeanFactoryPostProcessor,
				// 比如设置propertyEditor到BeanFactory中
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				// 注册BeanPostProcessors
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				// 为上下文初始化messagesource,其实是为了国际化,
				// 这里老哥不进行分析了,感觉没啥用
				initMessageSource();

				// Initialize event multicaster for this context.
				// 初始化应用消息广播
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				// TODO 留给子类初始化其他特殊bean
				onRefresh();

				// Check for listener beans and register them.
				// 检查并注册listener beans
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				// 初始化剩下的(非懒惰的)singletons,有些bean 比如那些实现BeanFactoryPostProcessor
				// 和BeanPostProcessor需要提前初始化,所以叫初始化剩余的
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				// 完成刷新,发布相应的事件
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				// 销毁已创建的单例bean避免占用资源
				destroyBeans();

				// Reset 'active' flag.
				// 取消刷新,重置active 标志
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				//从我们开始,重置Spring核心中的常见内省缓存
				//可能再也不需要单例bean的元数据......
				resetCommonCaches();
			}
		}
	}

d.将咱们刚才创建的WebApplicationContext对象塞到ServletConext的attributes中哎嘿嘿

e.然后就是将我们的WebApplicationContext对象暴露出去了,为了让咱们能够在web项目中用如下方式获取到ApplicationContext,Spring操作还是骚啊。

ContextLoader.getCurrentWebApplicationContext();
public static WebApplicationContext getCurrentWebApplicationContext() {
		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		if (ccl != null) {
			WebApplicationContext ccpt = currentContextPerThread.get(ccl);
			if (ccpt != null) {
				return ccpt;
			}
		}
		return currentContext;
	}

3.WebApplicationContext.refresh之前对于咱们的ConfigurableApplicationContext的骚操作示例

定义MyApplicationContextInitializer实现ApplicationContextInitializer接口

/**
 * @author 周宁
 * @Date 2019-08-13 16:46
 */
public class MyApplicationContextInitializer implements ApplicationContextInitializer<XmlWebApplicationContext> {
    @Override
    public void initialize(XmlWebApplicationContext applicationContext) {
        System.out.println("MyApplicationContextInitializer initialize");
    }
}

web.xml中配置下

<context-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>org.springframework.studymvc.acini.MyApplicationContextInitializer</param-value>
    </context-param>

然后在启动web应用,看到控制台的输出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值