Spring 源码分析--容器的功能扩展

Posted 否定之否定

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 源码分析--容器的功能扩展相关的知识,希望对你有一定的参考价值。

    经过前面几篇的分析,相信大家对Spring中容器功能有了简单的了解,在前面的章节中我们一直以BeanFactory接口以及它的默认实现类XmlBeanFactory为例进行分析。但是,Spring中还提供了另一个接口ApplicationContext,用于扩展BeanFactory中现有的功能。

    ApplicationContext和BeanFactory两者都是用于加载Bean的,但是相比之下,ApplicationContext提供了更多的扩展功能,ApplicationContext包含了BeanFactory的所有功能。绝大多数典型的企业应用和系统,ApplicationContext就是你需要使用的。

    首先看看使用这两个不同的类去加载配置文件在写法上的不同。

  • 使用BeanFactory方式加载XML

        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));

  • 使用ApplicationContext方式加载XML

        ApplicationContext bf = new ClassPathXmlApplicationContext("beanFactoryTest.xml"));

一:整体功能分析

    本文以ClassPathXmlApplicationContext作为切入点,开始对整体功能进行分析。

 

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {

    private Resource[] configResources;


    /**
     * Create a new ClassPathXmlApplicationContext for bean-style configuration.
     * @see #setConfigLocation
     * @see #setConfigLocations
     * @see #afterPropertiesSet()
     */
    public ClassPathXmlApplicationContext() {
    }

    /**
     * Create a new ClassPathXmlApplicationContext for bean-style configuration.
     * @param parent the parent context
     * @see #setConfigLocation
     * @see #setConfigLocations
     * @see #afterPropertiesSet()
     */
    public ClassPathXmlApplicationContext(ApplicationContext parent) {
        super(parent);
    }

    /**
     * Create a new ClassPathXmlApplicationContext, loading the definitions
     * from the given XML file and automatically refreshing the context.
     * @param configLocation resource location
     * @throws BeansException if context creation failed
     */
    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

    /**
     * Create a new ClassPathXmlApplicationContext, loading the definitions
     * from the given XML files and automatically refreshing the context.
     * @param configLocations array of resource locations
     * @throws BeansException if context creation failed
     */
    public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
        this(configLocations, true, null);
    }

    /**
     * Create a new ClassPathXmlApplicationContext with the given parent,
     * loading the definitions from the given XML files and automatically
     * refreshing the context.
     * @param configLocations array of resource locations
     * @param parent the parent context
     * @throws BeansException if context creation failed
     */
    public ClassPathXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
        this(configLocations, true, parent);
    }

    /**
     * Create a new ClassPathXmlApplicationContext, loading the definitions
     * from the given XML files.
     * @param configLocations array of resource locations
     * @param refresh whether to automatically refresh the context,
     * loading all bean definitions and creating all singletons.
     * Alternatively, call refresh manually after further configuring the context.
     * @throws BeansException if context creation failed
     * @see #refresh()
     */
    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
        this(configLocations, refresh, null);
    }

    /**
     * Create a new ClassPathXmlApplicationContext with the given parent,
     * loading the definitions from the given XML files.
     * @param configLocations array of resource locations
     * @param refresh whether to automatically refresh the context,
     * loading all bean definitions and creating all singletons.
     * Alternatively, call refresh manually after further configuring the context.
     * @param parent the parent context
     * @throws BeansException if context creation failed
     * @see #refresh()
     */
    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }


    /**
     * Create a new ClassPathXmlApplicationContext, loading the definitions
     * from the given XML file and automatically refreshing the context.
     * <p>This is a convenience method to load class path resources relative to a
     * given Class. For full flexibility, consider using a GenericApplicationContext
     * with an XmlBeanDefinitionReader and a ClassPathResource argument.
     * @param path relative (or absolute) path within the class path
     * @param clazz the class to load resources with (basis for the given paths)
     * @throws BeansException if context creation failed
     * @see org.springframework.core.io.ClassPathResource#ClassPathResource(String, Class)
     * @see org.springframework.context.support.GenericApplicationContext
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     */
    public ClassPathXmlApplicationContext(String path, Class<?> clazz) throws BeansException {
        this(new String[] {path}, clazz);
    }

    /**
     * Create a new ClassPathXmlApplicationContext, loading the definitions
     * from the given XML files and automatically refreshing the context.
     * @param paths array of relative (or absolute) paths within the class path
     * @param clazz the class to load resources with (basis for the given paths)
     * @throws BeansException if context creation failed
     * @see org.springframework.core.io.ClassPathResource#ClassPathResource(String, Class)
     * @see org.springframework.context.support.GenericApplicationContext
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     */
    public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz) throws BeansException {
        this(paths, clazz, null);
    }

    /**
     * Create a new ClassPathXmlApplicationContext with the given parent,
     * loading the definitions from the given XML files and automatically
     * refreshing the context.
     * @param paths array of relative (or absolute) paths within the class path
     * @param clazz the class to load resources with (basis for the given paths)
     * @param parent the parent context
     * @throws BeansException if context creation failed
     * @see org.springframework.core.io.ClassPathResource#ClassPathResource(String, Class)
     * @see org.springframework.context.support.GenericApplicationContext
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     */
    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();
    }


    @Override
    protected Resource[] getConfigResources() {
        return this.configResources;
    }

}

(1)setConfigLocations(configLocations) 语句

public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
        implements BeanNameAware, InitializingBean {

    private String[] configLocations;

    private boolean setIdCalled = false;


    /**
     * Create a new AbstractRefreshableConfigApplicationContext with no parent.
     */
    public AbstractRefreshableConfigApplicationContext() {
    }

    /**
     * Create a new AbstractRefreshableConfigApplicationContext with the given parent context.
     * @param parent the parent context
     */
    public AbstractRefreshableConfigApplicationContext(ApplicationContext parent) {
        super(parent);
    }


    /**
     * Set the config locations for this application context in init-param style,
     * i.e. with distinct locations separated by commas, semicolons or whitespace.
     * <p>If not set, the implementation may use a default as appropriate.
     */
    public void setConfigLocation(String location) {
        setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
    }

    /**
     * Set the config locations for this application context.
     * <p>If not set, the implementation may use a default as appropriate.
     */
    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++) {
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
    }

    /**
     * Return an array of resource locations, referring to the XML bean definition
     * files that this context should be built with. Can also include location
     * patterns, which will get resolved via a ResourcePatternResolver.
     * <p>The default implementation returns {@code null}. Subclasses can override
     * this to provide a set of resource locations to load bean definitions from.
     * @return an array of resource locations, or {@code null} if none
     * @see #getResources
     * @see #getResourcePatternResolver
     */
    protected String[] getConfigLocations() {
        return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
    }

    /**
     * Return the default config locations to use, for the case where no
     * explicit config locations have been specified.
     * <p>The default implementation returns {@code null},
     * requiring explicit config locations.
     * @return an array of default config locations, if any
     * @see #setConfigLocations
     */
    protected String[] getDefaultConfigLocations() {
        return null;
    }

    /**
     * Resolve the given path, replacing placeholders with corresponding
     * environment property values if necessary. Applied to config locations.
     * @param path the original file path
     * @return the resolved file path
     * @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
     */
    protected String resolvePath(String path) {
        return getEnvironment().resolveRequiredPlaceholders(path);
    }


    @Override
    public void setId(String id) {
        super.setId(id);
        this.setIdCalled = true;
    }

    /**
     * Sets the id of this context to the bean name by default,
     * for cases where the context instance is itself defined as a bean.
     */
    @Override
    public void setBeanName(String name) {
        if (!this.setIdCalled) {
            super.setId(name);
            setDisplayName("ApplicationContext ‘" + name + "‘");
        }
    }

    /**
     * Triggers {@link #refresh()} if not refreshed in the concrete context‘s
     * constructor already.
     */
    @Override
    public void afterPropertiesSet() {
        if (!isActive()) {
            refresh();
        }
    }

}

    此函数主要用于解析给定的路径数组,当然,如果数组中包含特殊符号,如${var},那么在resolvePath中会搜寻匹配的系统变量并替换。

 

(2)扩展功能

    设置了路径后,便可以根据路径做配置文件的解析以及各种功能的实现了。可以说refresh函数中包含了几乎ApplicationContext中提供的全部功能,而且此函数中逻辑非常清晰明了,使我们很容易分析对应的层次及逻辑。

org.springframework.context.support.AbstractApplicationContext
@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            //准备刷新的上下文环境
prepareRefresh();
// Tell the subclass to refresh the internal bean factory. //初始化BeanFactory,并进行XML文件读取
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. //对BeanFactory进行各种功能填充
prepareBeanFactory(beanFactory);
try { // Allows post-processing of the bean factory in context subclasses. //子类覆盖方法做额外的处理
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context. //激活各种BeanFactory处理器
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation. //注册拦截Bean创建的Bean处理器,这里只是注册,真正的调用是在getBean时候
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context. //为上下文初始化Message源,即不同语言的消息体,国际化处理
initMessageSource();
// Initialize event multicaster for this context. //初始化应用消息广播器,并放入"applicationEventMulticaster" bean中
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses. //留给子类来初始化其它的Bean
onRefresh();
// Check for listener beans and register them. //在所有注册的bean中查找Listener bean,注册到消息广播中
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons. //初始化剩下的单实例(非惰性的)
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event. //完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
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(); } } }

下面概况一下ClassPathXmlApplicationContext初始化的步骤,并从中解析一下它为我们提供的功能。

  (1)初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证。

      在某种情况下项目的使用需要读取某些系统变量,二这个变量的设置很可能会影响系统的正确性,那么ClassPathXmlApplicationContext为我们提供的这个准备函数就显得非常必要,它可以在Spring启动的时候提前对必须的变量进行存在性验证。

  (2)初始化BeanFactory,并进行XML文件读取。

  之前有提到ClassPathXmlApplicationContext包含着BeanFactory所提供的一切特征,那么这一步骤中将会复用BeanFactory中的配置文件读取解析及其它功能,这一步之后,ClassPathXmlApplicationContext实际上就已经包含了BeanFactory所提供的功能,也就是可以进行Bean的提取等基础操作了。

  (3)对BeanFactory进行各种功能填充。

  @Qualifier与@Autowired应该是大家非常熟悉的注解,那么这两个注解正是这一步骤中增加的支持。

  (4)子类覆盖方法做额外的处理

  Spring之所以强大,为世人所推崇,除了它功能上为大家提供了便利外,还有一方面是它的完美架构,开放式的架构让使用它的程序员很容易根据业务需要扩展已经存在的功能。这种开放式的设计在Spring中随处可见,例如在本例中就提供了一个空的函数实现postProcessBeanFactory来方便程序员在业务上做进一步的扩展。

  (5)激活各种BeanFactory处理器

  (6)注册拦截bean创建的bean处理器,这里只是注册,真正的调用是在getBean时候。

  (7)为上下文初始化Message源,即对不同语言的消息体进行国际化处理。

  (8)初始化应用消息广播器,并放入“applicationEventMulticaster” bean中。

  (9)留给子类来初始化其它的bean。

  (10)在所有注册的bean中查找listener bean,注册到消息广播器中。

  (11)初始化剩下的单实例(非惰性的).

  (12)完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人。

 

二:各功能分析

(1)AbstractApplicationContext 类中 prepareRefresh 方法

  prepareRefresh函数主要是做些准备工作,例如对系统属性及环境变量的初始化及验证。

/**
     * Prepare this context for refreshing, setting its startup date and
     * active flag as well as performing any initialization of property sources.
     */
    protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);

        if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        }

        // Initialize any placeholder property sources in the context environment
        initPropertySources();

        // Validate that all properties marked as required are resolvable
        // see ConfigurablePropertyResolver#setRequiredProperties
        getEnvironment().validateRequiredProperties();

        // Allow for the collection of early ApplicationEvents,
        // to be published once the multicaster is available...
        this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
    }

 

(2)加载BeanFactory

    obtainFreshBeanFactory 方法从字面理解是获取BeanFactory。ApplicationContext是对BeanFactory的功能上的扩展,不但包含了BeanFactory的全部功能,更在其基础上添加了大量的扩展应用,那么obtainFreshBeanFactor正是实现BeanFactory的地方,也就是经过这个函数后ApplicationContext就已经拥有了BeanFactory的全部功能。

(2.1)AbstractApplicationContext 类中 prepareRefresh 方法

/**
     * Tell the subclass to refresh the internal bean factory.
     * @return the fresh BeanFactory instance
     * @see #refreshBeanFactory()
     * @see #getBeanFactory()
     */
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

//AbstractApplicationContext类中的抽象方法
    protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

(2.2)AbstractRefreshableApplicationContext 类中实现 refreshBeanFactory 方法

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {

    private Boolean allowBeanDefinitionOverriding;

    private Boolean allowCircularReferences;

    /** Bean factory for this context */
    private DefaultListableBeanFactory beanFactory;

    /** Synchronization monitor for the internal BeanFactory */
    private final Object beanFactoryMonitor = new Object();


    /**
     * Create a new AbstractRefreshableApplicationContext with no parent.
     */
    public AbstractRefreshableApplicationContext() {
    }

    /**
     * Create a new AbstractRefreshableApplicationContext with the given parent context.
     * @param parent the parent context
     */
    public AbstractRefreshableApplicationContext(ApplicationContext parent) {
        super(parent);
    }


    /**
     * Set whether it should be allowed to override bean definitions by registering
     * a different definition with the same name, automatically replacing the former.
     * If not, an exception will be thrown. Default is "true".
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
     */
    public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) {
        this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding;
    }

    /**
     * Set whether to allow circular references between beans - and automatically
     * try to resolve them.
     * <p>Default is "true". Turn this off to throw an exception when encountering
     * a circular reference, disallowing them completely.
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences
     */
    public void setAllowCircularReferences(boolean allowCircularReferences) {
        this.allowCircularReferences = allowCircularReferences;
    }


    /**
     * This implementation performs an actual refresh of this context‘s underlying
     * bean factory, shutting down the previous bean factory (if any) and
     * initializing a fresh bean factory for the next phase of the context‘s lifecycle.
     */
    @Override
    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

    @Override
    protected void cancelRefresh(BeansException ex) {
        synchronized (this.beanFactoryMonitor) {
            if (this.beanFactory != null)
                this.beanFactory.setSerializationId(null);
        }
        super.cancelRefresh(ex);
    }

    @Override
    protected final void closeBeanFactory() {
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory.setSerializationId(null);
            this.beanFactory = null;
        }
    }

    /**
     * Determine whether this context currently holds a bean factory,
     * i.e. has been refreshed at least once and not been closed yet.
     */
    protected final boolean hasBeanFactory() {
        synchronized (this.beanFactoryMonitor) {
            return (this.beanFactory != null);
        }
    }

    @Override
    public final ConfigurableListableBeanFactory getBeanFactory() {
        synchronized (this.beanFactoryMonitor) {
            if (this.beanFactory == null) {
                throw new IllegalStateException("BeanFactory not initialized or already closed - " +
                        "call ‘refresh‘ before accessing beans via the ApplicationContext");
            }
            return this.beanFactory;
        }
    }

    /**
     * Overridden to turn it into a no-op: With AbstractRefreshableApplicationContext,
     * {@link #getBeanFactory()} serves a strong assertion for an active context anyway.
     */
    @Override
    protected void assertBeanFactoryActive() {
    }

    /**
     * Create an internal bean factory for this context.
     * Called for each {@link #refresh()} attempt.
     * <p>The default implementation creates a
     * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}
     * with the {@linkplain #getInternalParentBeanFactory() internal bean factory} of this
     * context‘s parent as parent bean factory. Can be overridden in subclasses,
     * for example to customize DefaultListableBeanFactory‘s settings.
     * @return the bean factory for this context
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowEagerClassLoading
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
     */
    protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
    }

    /**
     * Customize the internal bean factory used by this context.
     * Called for each {@link #refresh()} attempt.
     * <p>The default implementation applies this context‘s
     * {@linkplain #setAllowBeanDefinitionOverriding "allowBeanDefinitionOverriding"}
     * and {@linkplain #setAllowCircularReferences "allowCircularReferences"} settings,
     * if specified. Can be overridden in subclasses to customize any of
     * {@link DefaultListableBeanFactory}‘s settings.
     * @param beanFactory the newly created bean factory for this context
     * @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
     * @see DefaultListableBeanFactory#setAllowCircularReferences
     * @see DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
     * @see DefaultListableBeanFactory#setAllowEagerClassLoading
     */
    protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
        if (this.allowBeanDefinitionOverriding != null) {
            beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.allowCircularReferences != null) {
            beanFactory.setAllowCircularReferences(this.allowCircularReferences);
        }
    }

    /**
     * Load bean definitions into the given bean factory, typically through
     * delegating to one or more bean definition readers.
     * @param beanFactory the bean factory to load bean definitions into
     * @throws BeansException if parsing of the bean definitions failed
     * @throws IOException if loading of bean definition files failed
     * @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     */
    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
            throws BeansException, IOException;

}

 















以上是关于Spring 源码分析--容器的功能扩展的主要内容,如果未能解决你的问题,请参考以下文章

Spring读源码系列06----容器扩展功能--上

Spring读源码系列07----容器扩展功能--中

Spring读源码系列08----容器扩展功能--下

Spring源码分析(二十二)功能扩展

Spring源码分析带你正视一下Spring祖容器之BeanFactory的原理与功能分析

spring源码解析--事件监听机制的使用和原理解析