Spring源码学习【二】IOC容器的初始化(一)Resource定位

目录

一、总览

二、源码分析

1. refresh

2. obtainFreshBeanFactory

3. refreshBeanFactory

4-5. loadBeanDefinitions

6. loadBeanDefinitions

7. getResources

8. getResource


一、总览

在使用IOC容器之前,需要定义一个Resource来定位容器BeanDefinition的资源文件,Resource类继承关系如图1所示,参考使用XmlBeanFactory 和DefaultListableBeanFactory两个IOC容器时,均使用了ClassPathRescource作为BeanDefinition数据源,如下所示:

ClassPathResource resource = new ClassPathResource("beans.xml");
图1 Resource类继承关系

我们常用的ApplicationContext容器为我们提供了一系列加载不同Resource的功能,比如FileSystemApplicationContext、ClassPathXmlApplicationContext、XmlWebApplicationContext等,下面我们以FileSytemXmlApplicationContext为例,看一看ApplicationContext的Resource定位过程。

FileSystemApplicationContext的类继承关系图请参考Spring源码学习【一】初识IOC容器

其继承自AbstractXmlApplicationContext,IOC容器的功能由其父类实现,其主要是扩展了从文件系统读取BeanDefinition配置文件的功能,体现在覆盖了DefaultResourceLoader的getResourceByPath方法(参考 Spring源码学习【一】初识IOC容器)。

在FileSystemXmlApplicationContext的构造方法中,调用了refresh()方法来启动IOC容器的初始化,这是整个IOC容器初始化的入口,具体的调用的过程如图2所示,下面从源码的角度对这个过程进行分析。

图2 refresh() 调用时序图

二、源码分析

1. refresh

refresh()方法在FileSystemXmlApplicationContext的构造方法中调用,这一方法定义在AbstractApplicationContext中,启动了IOC容器的初始化过程,如下所示:

public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
    /**
     * 创建一个应用上下文,根据应用环境解析路径,并启动refresh()过程
     */
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }
}

2. obtainFreshBeanFactory

obtainFreshBeanFactory用于通知子类刷新内部的bean factory,其中调用了refreshBeanFactory方法,这一方法在AbstractApplicationContext中未给出具体实现,留给其子类实现,最终调用的为其子类AbstractRefreshableApplicationContext的refreshBeanFactory方法,代码如下:

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements Configu-rableApplicationContext {
    ……
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            ……
            // 通知子类刷新内部的bean factory
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            ……
        }
    }

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }
    protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
}

3. refreshBeanFactory

refreshBeanFactory由AbstractRefreshableApplicationContext类实现,且声明为final方法,不可以被覆盖。在这个方法中,首先判断如果已经创建了beanFactory,则销毁bean并关闭beanFactory,然后创建一个新的DefaultListableBeanFactory作为应用上下文的IOC容器并由当前类对象持有这个beanFactory,同时调用loadBeanDefinitions方法载入BeanDefinition。这里的loadBeanDefinitions是一个抽象方法,留给其子类实现。

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext{
    ……
    @Override
    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) { // 若已创建则销毁bean关闭beanFactory
            destroyBeans();
            closeBeanFactory();
        }
        // 创建一个新的DefaultListableBeanFactory作为应用上下文的IOC容器
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            // 载入BeanDefinition
            loadBeanDefinitions(beanFactory);
            // 由当前类对象持有这个beanFactory
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }
    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException;
}

4-5. loadBeanDefinitions

loadBeanDefinitions由AbstractXmlApplicationContext实现,在这个方法中,首先创建了一个XmlBeanDefinitionReader对象,并将这个reader回调给由父类创建的DefaultListableBeanFactory对象,然后对reader进行了一系列配置,最后调用了reader的loadBeanDefinitions方法,代码如下所示:

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplication-Context {
    ……
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 为给定的beanFactory创建一个XmlBeanDefnitionReader
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        // 用当前上下文的资源加载环境配置reader
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        // 初始化reader
        initBeanDefinitionReader(beanDefinitionReader);
        // 启动beanDefinition的加载
        loadBeanDefinitions(beanDefinitionReader);
	}

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

注:

beanDefinitionReader.setResourceLoader(this);

将这个ApplicationContext对象作为了reader的ResourceLoader,根据之前的分析我们能够知道,AbstractXmlApplicationContext间接继承了DefaultResourceLoader,而DefaultResourceLoader又实现了ResourceLoader接口,所以整个继承关系中的ApplicationContext类均为ResourceLoader类型的实例,在最后调用resourceLoader的getResource方法时实则调用了ApplicationContext的getResourceByPath方法,这就是为什么FileSystemXmlApplicationContext重写了getResourceByPath方法就可以实现从文件系统读取XML格式的配置文件。

6. loadBeanDefinitions

这一步调用了XmlBeanDefinitionReader父类AbstractBeanDefinitionReader的loadBeanDefinitions方法,这里有一系列loadBeanDefinitions方法的重载,在最终的调用方法中我们可以看到,对resourceLoader的类型进行了判断,如果resourceLoader是ResourcePatternResolver类型的实例则以通配符模式定义的路径定位资源,否则直接通过路径定位资源。

这里的resourceLoader正是上一步为reader设置的ApplicationContext实例,如图3所示

图3 AbstractApplicationContext 类图

AbstractAppliactionContext实现了ResourcePatternResolver接口,作为ResourcePatternResolver类型的resourceLoader实例对象使用,并调用了getResources方法来获得Resource资源对象,代码如下所示:

public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {
    ……
    @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);
    }

    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) {
            // 通配符模式匹配方式
            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 {
            // 通过绝对路径获取单个资源
            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
    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;
    }
}

这里调用的getResources方法是定义在ResourcePatternResolver接口中的方法,具体由PathMatchingResourcePatternResolver类实现,在AbstractApplicationContext中持有PathMatchingResourcePatternResolver实例,因此这一过程中调用的getResources实际为PathMatchingResourcePatternResolver对象的getResources方法。

7. getResources

getResources方法由PathMatchingResourcePatternResolver实现,代码如下:

public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
    ……
    @Override
    public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
        // 类路径资源,可能有多个资源文件	       
  
        if(getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                // 获取所有可匹配该包含’?’或’*’的类路径模式的资源
                return findPathMatchingResources(locationPattern);
            }
            else {
                // 获取所有给定的类路径资源
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
            }
        }
        else {
            int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :locationPattern.indexOf(':') + 1);
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                // 获取所有可匹配该包含’?’或’*’的类路径模式的资源
                return findPathMatchingResources(locationPattern);
            }
            else {
                // 获得给定路径的单个资源
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }
}

8. getResource

最终调用了resourceLoader的getResource方法,并在其中调用了getResourceByPath方法。如下为DefaultResourceLoader的实现,但在FileSystemXmlApplicationContext的实现中,实际上调用了被覆盖的getResourceByPath方法,从而实现了从文件系统读取资源文件的功能。

public class DefaultResourceLoader implements ResourceLoader {
    ……
    @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 {
                // 尝试将路径解析为URL路径
                URL url = new URL(location);
                return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlRe-source(url));
            }
            catch (MalformedURLException ex) {
                // 非URL路径,以普通路径形式解析
                return getResourceByPath(location);
            }
        }
    }

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

至此,FileSystemXmlApplicationContext就完成了FileSystemResource的定位工作,有了这个Resource,下一步就可以进行BeanDefinition的载入和注册过程了。

猜你喜欢

转载自blog.csdn.net/greedystar/article/details/81185714