【spring】源码-spring 容器启动过程之 initWebApplicationContext 方法分析(二)

目录

1.方法 initWebApplicationContext() 位于contextLoader 类中 

1.1 createWebApplicationContext(servletContext)

1.1.1 determineContextClass()

1.2 configureAndRefreshWebApplicationContext(cwac, servletContext)

 1.2.1 getEnvironment ()  

         父类StandardEnvironment 

         子类StandardServletEnvironment

1.2.2 customizeContext(sc, wac)

1.2.2.1 determineContextInitializerClasses(sc)

跳转 AbstractApplicationContext .refresh() 继续


1.方法 initWebApplicationContext() 位于contextLoader 类中 

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		// 先判断当前servlet 中是否存ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE根容器标识
		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 {
			// 1.默认会实例化一个根容器
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						//2.为创建的root 容器设置父上下文 一般父上下文为空
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					//3.配置容器并刷新
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			//  注册标识 全局共享与第一步判断对应
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
            //保存到当前线程
			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				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");
			}

			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;
		}
	}
//东方鲤鱼

首先先判断了当前servlet中是否已经有了名为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 的容器,不为空抛出异常提示;

1.1 createWebApplicationContext(servletContext)

如果为空 默认创建一个根容器,  spring 会生成一个父级ioc容器,就是这个方法,但是在后边的代码中会看到真正的ioc 容器;标识1 处 的  createWebApplicationContext(servletContext)方法内部实现如下

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		// 获得context的类
		Class<?> contextClass = determineContextClass(sc);
		//判断是否是ConfigurableWebApplicationContext 的子类
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		// 生成WebApplicationContext 的对象
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
//东方鲤鱼

类继承关系图如下

ConfigurableWebApplicationContext 接口:同时继承 WebApplicationContext  和 ConfigurableApplicationContext 的 接口

             WebApplicationConext 接口:继承了ApplicationContext 接口,为 web 应用 提供 配置(获取 ServletContext)的接口,当应用正在运行时,是只读的,但是如果实现类支持的话,可以重新加载;相对于其父接口的 ApplicationContext  ,该接口提供了 获取 ServletContext 对象的方法;

             ConfigurableApplicationContext 接口:ApplicationContext 的子类,提供 配置 一个 应用上下文的 属性,如设置 environment,BeanFactoryPostProcessor,ApplicationListener,ProtocolResolver 等;
 

1.1.1 determineContextClass()

在determineContextClass()方法中 经过两个判断来返回context 类的类信息,代码如下

protected Class<?> determineContextClass(ServletContext servletContext) {
		//获取web.xml 中配置的属性 一般不会自定义
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		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 {
			// 默认使用xmlwebApplicationcontext 
			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);
			}
		}
	}
// 东方鲤鱼

   第一次判断,从 ServletContext 获取配置的 context 类;

   第二个判断,从 ContextLoader.properties 获取配置的 context 类。
     默认情况下,我们不会主动在 ServletContext 中配置 context 类,所以基本是使用 ContextLoader.properties 配置的 context 类,即 XmlWebApplicationContext 类。

      回到initWebApplicationContext方法的标识第二步  ,为根容器设置一个父类,这个是spring 默认的,一般web应用的父类都为null

 两次判断决定context 的类型,

1.2 configureAndRefreshWebApplicationContext(cwac, servletContext)

 接着调用标识3 方法configureAndRefreshWebApplicationContext(cwac, servletContext) 来完成配置何容器的初始化,方法内部实现如下:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		// 如果上下文使用了默认id 重新配置
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
       //从web.xml 中获取
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// 默认生成
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}

		wac.setServletContext(sc);
		// 从servlet获取配置文件
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}
		//抽象方法 实现在AbstractApplicationContext 
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
         // 初始化自定义配置
		customizeContext(sc, wac);

		//webApplicationContext刷新
		wac.refresh();
	}
//东方鲤鱼

 1.2.1 getEnvironment ()  

解释:ConfigurableWebApplicationContext . getEnvironment 方法为抽象方法; AbstractApplicationContext . getEnvironment() 为方法的实现:依次调用了父类构造方法getEnvironment 方法 最终返回 StandardServletEnvironment 实现类,这里发生了子类重写父类的方法,其中的 propertySource 字段(MutablePropertySources 类)中初始化了 5 个 PropertySource(目前前 3 个 source 都是 占位符 object,后期会进行填充和替换);即完成了 Environment 相关实现类的 初始化;

依赖图:

   这里其实发发生了子类重写父类方法的过程,StandardServletEnvironment 类 继承StandardEnvironment 类,也有和父类customizePropertySources()方法;我们点击wac.getEnvironment()  进入ConfigurableApplicationContext 类 发现有一个私有方法 new 了一个 StandardEnvironment 对象; 这很好理解 ,真正的实现就在 StandardEnvironment 类;

父类StandardEnvironment 类

下图先看父类StandardEnvironment 类;

子类StandardServletEnvironment

这里只有两个资源属性,再看子类中 下图

一共生成了五个资源配置属性;

我们利用断点模式看一下生成那些环境资源属性:

1.2.2 customizeContext(sc, wac)

 接着完成自定义配置的初始化,进入customizeContext(sc, wac)方法,如下

protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
		// 1.获取到自定义的类
		List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
				determineContextInitializerClasses(sc);

		//2.循环判断泛型
		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));
		}

		//3.对实现了order接口的类排序
		AnnotationAwareOrderComparator.sort(this.contextInitializers);
		//4.按优先级顺序初始化
		for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
			initializer.initialize(wac);
		}
	}
//东方鲤鱼

1.2.2.1 determineContextInitializerClasses(sc)

解释:第一步,从web.xml 中解析配置参数,主要是自定义的配置类,determineContextInitializerClasses(sc)方法如下:

protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
			determineContextInitializerClasses(ServletContext servletContext) {

		List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
				new ArrayList<>();
      // 从web.xml 中找全局globalInitializerClasses的属性
		String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
		if (globalClassNames != null) {
			// 分割字符串
			for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
				// 多个的情况 循环反射拿到配置类信息放到集合
				classes.add(loadInitializerClass(className));
			}
		}
		// 从web.xml 中找CONTEXT_INITIALIZER_CLASSES_PARAM参数 的属性
		String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
		if (localClassNames != null) {
			for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
				classes.add(loadInitializerClass(className));
			}
		}

		return classes;
	}

       主要操作就是从web.xml 配置中解元素为globalInitializerClasses,contextInitializerClasses的属性,切割字符串后利用反射拿到类信息返回;一般情况这两个属性都不会配置,为null,所以不进断点;

此时我们再看webApplicationContext 对象,第一步的判断就是为了重新设置id 属性,而 wac.setConfigLocation(configLocationParam); 为 ConfigLocations 设置了值,见下图

 

第二步:对返回的自定义配置类集合循环判断其泛型是否为实现类,并设置到父级容器上下文中,

第三步:对实现了order 接口的类进行排序,

第四步:安装优先级顺序调用初始化方法

到此父级容器的配置初始化实例化完成,需要执行refresh()方法来刷新,这个方法中做了spring中的很多操作,ConfigurableApplicationContext . refresh 方法为抽象方法,AbstractApplicationContext .refresh(): 所有的 ApplicationContext 的唯一实现方法,采用了 模版方法设计模式;

跳转 AbstractApplicationContext .refresh() 继续

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东方鲤鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值