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 源碼分析(八)--容器的功能擴展