五、Spring IOC——BeanDefinition的加载

一、BeanDefinition的加载:
在Spring中,实际上是把DefaultListableBeanFactory作为一个默认的功能完整的IoC容器来使用的。我们通过编程式使用IoC容器可以更好地理解其原理:

ClassPathResource res = new ClassPathResource(“beans.xml”);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Reader.loadBeanDefinition(res);

可以看到使用IoC容器主要包含4个步骤:
创建配置文件的抽象资源,包含了BeanDefinition的定义信息。创建一个BeanFactory,这里使用DefaultListableBeanFactory。创建一个载入BeanDefinition的读取器。从定义好的资源位置读入配置信息,解析过程由BeanDefinitionReader来实现。
1)首先,初始化容器包括BeanDefinition的Resource定位,载入和注册三个基本过程。资源定位统一通过Resource接口来完成,Spring实现了多种定位方式,例如文件系统中的信息可以使用FileSystemResource来定位,类路径中的信息可以通过ClassPathResource来定位等。
2)其次,在定位到配置文件后,就会将配置信息转换成BeanDefinition,BeanDefinition就是POJO对象在IoC容器中的抽象表示,通过这种数据结构,使得IoC容器能够对Bean进行管理。
3)第三个过程是向IoC容器注册这些BeanDefinition,这个步骤必须基于BeanDefinition信息来完成。通过调用BeanDefinitionRegistry接口,把载入过程中解析得到的BeanDefinition向IoC容器进行注册。
通过这3步,容器的初始化就完成了,如果使用的是BeanFactory或者开启了懒加载,依赖注入通常发生在第一次调用getBean()向容器索取Bean的时候。而使用ApplicationContext则会在容器初始化的时候就进行依赖注入。

BeanDefinition的加载,位于在refresh方法中obtainFreshBeanFactory方法:

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        return getBeanFactory();
    }

1、定位
在Spring中,资源的定位主要由ResourceLoader模块实现。

五、Spring IOC——BeanDefinition的加载_第1张图片

 

ResourceLoader 的默认实现是DefaultResourceLoader,而我们可以看到,AbstractApplicationContext通过继承DefaultResourceLoader已经具备了读入BeanDefinition的能力。

public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException {
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException(
                "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
    }
    if (resourceLoader instanceof ResourcePatternResolver) {
        // Resource pattern matching available.
        try {
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
            int count = loadBeanDefinitions(resources);
            if (actualResources != null) {
                Collections.addAll(actualResources, resources);
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
            }
            return count;
        } 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 count = loadBeanDefinitions(resource);
        if (actualResources != null) {
            actualResources.add(resource);
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
        }
        return count;
    }
}

查看DefaultResourceLoader重要方法的代码实现:

public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");
    for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
        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);
        }
    }
}

protected Resource getResourceByPath(String path) {
    return new ClassPathContextResource(path, getClassLoader());
}

这是一个模板方法,作用是读取Resource资源, FileSystemXmlApplicationContext类重写了这个方法,返回一个FileSystemResource对象,通过这个对象,Spring可以进行相关的I/0操作,完成BeanDefinition的定位。如果是其他的ApplicationContext,那么会对应生成其他种类的Resource,比如ClassPathResource等。

在refresh()方法中调用了obtainFreshBeanFactory(),追踪这个方法可以发现它调用了refreshBeanFactory() -->loadBeanDefinitions(),这个方法的作用即定位载入Resource,在这个方法中,调用了getResourceByPath(),不同的子类具有不同的定位资源的方式实现。

2、载入和解析
载入的过程,相当于把定义的BeanDefinition在IoC容器中转化成一个Spring内部表示的数据结构的过程。这些BeanDefinition数据在IoC容器中通过一个HashMap来保持和维护。
容器的启动,主要由refresh()完成,这个方法描述了应用上下文的初始化过程。refreshBeanFactory()方法创建了BeanFactory,通过这个方法,可以了解bean载入的过程。

protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        //创建IoC容器,这里使用的是DefaultListableBeanFactory
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(beanFactory);
        //启动对BeanDefinition的载入
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    } catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

// loadBeanDefinitons是一个抽象方法,根据不同的子类实现不同资源的读取和载入。例如AbstractXmlApplicationContext中的实现:

@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);
}

由于AbstractXmlApplicationContext类是通过Xml读入资源的,基于之前configLocations参数,初始化读取器XmlBeanDefinitionReader,并将它在容器中设置好,最后启动这个读取器来完成BeanDefinition在IoC容器中的载入。

3、注册
通过前面的动作,用户定义的BeanDefinition信息已经在IoC容器中建立起了数据结构,但此时这些数据还不能提供IoC容器直接使用,需要在容器中对这些BeanDefinition数据进行注册。在DefaultListableBeanFactory中可以看到持有Bean的容器,是一个HashMap:
private final Map beanDefinitionMap = new ConcurrentHashMap(256);
该类实现了BeanDefinitionRegistry接口,具有注册的功能,通过实现registerBeanDefinition方法,实现注册的具体逻辑,将bean放入Map中。
调用关系如下图:

五、Spring IOC——BeanDefinition的加载_第2张图片

 此时,IoC容器完成了初始化的过程,在容器中已经建立了整个Bean的配置信息,并且这些Bean可以被容器使用了,他们都在beanDefinitionMap里被检索和使用。

你可能感兴趣的:(Spring,spring)