【Spring Framework 深入】—— IoC容器初始化 -> Bean定义资源的Resource定位

文章探讨了Spring框架中ApplicationContext的初始化过程,重点在于Bean定义资源的Resource定位。从BeanFactory和ApplicationContext的区别,到ApplicationContext的特性,如支持国际化、资源访问和应用事件。接着详细阐述了IoC容器初始化的步骤,包括构造函数的调用、BeanDefinition的Resource定位,并分析了AbstractApplicationContext和其子类如何通过委托模式完成这一过程。
摘要由CSDN通过智能技术生成

基本概念

ApplicationContext 继承体系

本文主要关注ApplicationContext的继承体系,至于BeanFactory的分支,以后再研究。
这里写图片描述

BeanFactory or ApplicationContext?

BeanFactory和ApplicationContext都是实现IoC容器的基础接口。Application是BeanFactory的子接口,包含了BeanFactory的功能,同时增加了对Transactions和AOP的支持。所以官方更推荐开发者使用ApplicationContext及其子类实现IoC容器。特别地,Spring在实现时,大量使用ApplicationContext实现BeanPostProcessor extension point。

官方文档有如下阐述:
The BeanFactory provides the underlying basis for Spring’s IoC functionality but it is only used directly in integration with other third-party frameworks and is now largely historical in nature for most users of Spring.
只有在与第三方框架集成时,才推荐使用BeanFactory。
BeanFactory vs ApplicationContext

org.springframework.context.Interface ApplicationContext

All Superinterfaces:(继承接口)
ApplicationEventPublisher, BeanFactory, EnvironmentCapable, HierarchicalBeanFactory, ListableBeanFactory, MessageSource, ResourceLoader, ResourcePatternResolver
ApplicationContext实现了上述接口,丰富了基本IoC容器(BeanFactory)的行为,可以说是一个高级的IoC容器。
从ApplicationContext接口的实现,我们看出其特点:
1. 支持信息源,可以实现国际化。(实现MessageSource接口)
2. 访问资源。(实现ResourcePatternResolver接口)
3. 支持应用事件。(实现ApplicationEventPublisher接口)
它的常用具体类有ClasspathXmlApplicationContext和FileSystemXmlApplicationContext,Web 项目方面有XmlWebApplicationContext。

IoC容器的初始化

IoC容器的初始化主要包括BeanDefinition的Resource定位、载入解析和注册这三个基本的过程。我们以ApplicationContext为例讲解。由于篇幅过大,这篇文章先详细讲解Resource定位
这里写图片描述

创建:

ApplicationContext =new FileSystemXmlApplicationContext(xmlPath);

构造函数:分3步,分别是调用父类构造函数、setConfigLocations和refresh

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)  
            throws BeansException {    
    super(parent);  
    setConfigLocations(configLocations);  
    if (refresh) {  
        refresh();  
    }  
} 

1.super(parent)的作用是为容器设置Bean资源加载器,通过debug,可知实际是由其父类AbstractApplicationContext 完成设置。注意,从这里可以看到AbstractApplicationContext 继承了DefaultResourceLoader,所以实际上它自身也作为资源加载器。

AbstractApplicationContext.java

public abstract class AbstractApplicationContext extends DefaultResourceLoader  
        implements ConfigurableApplicationContext, DisposableBean 

public AbstractApplicationContext(ApplicationContext parent) {
    this();
    setParent(parent);
}        
public AbstractApplicationContext() {
    this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}

2.接下来,setConfigLocations(configLocations)的作用是设置Bean定义资源文件的路径,实际是由其父类AbstractRefreshableConfigApplicationContext完成设置

AbstractRefreshableConfigApplicationContext.java

public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
        implements BeanNameAware, InitializingBean

//从这里可以看到配置Bean定义资源文件可以使用两种方式,字符串和字符串数组
//字符串会以,; /t/n这些分隔符分割
//location="a.xml,b.xml,..."
public void setConfigLocation(String location) {
        //String CONFIG_LOCATION_DELIMITERS = ",; /t/n";  
       //即多个资源文件路径之间用” ,; /t/n”分隔,解析成数组形式 
        setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
}
//location=new String[]{“a.xml”,”b.xml”,……} 
public void setConfigLocations(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++) {
                // resolvePath为同一个类中将字符串解析为路径的方法  
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
}

3.接下来是开始进行Bean定义资源文件加载,由AbstractApplicationContext的refresh函数完成。
refresh函数是一个模板方法,执行多个方法,而且提供了各(protected)方法的(默认)实现,其子类可以重写它们
模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类(使用protected方法)可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
refresh函数中调用了多个方法,这里先不详细讲解每一个方法,可以先通过英文注释大概了解各方法的作用。

AbstractApplicationContext.java

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()核心调用方法1:obtainFreshBeanFactory函数调用,完成了容器初始化的最重要最基础的功能,Bean定义资源的Resource定位、载入解析和注册。

AbstractApplicationContext.java

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;
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

这里使用了委派设计模式,obtainFreshBeanFactory中调用了两个抽象方法,定义了obtainFreshBeanFactory的算法骨架,实际的行为交给其子类(AbstractRefreshableApplicationContext)实现

AbstractRefreshableApplicationContext.java

    @Override
    protected final void refreshBeanFactory() throws BeansException {
        //如果已经有容器,销毁容器中的bean,关闭容器,以保证在refresh之后使用的是新建立起来的IoC容器
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //创建IoC容器
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            //对IoC容器进行定制化,如设置启动参数,开启注解的自动装配等
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            //调用载入Bean定义的方法,这里又使用了委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

在这个方法中,先判断BeanFactory是否存在,如果存在则先销毁beans并关闭beanFactory,接着创建DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)装载bean
使用了委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器(AbstractXmlApplicationContext)

AbstractXmlApplicationContext.java

@Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        //创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器中去,容器使用该读取器读取Bean定义资源
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        //为Bean读取器设置资源加载器,  
        //AbstractXmlApplicationContext的祖先父类AbstractApplicationContext继承DefaultResourceLoader,
        //因此,容器本身也是一个资源加载器
        //所以,这个资源加载器由始至终都是容器自身
        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);
    }

调用了另一个重载函数loadBeanDefinitions(beanDefinitionReader),委托给了XmlBeanDefinitionReader

AbstractXmlApplicationContext.java

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        //获取Bean定义资源的定位
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        //如果configResources为空,则获取FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源  
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            //XmlBeanDefinitionReader调用其父类AbstractBeanDefinitionReader读取定位的Bean定义资源
            reader.loadBeanDefinitions(configLocations);
        }
    }

这里也使用了委托模式,调用子类的获取Bean定义资源定位的方法(getConfigResources()),该方法在ClassPathXmlApplicationContext中实现,FileSystemXmlApplicationContext默认返回null。

由于FileSystemXmlApplicationContext的getConfigResources返回null,因此程序执行configLocations分支,调用XmlBeanDefinitionReader的父类AbstractBeanDefinitionReader的loadBeanDefinitions(String… locations)方法

AbstractBeanDefinitionReader.java

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

对每一个location调用loadBeanDefinitions,其抽象父类AbstractBeanDefinitionReader定义了方法骨架

AbstractBeanDefinitionReader.java

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        //1.获取在IoC容器初始化过程中设置的资源加载器,调用
        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 {
                //2.将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源(Resource)
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                //3.委派调用其子类XmlBeanDefinitionReader的方法,加载Resource
                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.
            //和上面步骤2一样,获得Resource。实际调用的是DefaultResourceLoader中的getSource()方法定位Resource
            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;
        }
    }

FileSystemXmlApplicationContext本身就是DefaultResourceLoader的实现类
这里写图片描述
意思是,AbstractBeanDefinitionReader中ResourceLoader resourceLoader = getResourceLoader(); 得到的是FileSystemXmlApplicationContext(AbstractApplicationContext)。
还记得在AbstractXmlApplicationContext中beanDefinitionReader.setResourceLoader(this); 为Bean读取器设置的资源加载器,正是AbstractApplicationContext,因为继承了DefaultResourceLoader,因此容器本身也是一个资源加载器

将指定位置的Bean定义资源文件解析为IoC容器封装的资源(Resource)的语句

Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
or
Resource resource = resourceLoader.getResource(location);

资源加载器获取要读入的资源(Resource)
实际上,调用了DefaultResourceLoader的getResource方法获取Resource。

DefaultResourceLoader

@Override
    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        //如果是类路径的方式,那需要使用ClassPathResource 来得到bean 文件的资源对象
        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 new UrlResource(url);
            }
            catch (MalformedURLException ex) {
                // No URL -> resolve as resource path.
                return getResourceByPath(location);
            }
        }
    }
至此,完成了Bean定义资源的Resource定位

总结一下从创建容器之后各个父类方法调用,不然就有点懵逼了!
ApplicationContext
这里写图片描述

接下来,开始Bean定义资源(已封装成Resource)的载入解析

回到XmlBeanDefinitionReader的loadBeanDefinitions方法

XmlBeanDefinitionReader.java

@Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        //将读入的XML资源进行特殊编码处理
        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<EncodedResource>(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();
            }
        }
    }

未完。。。待续
Reference:
Spring Framework Reference Documentation
http://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/htmlsingle/
博客园 —— 牛奶、不加糖
http://www.cnblogs.com/ITtangtang/p/3978349.html#a4

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值