Spring-IOC源码解读2.1-BeanDefinition的Resource定位

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring-IOC源码解读2.1-BeanDefinition的Resource定位相关的知识,希望对你有一定的参考价值。

Spring通过ResourceLoader来处理得到的Resource。我们先看下前面提到的ClassPathXmlApplicationContext 类定义:

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {

    private Resource[] configResources;

    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();
    }
    //这是应用于类路径上的Resource实现,这个getResource是在BeanDefinitionReader类(AbstractXmlApplicationContext)的loadBeanDefinition方法中调用的,
//loadBeanDefinition采用了模板方法设计模式,具体的实现实际是由各个子类来实现的。 @Override protected Resource[] getConfigResources() { return this.configResources; } }

refresh()方法会开始初始化容器,在refresh()方法中,准备好上下文之后通过obtainFreshBeanFactory()方法获取beanFactory,

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

进入AbstractRefreshableApplicationContext类的refreshBeanFactory()方法:

protected final void refreshBeanFactory() throws BeansException {
        //如果已经存在beanfactory则先销毁之后再重新创建
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //通过createBeanFactory()方法创建一个Ioc容器供容器使用,可以看到这个容器的类型是DefaultListableBeanFactory 
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            // 启动loadBeanDifinitions来载入BeanDifinition,因为有多种载入方式,这里通过一个抽象函数把具体的实现委托给子类来完成
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

那么ClassPathXmlApplicationContext在哪里如何调用BeanDefinitionLoader来完成对BeanDefinition的读入的呢? Spring设计的时候解耦的非常好,实际上BeanDefinition的定位,读入,注册过程是分开进行的。通过eclipse工具观察调用栈可以看到在ClassPathXmlApplicationContext的父类AbstractXmlApplicationContext的loadBeanDefinitions(XmlBeanDefinitionReader reader)方法中完成了资源文件的获取与读入。

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

BeanDefinition的具体载入过程委托给了AbstractRefreshableApplicationContext的子类完成,这里的实现就是在子类AbstartactXmlApplication类中的loadBeanDefinition()方法中完成:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 为指定的BeanFactory创建一个XmlBeanDefinitionReader 
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // 使用此上下文的资源加载环境配置BeanDefinitionReader,由于AbstartactXmlApplication继承了DefaultResourceLoader,所以这里的ResourceLoader传的是this
        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);
    }

具体的实现在AbstartctBeanDefinitionReader中:

public int loadBeanDefinitions(String location, 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");
        }
        //这里对ResourceLoader的路径模式进行解析,比如我们设定的各种Ant格式的路径定义,得到需要的resource集合,即我们已经定义好的BeanDefinition信息,可以是多个文件
        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                //调用DefaultResourceLoader的getResource完成具体的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;
        }
    }    

我们看下DefaultResourceLoader类中getReource()方法:

public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
                //处理带有classpath标识的resource
        if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
            try {
                // 处理带有url标识的resource
                URL url = new URL(location);
                return new UrlResource(url);
            }
            catch (MalformedURLException ex) {
                //如果既不是classpath也不是url则交给getResourceByPath()方法完成资源加载,这个一般由子类实现
                return getResourceByPath(location);
            }
        }
    }

前面我们看到getResourceByPath()会被子类ClassPathXMLApplicationContext实现,这个方法返回的是一个FileSystemResource对象,通过这个对象Spring就可以进行相关的IO操作,完成BeanDefinition的定位。分析到这里我们已经可以得出它实现的就是一个对path进行解析然后生成个FileSystemResource对象返回的过程。到这里就已经完成了Resource的定位过程,在BeanDefinition定位完成的基础上我们就可以通过返回的Resource对象进行BeanDefinition的载入了。

 


以上是关于Spring-IOC源码解读2.1-BeanDefinition的Resource定位的主要内容,如果未能解决你的问题,请参考以下文章

Spring-IOC源码解读1-整体设计

Spring-IOC源码解读3-依赖注入

Spring-IOC源码解读2-容器的初始化过程

Spring:源码解读Spring IOC原理

二:Spring-IOC源码

Spring-IOC学习笔记-01初识