Spring源码--IoC容器创建过程

本文详细介绍了Spring框架中的IoC容器创建过程及其核心原理。以Spring 5.0版本为例,深入分析了BeanFactory与ApplicationContext的区别,以及ClassPathXmlApplicationContext类在初始化时的工作流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    我们都知道Spring的核心就是AOP和IoC,Spring官网同时也说明它的思想是:BOP(Bean Oriented Programming)。今天我们就先讲讲IoC容器创建的时候都干了什么东西?原理是什么样的?以Spring5.0代码为例,进行讲解。

    大家都知道BeanFactory是最底层的接口,规定了Bean工厂的基本行为。

public interface BeanFactory {
Object getBean(String name) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
}

    上面为其中一部分源码,我们经常使用的部门,有兴趣的可以自己官网下载,自己看看。主要定义Bean工厂的功能:获得Bean的代理实例、是否包含ByName的Bean(还有很多检查更全面的重载方法)、是否是单例还是原型、获得Bean定义类的类型等等。那我们看一下其结构图。

    我们发现BeanFactory是最底层接口,它有三个子类(指接口),这三个子类都有什么作用呢?HierarchicalBeanFactory:表明层次关系(即继承关系);ListableBeanFactory:表明这些Bean是可以按不同的要求分成对应的集合;AutowireCapableBeanFactory:表明了Bean的自动装配的规则。大家可以看一下源码,一目了然。大家可以看到在Spring中四个最顶层的类:XmlBeanFactory、FileSystemXmlApplicationContext、ClassPathXmlApplicationContext、XmlWebApplicationContext,但是后者三个都是实现了ApplicationContext接口。那么,问题又来了:BeanFactory与ApplicationContext有什么区别呢?

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
      MessageSource, ApplicationEventPublisher, ResourcePatternResolver

    大家是不是就一目了然了,他们有什么区别了。有兴趣的大家可以留言,说说他们具体的区别。

    那言归正传,我们以ClassPathXmlApplicationContext类为例,我们在初始化时,第一步就是创建其实例,那我们直接看他的构造方法中干了什么?

public ClassPathXmlApplicationContext() {
}

public ClassPathXmlApplicationContext(ApplicationContext parent) {
   super(parent);
}

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
   this(new String[] {configLocation}, true, null);
}

public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
   this(configLocations, true, null);
}

public ClassPathXmlApplicationContext(String[] configLocations, @Nullable ApplicationContext parent)
      throws BeansException {

   this(configLocations, true, parent);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
   this(configLocations, refresh, null);
}

public ClassPathXmlApplicationContext(
      String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
      throws BeansException {

   super(parent);
   setConfigLocations(configLocations);
   if (refresh) {
      refresh();
   }
}
    我们一般都是以这样的方式开始实例化:ClassPathXmlApplicationContext context = ClassPathXmlApplicationContext("framework-provider.xml"),我们最终发现其最终调用的就是上图中最后一个构造方法。它做了三件事情:1.super(parent),2.setConfigLocations(configLocations),3.refresh()。那我们就从这三件事情开始逐步分析:

  • super(parent)
    我们发现其正常实现该方法的是在AbstractApplicationContext这个类中。
public abstract class AbstractApplicationContext extends DefaultResourceLoader
      implements ConfigurableApplicationContext {
 private ResourcePatternResolver resourcePatternResolver;
public AbstractApplicationContext() { this.resourcePatternResolver = getResourcePatternResolver(); } public AbstractApplicationContext(@Nullable ApplicationContext parent) { this(); setParent(parent); } protected ResourcePatternResolver getResourcePatternResolver() { return new PathMatchingResourcePatternResolver(this); } @Override public void setParent(@Nullable ApplicationContext parent) { this.parent = parent; if (parent != null) { Environment parentEnvironment = parent.getEnvironment(); if (parentEnvironment instanceof ConfigurableEnvironment) { getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); } } }}
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
    public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {     Assert.notNull(resourceLoader, "ResourceLoader must not be null");     this.resourceLoader = resourceLoader;
   }
}    可以看出来先this(),然后又调用了setParent(parent)方法,而this()里面又调用了getResourcePatternResolver()方法,而这个方法中又把自己给赋值进去了,而其构造方法中却是ResourceLoader这么个类,那为什么可以这样呢?那我们就要先看看AbstractApplicationContext继承的类和实现的接口,看看其结构图。

    很明显我们发现AbstractApplicationContext继承了DefaultResourceLoader类,而DefaultResourceLoader又实现了ResourceLoader接口,看了之后有意思的吧,所以AbstractApplicationContext类不仅是一个IoC容器,还是一个资源加载器

    我们再看setParent(parent)方法,如果我们参数中的parent不为null,会配置好IoC层次关系。这里有兴趣的同学可以看看,其实没有太多的东西。

  • setConfigLocations(configLocations)
    其实这个方法进入到了ConfigurableApplicationContext类中,我们看看这个类一些重要的方法。
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {

   String CONFIG_LOCATION_DELIMITERS = ",; \t\n";

@Nullable
private String[] configLocations;
public void setConfigLocation(String location) {
   setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
}
public void setConfigLocations(@Nullable 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++) {
         this.configLocations[i] = resolvePath(locations[i]).trim();
      }
   }
   else {
      this.configLocations = null;
   }
}
}

    有意思了,我们其实在构造方法里写"abc.xml,cdf.xml" or "abc.xml","cdf.xml"等上述图片分隔符都是可以的,所以大家想加载多个配置文件时,可以写点别的花样。所以不难发现,该方法就是将传入的xml配置文件信息设置成对应的Bean资源路劲,放入到string[]数组中,即定位Bean资源

  • refresh()
    这个方法才是重点,也是最为核心的部门,就算你不清楚其他的方法,其实问题也不大,因为前面的方法都是为这个方法做铺垫的(初始化资源加载器,定位Bean资源路径)。
@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      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()方法中是用了synchronized同步锁的,为什么呢?我们都知道在spring中IoC容器只能有一个,即单例的,所以他为了保证其唯一性,用到了同步锁。首先看看prepaerRefresh()方法干了什么?看名字,我们可以猜出是准备刷新的意思,就是说为刷新之前做了一些准备,做了什么准备呢?直接看代码。

protected void prepareRefresh() {
   this.startupDate = System.currentTimeMillis();
   this.closed.set(false);
   this.active.set(true);

   if (logger.isInfoEnabled()) {
      logger.info("Refreshing " + this);
   }

   // Initialize any placeholder property sources in the context environment
   initPropertySources();

   // Validate that all properties marked as required are resolvable
   // see ConfigurablePropertyResolver#setRequiredProperties
   getEnvironment().validateRequiredProperties();

   // Allow for the collection of early ApplicationEvents,
   // to be published once the multicaster is available...
   this.earlyApplicationEvents = new LinkedHashSet<>();
}
    准备工作:刷新时开始的时间,容器是否关闭属性设置“false”,容器是否活跃设置“true”,初始化一些属性资源等,这里也不细说了。obtainFreshBeanFactory():注释中说明的很清楚,就是告诉子类刷新其内部的Bean Factory,来看看其内部的代码。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
   refreshBeanFactory();
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   if (logger.isDebugEnabled()) {
      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
   }
   return beanFactory;
}
    其实在refreshBeanFactory()方法并不在AbstractApplicationContext类中,而是运用了委派设计模式(其实在BeanFactory中大量用了该模式),在其子类AbstractRefreshableApplicationContext中实现了该方法。
@Override
protected final void refreshBeanFactory() throws BeansException {
   if (hasBeanFactory()) {
      destroyBeans();
      closeBeanFactory();
   }
   try {
      DefaultListableBeanFactory beanFactory = createBeanFactory();
      beanFactory.setSerializationId(getId());
      customizeBeanFactory(beanFactory);
      loadBeanDefinitions(beanFactory);
      synchronized (this.beanFactoryMonitor) {
         this.beanFactory = beanFactory;
      }
   }
   catch (IOException ex) {
      throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
   }
}
    很明显,从这里就可以看出为什么我们总是说IoC容器是单例的,看上面的代码:首先它会判断当前上下文中是否已经有了该容器,如果有先销毁容器中的Bean,最后再关闭该容器。在关闭后,我们又创建了一个BeanFactory。所以这个createBeanFactory()方法我也就不多讲了,里面大家自己看看就可以了;后面又设置了其id属性,customizeBeanFactory(beanFactory)方法作用就是设置了一些容器启动时的一些参数(创建容器后初始化一些参数可以这么理解),loadBeanDefinitions(beanFactory)就是将Bean的定义类加载到容器里了。
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
   // Create a new XmlBeanDefinitionReader for the given BeanFactory.
   XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

   // Configure the bean definition reader with this context's
   // resource loading environment.
   beanDefinitionReader.setEnvironment(this.getEnvironment());
   beanDefinitionReader.setResourceLoader(this);
   beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

   // Allow a subclass to provide custom initialization of the reader,
   // then proceed with actually loading the bean definitions.
   initBeanDefinitionReader(beanDefinitionReader);
   loadBeanDefinitions(beanDefinitionReader);
}

    在该方法中首先我们是创建了一个Bean的读取器,但是它是通过回调将该读取器设置到容器中的,用来读取一些Bean的定义资源;并且将当前环境、资源加载器和资源解析器设置到读取器中;前面你可以说是读取器的一些初始化工作,那么initBeanDefinitionReader(beanDefiniReader)同样也是初始化读取器;loadBeanDefinitions(beanDefinitionReader)就是真正读取器加载Bean的方法了。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
   Resource[] configResources = getConfigResources();
   if (configResources != null) {
      reader.loadBeanDefinitions(configResources);
   }
   String[] configLocations = getConfigLocations();
   if (configLocations != null) {
      reader.loadBeanDefinitions(configLocations);
   }
}
    有没有发现一个很熟悉的地方,string[](configLocations)就是之前setConfigLocation()方法做的事,而Resource[]也是ClassPathXmlApplicationContext中的一个属性。
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
   Assert.notNull(resources, "Resource array must not be null");
   int counter = 0;
   for (Resource resource : resources) {
      counter += loadBeanDefinitions(resource);
   }
   return counter;
}

@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(location, null);
}
    可以看出,其方法返回的是最后加载BeanDefinitions的数量,而真正加载的方法又是上图最下面的loadBeanDefinitions(location, null)
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
   ResourceLoader resourceLoader = getResourceLoader();
   if (resourceLoader == null) {
      throw new BeanDefinitionStoreException(
            "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
   }

   if (resourceLoader instanceof ResourcePatternResolver) {
      // Resource pattern matching available.
      try {
         Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
         int loadCount = loadBeanDefinitions(resources);
         if (actualResources != null) {
            for (Resource resource : resources) {
               actualResources.add(resource);
            }
         }
         if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
         }
         return loadCount;
      }
      catch (IOException ex) {
         throw new BeanDefinitionStoreException(
               "Could not resolve bean definition resource pattern [" + location + "]", ex);
      }
   }
   else {
      // Can only load single resources by absolute URL.
      Resource resource = resourceLoader.getResource(location);
      int loadCount = loadBeanDefinitions(resource);
      if (actualResources != null) {
         actualResources.add(resource);
      }
      if (logger.isDebugEnabled()) {
         logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
      }
      return loadCount;
   }
}

@Override
@Nullable
public ResourceLoader getResourceLoader() {
   return this.resourceLoader;
}
    首先该方法拿到了一个资源加载器,然后将Bean的定义资源解析成多个资源文件,注意了,下面loadBeanDefinitions(resources)方法是调用的子类XmlBeanDefinitions中的方法。现在我们看看getResource(location)中的方法做了什么。
@Override
public Resource getResource(String location) {
   Assert.notNull(location, "Location must not be null");

   for (ProtocolResolver protocolResolver : this.protocolResolvers) {
      Resource resource = protocolResolver.resolve(location, this);
      if (resource != null) {
         return resource;
      }
   }

   if (location.startsWith("/")) {
      return getResourceByPath(location);
   }
   else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
      return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
   }
   else {
      try {
         // Try to parse the location as a URL...
         URL url = new URL(location);
         return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
      }
      catch (MalformedURLException ex) {
         // No URL -> resolve as resource path.
         return getResourceByPath(location);
      }
   }
}

public static final String CLASSPATH_URL_PREFIX = "classpath:";

        其实该方法又是调用类DefaultResourceLoader类中的方法,可以看出,它会根据传入的“location”路径的方式,用不同的方式得到对应Bean的资源对象。这里又有一个问题,其实看代码的时候会有一个“classpath:”和“classpath*:”,那他们有什么区别呢?知道的朋友也可以底下留言。

    前面我们讲到了loadBeanDefinitons(Resource resource),在XmlBeanDefinitionReader类中还有加载类的另一个过程。

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(new EncodedResource(resource));
}

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.getResource());
   }

   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
   if (currentResources == null) {
      currentResources = new HashSet<>(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();
      }
   }
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
   try {
      Document doc = doLoadDocument(inputSource, resource);
      return registerBeanDefinitions(doc, resource);
   }
   catch (BeanDefinitionStoreException ex) {
      throw ex;
   }
   catch (SAXParseException ex) {
      throw new XmlBeanDefinitionStoreException(resource.getDescription(),
            "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
   }
   catch (SAXException ex) {
      throw new XmlBeanDefinitionStoreException(resource.getDescription(),
            "XML document from " + resource + " is invalid", ex);
   }
   catch (ParserConfigurationException ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(),
            "Parser configuration exception parsing XML from " + resource, ex);
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(),
            "IOException parsing XML document from " + resource, ex);
   }
   catch (Throwable ex) {
      throw new BeanDefinitionStoreException(resource.getDescription(),
            "Unexpected exception parsing XML document from " + resource, ex);
   }
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
   return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
         getValidationModeForResource(resource), isNamespaceAware());
}
    这个方法其实就可以看出,我们在加载xml中Bean的资源时,是将其先转化为IO流的形式,而真正的读取过程其实在doLoadBeanDefinitions()方法中。哈哈,看到这里我们是不是熟悉了啊,我们经常解析xml文件时,不就是用的Document进行解析的嘛,我们看看doLoadDocument().loadDocument()方法,当然了还有最重要的registerBeanDefinitons()方法了(注册)。
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
   if (logger.isDebugEnabled()) {
      logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
   }
   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
   return builder.parse(inputSource);
}

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
      throws ParserConfigurationException {

   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
   factory.setNamespaceAware(namespaceAware);

   if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
      factory.setValidating(true);
      if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
         // Enforce namespace aware for XSD...
         factory.setNamespaceAware(true);
         try {
            factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
         }
         catch (IllegalArgumentException ex) {
            ParserConfigurationException pcex = new ParserConfigurationException(
                  "Unable to validate using XSD: Your JAXP provider [" + factory +
                  "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
                  "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
            pcex.initCause(ex);
            throw pcex;
         }
      }
   }

   return factory;
}
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
      @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
      throws ParserConfigurationException {

   DocumentBuilder docBuilder = factory.newDocumentBuilder();
   if (entityResolver != null) {
      docBuilder.setEntityResolver(entityResolver);
   }
   if (errorHandler != null) {
      docBuilder.setErrorHandler(errorHandler);
   }
   return docBuilder;
}
    上面的代码就简单的说明下,他首先创建了一个文档解析器DocumentBuilder,然后又设置一些解析XML的校验。我们看看最后注册时做了什么东西(registerBeanDefinitons())?
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   logger.debug("Loading bean definitions");
   Element root = doc.getDocumentElement();
   doRegisterBeanDefinitions(root);
}
    该方法就是获取了Document文件的根节点,然后进行注册动作。里面注册就不多说了,主要就是将节点信息生成对应的代理对象存到map中,如果有后续,后面会继续更新。







评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值