Spring IOC系列学习笔记一:前置刷新

原文地址程序员囧辉大佬

相关文章

Spring IOC系列学习笔记一:前置刷新
Spring IOC系列学习笔记二:obtainFreshBeanFactory方法
Spring IOC系列学习笔记三:parseDefaultElement详解
Spring IOC系列学习笔记四:parseCustomElement解析
Spring IOC系列学习笔记五:context:component-scan 节点解析
Spring IOC系列学习笔记六:invokeBeanFactoryPostProcessors解析
Spring IOC系列学习笔记七:registerBeanPostProcessors
Spring IOC系列学习笔记八:finishBeanFactoryInitialization
Spring IOC系列学习笔记九:getBean方法
Spring IOC系列学习笔记十:createBean方法(上)
Spring IOC系列学习笔记十一:createBean方法(下)
Spring IOC系列学习笔记十二:@Autowire注解


前言

众所周知:Spring的核心是refresh方法,但是在执行该方法之前还有一些比较重要的操作,下面我们来讲解一下。


web.xml

	<!--ContextLoaderListener的方式启动spring容器 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!--读取spring的配置文件的位置-->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>
	

这两个标签是spring框架的基本配置,一个读取spring配置文件的路径,另一个进入spring加载的入口开始构建IOC容器。
在正式进入ioc之前我们先看一个比较重要的参数defaultStrategies
该参数初始化实在ContextLoaderListener类的父类中赋值的。进入父类ContextLoader

	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
		//1、DEFAULT_STRATEGIES_PATH的是ContextLoader.properties
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
			//2、加载resource的属性,在这边我们拿到了默认的WebApplicationContext,即:XmlWebApplicationContext
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		}
	}

DEFAULT_STRATEGIES_PATH的是ContextLoader.properties,看一下该文件内的内容获取的是XmlWebApplicationContext

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

defaultStrategies的属性值
我们正式进入ioc前置配置流程。

initWebApplicationContext方法

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		//校验WebApplicationContext是否初始化,如果不会控直接抛出异常
		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.
			if (this.context == null) {
				1.创建WebApplicationContext.
				this.context = createWebApplicationContext(servletContext);
			}
			//判断应用上下午是否是ConfigurableWebApplicationContext类型
			//我们这儿获取的是XmlWebApplicationContext他是该类型的子类,所以这儿为true
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				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 ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					2.配置刷新应用上下文
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			3.设置WebApplicationContext属性
			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;
		}
	}

1.创建WebApplicationContext.见代码块一
2.配置刷新应用上下文见代码块三

代码块一:createWebApplicationContext

	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		1.确定应用上下文这儿就是之前的XmlWebApplicationContext
		Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

1.见代码块二

代码块二:determineContextClass

protected Class<?> determineContextClass(ServletContext servletContext) {	
		1.先检查web.xml是否指定(可以在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 {
			2.从先前设置的应用上下文获取  这儿获取的就是XmlWebApplicationContext
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try {
			3.使用工具类构建实例
				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}
	}

代码块三:configureAndRefreshWebApplicationContext

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
			// 可以从web.xml配置id
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				// 生成默认的id 形如org.springframework.web.context.WebApplicationContext:/library_test_war_exploded
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}

		wac.setServletContext(sc);
		//获取web.xml配置的配置文件路径   classpath:applicationContext.xml
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			1.设置路径
			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
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			2.初始化数据源
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		3.定制上下文 
		customizeContext(sc, wac);
		4.刷新正式进入refresh方法 
		wac.refresh();
	}

1.设置路径见代码块四
2.初始化数据源见代码块十
3.定制上下文见代码块十一
4.正式进入refresh方法 (spring重中之重的方法)

代码块四:setConfigLocation

	public void setConfigLocation(String location) {
		//分割路径 赋值给ConfigLocations
		setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
	}

	/**
	 * Set the config locations for this application context.
	 * <p>If not set, the implementation may use a default as appropriate.
	 */
	public void setConfigLocations(String... locations) {
		if (locations != null) {
			Assert.noNullElements(locations, "Config locations must not be null");
			this.configLocations = new String[locations.length];
			for (int i = 0; i < locations.length; i++) {
				1.解析路径
				this.configLocations[i] = resolvePath(locations[i]).trim();
			}
		}
		else {
			this.configLocations = null;
		}
	}

1.解析路径 见代码块五

代码块五:resolvePath

	protected String resolvePath(String path) {
		return getEnvironment().resolveRequiredPlaceholders(path);
	}


	@Override
	public ConfigurableEnvironment getEnvironment() {
		if (this.environment == null) {
			//创建Environment
			this.environment = createEnvironment();
		}
		return this.environment;
	}

	protected ConfigurableEnvironment createEnvironment() {
		return new StandardEnvironment();
	}

查看StandardEnvironment类的继承关系如下图所示:
StandardEnvironment关系图

继承父类AbstractEnvironment,所以会先执行父类的构造方法。见代码块八

代码块八:AbstractEnvironment构造方法

	public AbstractEnvironment() {
		//定制数据源
		customizePropertySources(this.propertySources);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug(String.format(
					"Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
		}
	}

代码块九 :customizePropertySources

	
	protected void customizePropertySources(MutablePropertySources propertySources) {
	}
	//进入子类StandardServletEnvironment 的重写方法 
	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		//添加servletConfigInitParams(作为占位符, 之后会被替换)
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		//添加servletContextInitParams(作为占位符, 之后会被替换)
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			//设置jndi数据源
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		1.调用父类的方法
		super.customizePropertySources(propertySources);
	}
//进入父类StandardEnvironment的重写方法
	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		//添加systemProperties属性
		propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
		//添加systemEnvironment属性
		propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
	}

此时propertySources属性如下所示:

代码块十:initPropertySources

	@Override
	public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
		//
		WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
	}
	public static void initServletPropertySources(
			MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) {

		Assert.notNull(propertySources, "'propertySources' must not be null");
		
        // 1.如果servletContext不为null && propertySources中包含servletContextInitParams数据源 && 该数据源的类型为StubPropertySource,
        // 则将servletContextInitParams的数据源替换成servletContext
		if (servletContext != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) &&
				propertySources.get(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
			propertySources.replace(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME,
					new ServletContextPropertySource(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext));
		}
		
        // 2.如果servletConfig不为null && propertySources中包含servletConfigInitParams数据源 && 该数据源的类型为StubPropertySource,
        // 则将servletConfigInitParams的数据源替换成servletConfig
		if (servletConfig != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) &&
				propertySources.get(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
			propertySources.replace(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
					new ServletConfigPropertySource(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig));
		}
	}

代码块九中的临时数据源替换为真正的数据源

代码块十一:customizeContext

	protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
		1.确定应用上下文的初始化类
		List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
				determineContextInitializerClasses(sc);

		for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
		// 如果initializerClasses不为空, 遍历处理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()));
			}
			2.实例化initializerClass添加到contextInitializers
			this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
		}

		AnnotationAwareOrderComparator.sort(this.contextInitializers);
		for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
			3.初始化wac,进行自定义操作
			initializer.initialize(wac);
		}
	}

1.确定应用上下文初始化类代码块十二

代码块十二:determineContextInitializerClasses

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

		List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
				new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
			//获取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定义contextInitializerClasses属性
		String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
		if (localClassNames != null) {
			for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
			1.实例化className,获得className对应的类实例
				classes.add(loadInitializerClass(className));
			}
		}

		return classes;
	}

1.实例化className,获得className对应的类实例见代码块十三

代码块十三:loadInitializerClass

@SuppressWarnings("unchecked")
private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {
    try {
        // 1.实例化className
        Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
        if (!ApplicationContextInitializer.class.isAssignableFrom(clazz)) {
            // 2.校验clazz是否实现了ApplicationContextInitializer接口, 如果没有则抛异常
            throw new ApplicationContextException(
                    "Initializer class does not implement ApplicationContextInitializer interface: " + clazz);
        }
        // 3.clazz强转成ApplicationContextInitializer, 并返回
        return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;
    } catch (ClassNotFoundException ex) {
        throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
    }
}

customizeContext方法扩展
代码块11代码块13,可以很明显的看出 customizeContext 方法是 Spring 提供给开发者的一个扩展点,我们可以通过此方法对 ConfigurableApplicationContext 进行一些自定义操作

十四:一个扩展的小栗子

1.编写一个自定义的扩展,我这儿只是简单的输出了一句话

public class MyBeanInit implements BeanFactoryPostProcessor {

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("我爱学习学习爱我!!!");
    }
}

2、被web.xml加载的容器 把扩展放到应用上下文中

public class SpringApplicationInit implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    public void initialize(ConfigurableApplicationContext applicationContext) {
        MyBeanInit myBeanInit = new MyBeanInit();
        applicationContext.addBeanFactoryPostProcessor(myBeanInit);
    }
}

3、在web.xml配置contextInitializerClasses地址

	<context-param>
		<param-name>contextInitializerClasses</param-name>
		<param-value>zgf.library.utils.SpringApplicationInit</param-value>
	</context-param>

总结

至此,refresh 方法之前的操作都进行完毕,比较重要的操作有:

  • 获取默认的 WebApplicationContext:XmlWebApplicationContext。
  • 获取 Spring 配置文件的路径(参数 contextConfigLocation),并赋值给
    AbstractRefreshableConfigApplicationContext 类的 configLocations 属性。
  • 提供自定义应用上下文 ConfigurableApplicationContext 的扩展点。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>