spring源码分析-01-IOC初始化容器过程分析
2019-01-02 14:10
453 查看
入口:
public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, ApplicationContext parent) throws BeansException { super(parent); Assert.notNull(paths, "Path array must not be null"); Assert.notNull(clazz, "Class argument must not be null"); this.configResources = new Resource[paths.length]; for (int i = 0; i < paths.length; i++) { this.configResources[i] = new ClassPathResource(paths[i], clazz); } refresh(); }
主要方法就是refresh方法进行IOC容器初始化。初始化过程包括:
- 定位
- 加载:加载过程最复杂,先将xml读取为Element然后在将Element转换为BeanDefinitions。里面还有好多逻辑
- 注册:注册比较简单,加载完毕后生成BeanDefinitionHolder对象,这个对象持有beanName和BeanDef所以直接get到然后放到Map结构中
最后将xml解析为BeanDefinition对象,放到map中,当调用getBean()执行时就会
- 实例化
- 依赖注入
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. //调用容器准备刷新方法,获取容器的当前时间,同时给容器设置同步标识 prepareRefresh(); // Tell the subclass to refresh the internal bean factory. //告诉子类启动refreshBeanFactory方法,Bean定义资源文件的载入从子类的refreshBeanFactory()方法启动 /** * 1.AbstractRefreshableApplicationContext.refreshBeanFactory这个方法里面就是构造BeanFactory * 2.然后调用AbstractRefreshableApplicationContext.getBeanFactory赋值给ConfigurableListableBeanFactory * 3.返回return这个BeanFactory */ ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. //为BeanFactory配置容器特性,例如类加载器,事件处理器等 prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. //国际化初始化 initMessageSource(); // Initialize event multicaster for this context. //事件传播 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. 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. destroyBeans(); // Reset 'active' flag. 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... resetCommonCaches(); } } }
上面refresh方法实现的代码
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();这个就是创建IOC容器的方法。所以继续看这个方法的代码
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { //采用委派模式,父类提供接口,子类提供实现 //创建BeanFactory的方法。 refreshBeanFactory(); //创建好BeanFactory之后,通过getBeanFactory获得方法 ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
调用父类
AbstractRefreshableApplicationContext.refreshBeanFactory()创建容器。然后调用父类
AbstractRefreshableApplicationContext.getBeanFactory()获得创建的IOC容器,并将这个IOC容器返回出去。下面是refreshBeanFactory代码第一部分是判断容器是否存在如果存在则销毁容器,重新创建。
protected final void refreshBeanFactory() throws BeansException { //判断是否已经存在IOC容器,如果存在,则销毁 /** * hasBeanFactory()在判断AbstractRefreshableApplicationContext里面的成员变量beanFactory如果不是空,则需要销毁IOC容器 */ if (hasBeanFactory()) { //主要就是清除map。IOC的容器就是一个Map destroyBeans(); //将BeanFactory=null closeBeanFactory(); } try { //创建IOC容器,这个时候的beanFacotr仅仅只是new一下,容器里面还没有东西 DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); //对IOC容器进行定制化,如设置启动参数,开启注解的自动装配等等 customizeBeanFactory(beanFactory); //调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的loadBeanDefinitions,具体的实现调用子类容器 //使用子类AbstractXmlApplicationContext来实现 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
从上面的代码可以知道spring实例了一个DefaultListableBeanFactory给用户使用。在
DefaultListableBeanFactory beanFactory = createBeanFactory();里面只是new了一个容器,当时容器里面什么都没有真正填充容器的是loadBeanDefinitions(beanFactory)。
//调用AbstractXmlApplicationContext.loadBeanDefinitions这里采用了委派模式,自己不做事,交给子类做事 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. //创建XmlBeanDefinitionReader,即Bean的读取器,并通过回调设置到容器beanFactory里面(目前beanFactory仅仅只是new了一个对象)容器使用这个读取器去读取Bean定义资源 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. //为Bean读取器设置Spring资源加载器,AbstractXmlApplicationContext的父类AbstractApplicationContext 继承了 DefaultResourceLoader,因此容器也是一个Bean读取器 beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); //为Bean读取器设置SAX xml解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. //当Bean读取器读取Bean定义的xml资源文件时,启用xml校验机制,此处仅仅是设置了校验标志位:设置true initBeanDefinitionReader(beanDefinitionReader); //Bean读取器真正实现加载的方法 loadBeanDefinitions(beanDefinitionReader); }
下面关于loadBeanDefinitions的层次很深,需要认真跟踪。
//AbstractXmlApplicationContext.loadBeanDefinitions protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //获取Bean定义资源的定位 Resource[] configResources = getConfigResources(); if (configResources != null) { //xml Bean读取器调用父类AbstractBeanDefinitionReader读取定位的Bean定义资源 reader.loadBeanDefinitions(configResources); } //采用委派模式,调用子类的获取Bean定义资源定位的方法,该方法在ClassPathXmlApplicationContext中实现 //如果子类种获取的Bean定义资源定位为空,则获取FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源 String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
上面将资源定位到,调用XML读取器解析xml。调用
XmlBeanDefinitionReader.loadBeanDefinitions()继续解析xml在spring里面如果某个方法以do开头那么这个方法就是真正做事的方法,其他方法都是套路,不停的调用调用而已看到下面方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //真正做事的人找到了! return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
继续跟踪doLoadBeanDefinitions。这个方法里面首先将xml文件流解析为Document对象。
Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource);
继续进入
registerBeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //创建DefaultBeanDefinitionDocumentReader对象,这个对象里面存放就是bean alias import resource等等 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); //前面已经将XML文件内容转换为Document对象。 //下面方法是将Document对象转成Spring里面的BeanDefinition对象 //DefaultBeanDefinitionDocumentReader类里面的方法 //并且将BeanDefinition注册到IOC容器中 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
上面
registerBeanDefinitions中主要就是
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));将Document对象转成BeanDefinitions对象。继续进入这个方法。找到
DefaultBeanDefinitionDocumentReader.registerBeanDefinitions方法。(马上就可以看到曙光了)
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); //从Document里面获得Element的根元素开始解析。 //以do开头的方法就是具体做事的方法 doRegisterBeanDefinitions(root); }
继续看doRegisterBeanDefinitions方法
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; //创建BeanDefinitionParserDelegate对象。这个对象就是解析Document对象中的数据。是要生成BeanDefinition的处理对象。在下面创建对象的时候已经将配置文件中<beans>里面的默认熟悉解析出来了 /** * 在<beans><beans/>里面主要定义了默认的属性如下 default-lazy-init default-merge default-autowire default-dependency-check default-autowire-candidates default-init-method default-destroy-method */ this.delegate = createDelegate(getReaderContext(), root, parent); //默认的命名空间,也就是 /** *xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" */ if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isInfoEnabled()) { logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } preProcessXml(root); //处理配置文件 parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
上面的方法首先创建
BeanDefinitionParserDelegate,然后读取配置文件中配置的全局默认属性。紧接着处 1bb8c 理命名空间。然后处理
preProcessXml(root);这个方法是空的。真正处理配置的方法是
parseBeanDefinitions(root, this.delegate);
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { //取出root下面的子节点。一个一个的bean NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; //判断当前节点是否是命名空间,如果是命名空间则交给命名空间类处理 if (delegate.isDefaultNamespace(ele)) { //普通类型,则开始将Element转换成BeanDefinitions。下面的方法也区分是什么类型:import;alias;bean;beans //一般的spring配置文件根目录只包含上面四种情况,所以下面的方法分别处理 parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
上面方法中从node节点中取出所有子节点,如果是普通的类型则开始解析
parseDefaultElement(ele, delegate);
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { //处理import if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } //处理alias。在SimpleAliasRegistry对象中有一个map结构,将name和alias保存到map里面 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } //处理bean else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } //处理beans else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { //递归调用 doRegisterBeanDefinitions(ele); } }
看到上面的方法心里终于放下心了,已经找到调用真正真正处理的地方了。直接进入处理Bean的地方
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { //将Element转成BeanDefinition。然后将BeanDefinition和BeanName交给BeanDefinitionHolder持有 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. //将BeanDefinition注册到IOC容器中 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
上面代码中
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);就是处理Element的方法,将Element转化成BeanDefinitionsHolder这个对象里面持有beanName和BeanDefinitions。然后就是注册过程。注册过程简单,调用
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());内部就是将beanName和BeanDefinitions关联起来
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); //注册,将beanName和BeanDefinitions放到map中。使用的类:DefaultListableBeanFactory //主要有一下数据: // beanDefinitionMap:beanName作为key,BeanDefinitions作为value //List<String> beanDefinitionNames:list保存beanName //private volatile Set<String> manualSingletonNames保存单例的名字 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
上面已经将xml文件注册成了BeanDefinitions对象并用键值对的方式保存到Map中。下面就是依赖注入的过程。当调用getBean()方法时就会触发依赖注入。在spring源码中经常看到N多个重载方法,一般都是有一个最全参数的重载方法,然后其他方法都是有默认值的调用这个最全参数的方法。在getBean中同样是这样设计的。可以找到getBean的入口方法:
AbstractApplicationContext.getBean(String name)所有的重载方法getBean最后都是doGetBean()。代码如下
AbstractBeanFactory.doGetBean:
protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { //1.如果是FactoryBean这种Bean都是以&开头。所以需要去掉& //2.如果name的别名,则需要在SimpleAliasRegistry里面进行判断获取的真正的那么 final String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. //从缓存中获取对象,避免重复创建 Object sharedInstance = getSingleton(beanName); System.out.println(sharedInstance); if (sharedInstance != null && args == null) { if (logger.isDebugEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.debug("Returning cached instance of singleton bean '" + beanName + "'"); } } /*这里 的 getObjectForBeanInstance 完成 的 是 FactoryBean 的 相关 处理, 以 取得 FactoryBean 的 生产 结果, BeanFactory 和 FactoryBean 的 区别 已经 在前面 讲过, 这个 过程 在后面 还会 详细 地 分析*/ bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // Fail if we're already creating this bean instance: // We're assumably within a circular reference. if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. /* 这里 对 IoC 容器 中的 BeanDefintion 是否 存在 进行检查, 检查 是否 能在 当前 的 BeanFactory 中 取得 需要 的 Bean。 如果 在 当前 的 工厂 中 取 不到, 则 到 双亲 BeanFactory 中 去取; 如果 当前 的 双亲 工厂 取 不到, 那就 顺着 双亲 BeanFactory 链 一直 向上 查找*/ BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // Not found -> check parent. String nameToLookup = originalBeanName(name); if (args != null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } else { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } } if (!typeCheckOnly) { markBeanAsCreated(beanName); } try { //通过beanName获得BeanDefinition对象 //这里 根据 Bean 的 名字 取得 BeanDefinition final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. //检查bean配置是否设置了depend-on属性。即实例A需要先实例B //获取 当前 Bean 的 所有 依赖 Bean, 这样 会 触发 getBean 的 递归 调用, 直到 取 到 一个 没有 // 任何 依赖 的 Bean 为止 String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } /*这里 通过 调用 createBean 方法 创建 Singleton bean 的 实例, 这里 有一个 回 调 函数 getObject, 会在 getSingleton 中 调用 ObjectFactory 的 createBean*/ // Create bean instance. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // Check if required type matches the type of the actual bean instance. // 这里 对 创建 的 Bean 进行 类型 检查, 如果 没有 问题, 就 返回 这个 新 创建 的 Bean, 这个 Bean 已经 //是 包含 了 依赖 关系 的 Bean if (requiredType != null && bean != null && !requiredType.isInstance(bean)) { try { return getTypeConverter().convertIfNecessary(bean, requiredType); } catch (TypeMismatchException ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean; }
相关文章推荐
- spring源码学习之路---深度分析IOC容器初始化过程(四)
- spring源码学习之路---深度分析IOC容器初始化过程(四)
- spring源码学习之路---深度分析IOC容器初始化过程(三)
- Spring源码分析----IOC容器的实现(IoC容器的初始化过程(定位、载入解析、注册))
- spring源码学习之路---深度分析IOC容器初始化过程(四)
- spring源码研究之IoC容器在web容器中初始化过程
- spring源码研究之IoC容器在web容器中初始化过程(转)
- spring源码研究之IoC容器在web容器中初始化过程
- Spring技术内幕之IOC容器的实现(01)-IOC容器初始化过程
- 【spring源码学习】spring的IOC容器在初始化bean过程
- spring 源码分析--IOC容器初始化七
- Spring IOC 容器源码分析 - 余下的初始化工作
- Spring原理与源码分析系列(三)- Spring IoC容器启动过程分析(下)
- 【Spring】IOC核心源码学习(二):容器初始化过程
- 小编教您Spring源码分析之IoC容器初始化
- Spring IOC容器bean初始化源码分析
- spring 源码分析--IOC容器初始化五
- spring源码分析-web容器初始化过程解析1
- 【Spring源码解析】—— 结合SpringMVC过程理解IOC容器初始化之注解部分探究
- Spring源码分析(二)-Spring IoC容器的初始化No.2