IoC容器初始化过程(一):Resource定位

BeanDefinition

application.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans 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">
     <!--XML方式配置Bean-->  
    <bean id="student" class="com.springleanring.Student">
    </bean>
</beans>

Spring会把配置在XML中的bean信息转为IoC容器内部的数据结构,这个数据结构就是 BeanDefinition,也就是说在Spring的XML配置文件中存在的元素,在容器初始化时,会读取这些XML文件,从中解析,然后封装为BeanDefinition对象。

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
     
    String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
    
    String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;

    int ROLE_APPLICATION = 0;

    int ROLE_SUPPORT = 1;

    int ROLE_INFRASTRUCTURE = 2;
    
    void setParentName(@Nullable String parentName);

    @Nullable
    String getParentName();
    
    void setBeanClassName(@Nullable String beanClassName);//设置bean的className

    @Nullable
    String getBeanClassName();

    void setScope(@Nullable String scope);//设置bean的scope属性

    @Nullable
    String getScope();

    void setLazyInit(boolean lazyInit);//设置lazyInit
    
    boolean isLazyInit();//

    void setDependsOn(@Nullable String... dependsOn);
    
    //省略了其他方法
}

IoC容器初始化过程

(1)Resource定位过程

Resource定位指的是BeanDefinition的资源定位,这个过程类似于容器寻找数据的过程。

以在application.xml中配置的为例:

BeanDefinition是元素在IoC容器内部的存在形式,对BeanDefinition的定位也就是对元素所在资源文件application.xml的定位,定位到资源文件的位置 才能从application.xml中读取信息,因此IoC容器初始化时首先要完成对Resource的定位。

(2)BeanDefinition的载入

这个载入过程是把用户定义好的Bean表示成IoC容器的内部数据结构,这个容器内部数据结构就是BeanDefinition,也就是POJO对象在IoC容器中的抽象,通过BeanDefinition IoC容器可以方便的对Bean进行管理。

(3)向IoC容器注册这些BeanDefinition

BanDefinition注册指的是将第(2)步中得到的每一个BeanDefinition注册到一个HashMap中,IoC容器通过这个HashMap来持有这些BeanDefinition数据。

Spring IoC中的两个主要容器

IoC容器可以理解为spring管理Bean的地方。

(1)BeanFactory

是一个接口,定义了IoC容器最基本的功能规范,提供容器最基本的功能。如getBean方法,通过该方法可以从容器中获取Bean。

public interface BeanFactory {

    /**
     * 通过name获取容器中管理的bean
     * @param name bean的name
     * @return bean的一个实例
     */
    Object getBean(String name) throws BeansException;
    
    <T> T getBean(String name, @Nullable Class<T> requiredType)throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;
     
    <T> T getBean(Class<T> requiredType) throws BeansException;
     
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
    
    boolean containsBean(String name);//是否包含某个bean
      
    //是否是单 模式
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    //是否是原型模式
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    //省略了其他方法
}

(2)ApplicationContext

如果说BeanFactory是IoC容器的基本形式,那么ApplicationContext就是IoC容器的高级表现形式,它除了包含BeanFactory中的基本方法,还提供了一些其他的方法。

ApplicationContext同样是一个接口,它继承了ListableBeanFactory接口,ListableBeanFactory继承了BeanFacotry接口,因此ApplicationContext包含了BeanFactory接口中的所有方法。

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

    String getId();

    String getApplicationName();
    
    String getDisplayName();

    long getStartupDate();

    ApplicationContext getParent();
    
    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;

}

常用的实现ApplicationContext接口的三种容器:

FileSystemXmlApplicationContext:从文件系统载入Resource

ClassPathXmlApplicationContext:从class path载入Resource

XmlWebApplicationContext:在web容器中载入Resource

通过ApplicationContext从容器中获取一个bean:

//使用FileSystemXmlApplicationContext加载配置文件
ApplicationContext context=new FileSystemXmlApplicationContext("classpath*:/spring/applicationContext.xml");
//ClassPathXmlApplicationContext加载
//ApplicationContext context=new ClassPathXmlApplicationContext("classpath*:/spring/applicationContext.xml");
//从容器中获取bean
Student student= (Stuedent) context.getBean("student");//根据id从配置文件中获取bean
student.getName();

那么就以FileSystemXmlApplicationContext为例,分析ApplicationContext的实现过程。

1.FileSystemXmlApplicationContext

public FileSystemXmlApplicationContext {
   }

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

   /** 
    * 构造函数:需要传入XML资源文件的路径 
    */
   public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
       this(new String[]{configLocation}, true, (ApplicationContext)null);
   }
   
   /** 
    * 构造函数:传入多个资源文件 
    */
   public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
       this(configLocations, true, (ApplicationContext)null);
   }

   /** 
    * 构造函数 
    * @param configLocations 资源文件数组 
    * @param parent 双亲IoC容器 
    */
   public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
       this(configLocations, true, parent);
   }
   
   /** 
    * 构造函数 
    * @param configLocations 资源文件路径 
    * @param refresh 是否调用refresh方法载入BeanDefinition 
    */ 
   public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
       this(configLocations, refresh, (ApplicationContext)null);
   }

   /** 
    * 构造函数,其他构造函数最终调用的都是该函数
    * @param configLocations 资源文件路径 
    * @param refresh 是否调用refresh方法载入BeanDefinition 
    */ 
   public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
       super(parent);
       this.setConfigLocations(configLocations);
       if(refresh) {
           this.refresh();//分析容器初始化的一个重要入口
       }

   }
   /**
    * 通过资源文件路径获取Resource对象,返回的是一个FileSystemResource对象,通过这个对象可以进行相关I/O操作,完成BeanDefinition定位 
    * @param path 资源文件路径 
    * @return Resource spring中的资源对象 
    */ 
   protected Resource getResourceByPath(String path) {
       if(path.startsWith("/")) {
           path = path.substring(1);
       }

       return new FileSystemResource(path);
   }
  • 从第二个构造函数开始,实际上都是调用的 FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)这个构造函数。
  • 对BeanDefinition资源定位的过程是从refresh方法开始的,refresh方法是分析容器初始化的重要入口。

2.AbstractApplicationContext

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
 public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            // 调用obtainFreshBeanFactory()方法获取IoC容器,容器的创建和对Resource的定位就在该方法中
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if(this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }
}
/**
 * 获取BeanFactory
 */
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        //refreshBeanFactory中会创建IoC容器
        this.refreshBeanFactory();
        //获取创建的IoC容器
        ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
        if(this.logger.isDebugEnabled()) {
            this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
        }

        return beanFactory;
    }

public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
  • refreshBeanFactory()方法,在AbstractApplicationContext中,该方法是一个抽象方法,具体的实现由AbstractApplicationContext的子类AbstractRefreshableApplicationContext来实现,该方法中创建了IoC容器BeanFacotry对象。
  • getBeanFactory()方法也是抽象方法,在AbstractRefreshableApplicationContext中实现,其实就是返回refreshBeanFactory方法中创建的BeanFacotry对象。

3.AbstractRefreshableApplicationContext

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {

protected final void refreshBeanFactory() throws BeansException {
        //如果已经建立了BeanFactory,销毁并关闭BeanFactory  
        if(this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }

        try {
           //构造了一个BeanFactory,这里使用DefaultListableBeanFactory实现  
            DefaultListableBeanFactory beanFactory = this.createBeanFactory();
            beanFactory.setSerializationId(this.getId());
            this.customizeBeanFactory(beanFactory);
            //载入BeanDefinition 
            this.loadBeanDefinitions(beanFactory);
            Object var2 = this.beanFactoryMonitor;
            synchronized(this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        } catch (IOException var5) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
        }
    }
    
    /** 
     * 创建DefaultListableBeanFactory的地方 
     * getInternalParentBeanFactory()的具体实现可以参看AbstractApplicationContext的实现 
     * 
     * @return 
     */
    protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
    }
    
    /** 
     * 获取创建的IoC容器
     */
    public final ConfigurableListableBeanFactory getBeanFactory() {
        Object var1 = this.beanFactoryMonitor;
        synchronized(this.beanFactoryMonitor) {
            if(this.beanFactory == null) {
                throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext");
            } else {
                return this.beanFactory;
            }
        }
    }
    
    /** 
     * 抽象方法,载入BeanDefinition,由子类AbstractXmlApplicationContext实现
     * 
     * @param beanFactory 
     * @throws BeansException 
     * @throws IOException 
     */  
    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)  
            throws BeansException, IOException;
}
  • refreshBeanFactory()方法中调用了createBeanFactory()方法完成了容器的创建,使用的是DefaultListableBeanFactory类型的容器。
  • 创建容器之后,调用了loadBeanDefinitions()方法载入BeanDefinition,该方法由子类AbstractXmlApplicationContext实现。
  • loadBeanDefinitions()方法中包含了Resource资源定位的过程。

4.AbstractXmlApplicationContext

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {

    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        //创建XmlBeanDefinitionReader对象,通过回调设置到BeanFactory中 
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        //设置ResourceLoader
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        //启动Bean定义信息载入的过程,委托给beanDefinitionReader完成  
        this.initBeanDefinitionReader(beanDefinitionReader);
        //载入BeanDefinition
        this.loadBeanDefinitions(beanDefinitionReader);
    }
    
       protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {  
           reader.setValidating(this.validating);  
       }  
  
       /** 
        * 载入BeanDefinition 
        * @param reader 
        * @throws BeansException 
        * @throws IOException 
        */  
       protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {  
           //获取Reource类型的配置文件的地址
           Resource[] configResources = getConfigResources();  
           if (configResources != null) {  
               //调用AbstractBeanDefinitionReader中的loadBeanDefinitions方法 
               reader.loadBeanDefinitions(configResources);
               }  
           //获取字符串类型的配置文件的地址
           String[] configLocations = getConfigLocations();  
           if (configLocations != null) {  
               //调用AbstractBeanDefinitionReader中的loadBeanDefinitions方法 
               reader.loadBeanDefinitions(configLocations);  
           }  
       }  
  
       protected Resource[] getConfigResources() {  
           return null;  
       }
}
  • loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中首先创建了XmlBeanDefinitionReader对象,从名称上可以猜到,它可以读取XML中定义的bean信息。
  • loadBeanDefinitions(XmlBeanDefinitionReader reader)方法中又调用了XmlBeanDefinitionReader中的loadBeanDefinitions()方法,XmlBeanDefinitionReader是AbstractBeanDefinitionReader的子类,该方法在AbstractBeanDefinitionReader中实现。

5.AbstractBeanDefinitionReader

载入BeanDefinition时:

  • AbstractBeanDefinitionReader中实现的是参数类型是String的方法。
  • 如果传入的参数是Resource类型的,先进入到loadBeanDefinitions(Resource… resources)方法中,在该方法中又会调用XmlBeanDefinitionReader中的loadBeanDefinitions方法,也就是如果参数类型是Resource,是由XmlBeanDefinitionReader实现的。

查看上面FileSystemXmlApplicationContext用法的例子,传入的参数是资源文件的地址,因此调用的是AbstractBeanDefinitionReader的loadBeanDefinitions(String… locations)方法:

FileSystemXmlApplicationContext("classpath*:/spring/applicationContext.xml");

AbstractBeanDefinitionReader类中实现的方法为例:

public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {

    /** 
    * 载入参数类型是Resource对象的BeanDefinition,由子类XmlBeanDefinitionReader实现
    */ 
    @Override
    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        Assert.notNull(resources, "Resource array must not be null");
        //对载入Bean的数量进行统计
        int counter = 0;
        //遍历整个Resource集合,从每个集合中载入所有的BeanDefinition
        for (Resource resource : resources) {
            //loadBeanDefinitions方法是一个接口方法,在XmlBeanDefinitionReader中有具体的实现  
            counter += loadBeanDefinitions(resource);
        }
        return counter;
    }
    
   /** 
    * 根据配置文件的地址,载入BeanDefinition
    */ 
    @Override
    public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(location, null);
    }

    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
       //获取ResourceLoader对象,实际使用的是DefaultResourceLoader  
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }
       //这里对Resource的路径模式进行解析,得到Resource集合,这些集合指向我们已经定义好的BeanDefinition信息
        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                //调用DefaultResourceLoader的getResource完成Resource定位,将传入的资源文件的地址,转为Resource对象
                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;
        }
    }

   /** 
    * 根据配置文件的地址,载入BeanDefinition
    */ 
    @Override
    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int counter = 0;
        for (String location : locations) {
            counter += loadBeanDefinitions(location);
        }
        return counter;
    }
}
  • 在loadBeanDefinitions(String location, @Nullable Set actualResources)方法中定义了ResourceLoader对象,由ResourceLoader完成资源的加载。ResourceLoader其实是使用了DefaultResourceLoader对象的getResource方法完成Resource对象的定位。
  • 在loadBeanDefinitions(String location, @Nullable Set actualResources)方法中根据location资源文件地址将资源文件转为了Resource对象,然后还是调用的XmlBeanDefinitionReader中的loadBeanDefinitions(Resource resource)完成BeanDefinition的载入。

6.DefaultResourceLoader

进入DefaultResourceLoader看一下如何根据配置文件地址完成对Resource的定位:

public class DefaultResourceLoader implements ResourceLoader {  
     public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        Iterator var2 = this.protocolResolvers.iterator();

        Resource resource;
        do {
            if(!var2.hasNext()) {
                //处理带有/反斜杠的resource  
                if(location.startsWith("/")) {
                    return this.getResourceByPath(location);
                }
                //处理带有calsspath标识的resource  
                if(location.startsWith("classpath:")) {
                    return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
                }

                try {
                    //处理URL标识的resource  
                    URL url = new URL(location);
                    return (Resource)(ResourceUtils.isFileURL(url)?new FileUrlResource(url):new UrlResource(url));
                } catch (MalformedURLException var5) {
                    return this.getResourceByPath(location);
                }
            }

            ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
            resource = protocolResolver.resolve(location, this);
        } while(resource == null);

        return resource;
    }
    
    protected Resource getResourceByPath(String path) {
        return new DefaultResourceLoader.ClassPathContextResource(path, this.getClassLoader());
    }
}

到此,IoC容器对Resource的定位过程已经完成。

最后简单总结一下重点流程:

1.初始化时需要创建容器BeanFactory,默认使用的是DefaultListableBeanFactory类型的容器。 2.容器创建之后,会调用loadBeanDefinitions()方法载入BeanDefinition. 载入过程中的流程: (1)创建XmlBeanDefinitionReader对象,为读取XML资源文件中定义的bean信息做准备。 (2)为XmlBeanDefinitionReader对象设置ResoucrLoader,用于资源文件的加载。 (3)调用XmlBeanDefinitionReader父类AbstractBeanDefinitionReader中实现的loadBeanDefinitions()方法,传入资源文件对象或文件地址,在这一步中,ResourceLoader对象实际使用的是DefaultResourceLoader。 (4)在DefaultResourceLoader中完成了对Resource的定位。

参考:

spring技术内幕:深入解析spring架构与设计原理

竹叶青1986:Spring源码阅读之IoC容 初始化1 – Resource定位

IoC容器初始化过程(一):Resource定位 | SHAN (shan-ml.github.io)

请登录后发表评论

    没有回复内容