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

来源:互联网 发布:自动电话营销软件 编辑:程序博客网 时间:2024/06/07 01:07

基本概念

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.javapublic 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.javapublic 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.javapublic 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.javaConfigurableListableBeanFactory 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.javaprotected 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.javapublic 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);orResource 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 0