Spring5源碼解析-Spring框架中的單例和原型bean 最近一直有問我單例和原型bean的一些原理性問題,這裡就開一篇來說說的 通過Spring中的依賴註入極大方便了我們的開發。在xml通過<bean>定義(或者通過@Bean在配置類里定義)對象之後,然後只需簡單地使用@Autowired註 ...
Spring5源碼解析-Spring框架中的單例和原型bean
最近一直有問我單例和原型bean的一些原理性問題,這裡就開一篇來說說的
通過Spring中的依賴註入極大方便了我們的開發。在xml
通過<bean>
定義(或者通過@Bean
在配置類里定義)對象之後,然後只需簡單地使用@Autowired註解,就可以使用由Spring上下文管理的每個對象。需要註意的是,所有這些對象在Spring中預設都是單例。
這一次我們會去討論Spring如何來管理這些定義的bean。在第一部分中,我們將講解單例和原型作用域的概念。第二部分中,我們將分析單例和原型作用域之間的依賴關係。其後說一下方法註入。最後專門對相關Spring的代碼來做下分析,具體看看bean是如何構建出來的。
Spring中的bean預設都是單身貴族
Spring使用單例設計模式來管理bean?不完全是。Singleton設計模式假定它們是由Java的類載入器管理的jvm中給定類的唯一一個實例。在Spring中,還是有點不一樣。預設情況下,它們為每個給定的org.springframework.context.ApplicationContext實例存在唯一的一個bean (有點彆扭,也就是可以有多個Spring容器,每一個容器記憶體在唯一bean實例,之前的文章中有涉及例子的)。這意味著如果你有兩個或更多上下文,所有這些上下文都由同一Java的類載入器管理(因為在同一個jvm環境中),則可能會有多個給定bean的實例。唯一需要做到的是必須在每個上下文中定義此bean。講那麼多不如代碼更有說服力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class MultipleContextes { public static void main(String[] args) { try { // retreive two different contexts ApplicationContext firstContext = new FileSystemXmlApplicationContext("/home/bartosz/webapp/src/main/resources/META-INF/applicationContext.xml"); ApplicationContext secondContext = new FileSystemXmlApplicationContext("/home/bartosz/webapp/src/main/resources/META-INF/applicationContext.xml"); // compare the objects from different contexts ShoppingCart firstShoppingCart = (ShoppingCart) firstContext.getBean("shoppingCart"); ShoppingCart secondShoppingCart = (ShoppingCart) secondContext.getBean("shoppingCart"); System.out.println("1. Are they the same ? " + (firstShoppingCart == secondShoppingCart)); // compare the objects from the same context ShoppingCart firstShoppingCartBis = (ShoppingCart) firstContext.getBean("shoppingCart"); System.out.println("2. Are they the same ? "+ (firstShoppingCart == firstShoppingCartBis)); } catch (Exception e) { e.printStackTrace(); } } } |
通過執行此代碼,你應該得到:
1 2 | 1. Are they the same ? false 2. Are they the same ? true |
所以你可以看到,bean只是一個上下文的單例。這就是為什麼你不應該將Spring的單例概念與設計模式中的的單例混合在一起。
但是,如果要為一個定義的bean在一個上下文內可以使用不同的實例,應該怎麼做?很簡單,你應該將此Bean配置為原型作用域:
1 2 | <bean id="shoppingCart" class="com.migo.data.ShoppingCart" scope="prototype"> </bean> |
現在,在運行以前的代碼之後,你可以看到如下輸出:
1 2 | 1. Are they the same ? false 2. Are they the same ? false |
我們已經知道兩個作用域之間的區別。但在哪種情況下我們應該選擇使用單例還是原型?Singleton適用於無狀態的bean,即沒有狀態的bean。比如一個service
,DAO
或者controller
。他們都沒有自己的狀態(舉個簡單的例子,一個函數sin(x)
,這個函數本身就是無狀態的,所以我們現在喜歡的函數式編程也遵循這個理念)。而是根據傳輸的參數執行一些操作(作為HTTP請求參數)。另一方面,我們可以通過狀態bean管理一些狀態。比如購物車bean,假如它是一個單例,那麼兩個不同消費者購買的產品將被放置在同一個對象上。而如果其中一個消費者想要刪除一個產品,另一個消費者就鐵定不高興。這也就是狀態類對象應該是原型。
這裡說點題外話,不能確定時間的保證,未來會出一個用Java的代碼習慣去解析vue的一些東西,內容已經總結完畢,也應用到自己的項目中了,然後得出的一些方法論,為什麼在這裡去說,就是因為vue也是遵循這個無狀態和狀態專門管理的原則的,扯遠了,接著進行下一部分。
將原型放在單例中,反之亦然
通過上面的描述,很多概念都很清楚了吧,但有時候會發生一些更複雜的情況。第一個是在原型bean中放置單例。顯然,如果註入的單例對象真的是一個單例的bean(沒有狀態),這個真的沒一點問題。想象一下,對於我們的購物車,我們需要註入產品服務。此服務只會檢查添加到購物車的產品是否庫存。由於服務沒有狀態,並且會基於在方法簽名中所傳遞的對象進行驗證,因此不存在風險。
另一方面,將原型bean放在單例中需要做更多的工作。我們不能在單例bean中通過使用自動註入(比如@Autowired
註解)註入原型bean。當Spring初始化所有具有依賴關係的單例bean時,這些註入只會執行一次。這也就意味著在以下代碼,ShoppingCart
的實例將始終是相同的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class TestController { private ShoppingCart shoppingCart; public String testAdd(@PathVariable(value="productName") String productName) { Product product = new Product(); product.setName(productName); this.shoppingCart.addProduct(product); LOGGER.debug("ShoppingCart is "+this.shoppingCart); return "test"; } } |
編譯此類併進行一些URL調用:http://localhost:8080/addProduct/ice%20tea,http://localhost:8080/addProduct/milk。你將看到如下輸出的順序:
1 2 3 4 | // after http://localhost:8080/addProduct/ice%20tea ShoppingCart is ShoppingCart {products: [Product {ice tea}]} // after http://localhost:8080/addProduct/milk ShoppingCart is ShoppingCart {products: [Product {ice tea}, Product {milk}]} |
為了在按照我們預想情況下工作(要求不一樣的ShoppingCart
),我們可以通過bean工廠手動獲取ShoppingCart
實例(這樣就可以再一次生成一個不一樣的ShoppingCart
實例了):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class TestController { private ApplicationContext context; public String testAdd(@PathVariable(value="productName") String productName) { Product product = new Product(); product.setName(productName); ShoppingCart shoppingCart = (ShoppingCart) context.getBean("shoppingCart"); shoppingCart.addProduct(product); LOGGER.debug("ShoppingCart is "+shoppingCart); return "test"; } } |
這樣,你就可以日誌中看到,每次調用都會有新的ShoppingCart
實例的生成:
1 2 3 4 | // after http://localhost:8080/addProduct/ice%20tea ShoppingCart is ShoppingCart {products: [Product {ice tea}]} // after http://localhost:8080/addProduct/milk ShoppingCart is ShoppingCart {products: [Product {milk}]} |
方法註入
有沒有別的方法在每次調用都會產生一個新實例?這就是接下來要說的方法註入的技術。它看起來有點像我們的手動去進行bean的查找,但更優雅。一個可以被上下文所感知(訪問應用程式上下文可以得到)的bean將負責在單例bean中生成原型bean實例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class ShoppingCartProvider implements ApplicationContextAware { private ApplicationContext context; public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; } public ShoppingCart getInstance() { return (ShoppingCart) context.getBean("shoppingCart"); } } |
經過上面的修改,controller這裡相應修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class TestController { private ShoppingCartProvider shoppingCartProvider; public String testAdd(@PathVariable(value="productName") String productName) { Product product = new Product(); product.setName(productName); ShoppingCart shoppingCart = shoppingCartProvider.getInstance(); shoppingCart.addProduct(product); System.out.println("ShoppingCart is "+shoppingCart); return "test"; } } |
也可以在XML配置文件中定義。裡面會有一個屬性引用原型bean,並允許在每次調用時創建新的實例。它可以很輕鬆地在一個bean中混合更多東西:
1 2 3 4 5 6 7 | <bean id="shoppingCartProvider" class="com.migo.data.ShoppingCartProvider"> <lookup-method name="getInstance" bean="shoppingCart"> </lookup-method> </bean> <bean id="shoppingCart" class="com.migo.data.ShoppingCart" scope="prototype"> </bean> |
1 2 3 | public abstract class ShoppingCartProvider { public abstract ShoppingCart getInstance(); } |
Controller
的代碼與實現ApplicationContextAware
介面的provider的那個例子是一樣的。而區別也僅在於provider的bean定義和實現。該定義包含一個標簽查找方法。它指定必須使用哪個方法來獲取bean屬性中指定的bean的新實例。在我們的這個例子中,我們通過調用ShoppingCartProvider
類的getInstance
方法來尋找新的ShoppingCart
的實例。需要註意的一點,類和方法都可以是抽象的。通過這樣做,你可以讓Spring生成將實現該方法並返回所需bean的子類。如果這個方法不是抽象的,Spring會重寫覆蓋它。
Spring中的Bean類
單例的源碼實現主要存在於org.springframework.beans和org.springframework.context包中。首先,從Bean包中查看BeanFactory介面。它包含兩個我們絕對感興趣的方法,可用來確定bean是單例還是原型:
- boolean isSingleton(String name)throws NoSuchBeanDefinitionException
- boolean isPrototype(String name)throws NoSuchBeanDefinitionException
接下來,我們來深入一下AbstractFactoryBean
,從這個類的註釋可以知道它是作為“FactoryBean實現的簡單模板超類(還是直白翻譯下比較好,說預設實現也覺得不靠譜)
”。它包含一個用來返回單例或創建原型bean的getObject
方法的實現。原型和單例是通過createInstance
方法在不同的時間段進行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | /** * Simple template superclass for {@link FactoryBean} implementations that * creates a singleton or a prototype object, depending on a flag. * * <p>If the "singleton" flag is {@code true} (the default), * this class will create the object that it creates exactly once * on initialization and subsequently return said singleton instance * on all calls to the {@link #getObject()} method. * * <p>Else, this class will create a new instance every time the * {@link #getObject()} method is invoked. Subclasses are responsible * for implementing the abstract {@link #createInstance()} template * method to actually create the object(s) to expose. * * @author Juergen Hoeller * @author Keith Donald * @since 1.0.2 * @see #setSingleton * @see #createInstance() */ public abstract class AbstractFactoryBean<T> implements FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean { /** * Expose the singleton instance or create a new prototype instance. * @see #createInstance() * @see #getEarlySingletonInterfaces() */ public final T getObject() throws Exception { if (isSingleton()) { return (this.initialized ? this.singletonInstance : getEarlySingletonInstance()); } else { return createInstance(); } } ... /** * Template method that subclasses must override to construct * the object returned by this factory. * <p>Invoked on initialization of this FactoryBean in case of * a singleton; else, on each {@link #getObject()} call. * @return the object returned by this factory * @throws Exception if an exception occurred during object creation * @see #getObject() */ protected abstract T createInstance() throws Exception; ... } |
另一個我們會感興趣的一個點是BeanDefinition介面
。bean如其名
,它定義了一個bean屬性,例如:scope,class name,factory method name,properties或constructor arguments。
org.springframework.beans.factory.config.BeanDefinition
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * A BeanDefinition describes a bean instance, which has property values, * constructor argument values, and further information supplied by * concrete implementations. * * <p>This is just a minimal interface: The main intention is to allow a * {@link BeanFactoryPostProcessor} such as {@link PropertyPlaceholderConfigurer} * to introspect and modify property values and other bean metadata. * * @author Juergen Hoeller * @author Rob Harrop * @since 19.03.2004 * @see ConfigurableListableBeanFactory#getBeanDefinition * @see org.springframework.beans.factory.support.RootBeanDefinition * @see org.springframework.beans.factory.support.ChildBeanDefinition */ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { //限於篇幅,請自行查看源碼,能發現很多有用的東西 } |
想要看到bean被初始化的位置,我們需要跳轉到context包中,更準確地說就是在AbstractApplicationContext
類(這個類我們已經接觸過好多次了)中。在它的public void refresh()throws BeansException,IllegalStateException我們可以找到一些關於bean創建的片段,特別是:
- protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory):實現org.springframework.beans.factory.config .BeanFactoryPostProcessor介面的所有bean 都被初始化和調用。這種類型bean允許修改另一個bean的屬性或構造函數參數(請看PostProcessorRegistrationDelegate的相應代碼可以知道,使用BeanFactoryPostProcessor來處理我們所要用beanFactory生成的bean,這裡可以直接把beanFactory看成是我們需要的bean即可)。但是請註意,在此階段只能修改bean定義。“正常”bean實例尚未創建。關於這塊會請參考文章Spring中的bean工廠後置處理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * Instantiate and invoke all registered BeanFactoryPostProcessor beans, * respecting explicit order if given. * <p>Must be called before singleton instantiation. */ protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor) if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); } } |
org.springframework.context.support.PostProcessorRegistrationDelegate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /** * Delegate for AbstractApplicationContext's post-processor handling. * * @author Juergen Hoeller * @since 4.0 */ class PostProcessorRegistrationDelegate { public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) { // Invoke BeanDefinitionRegistryPostProcessors first, if any. Set<String> processedBeans = new HashSet<>(); if (beanFactory instanceof BeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; List<BeanFactoryPostProcessor> regularPostProcessors = new LinkedList<>(); List<BeanDefinitionRegistryPostProcessor> registryPostProcessors = new LinkedList<>(); for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) { if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { BeanDefinitionRegistryPostProcessor registryPostProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor; registryPostProcessor.postProcessBeanDefinitionRegistry(registry); registryPostProcessors.add(registryPostProcessor); } else { regularPostProcessors.add(postProcessor); } } ... } |
- protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory):這裡上下文實例化並調用實現了org.springframework.beans.factory.config.BeanPostProcessor介面的所有bean 。實現此介面的bean包含可以在其他bean初始化之前或之後調用的回調。因為內容比較多,關於這塊會請參考文章Spring中的bean工廠後置處理器。
1 2 3 4 5 6 7 8 | /** * Instantiate and invoke all registered BeanPostProcessor beans, * respecting explicit order if given. * <p>Must be called before any instantiation of application beans. */ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this); } |
- protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory):主要調用定義在org.springframework.beans.factory.config.ConfigurableListableBeanFactory介面內的
preInstantiateSingletons
方法。該方法的目的是實例化所有被定義為非延遲載入的bean。如果在應用程式上下文載入時遇到BeansException異常,則可能來自此方法。當bean無法創建時,它會拋出BeansException異常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | /** * Finish the initialization of this context's bean factory, * initializing all remaining singleton beans. */ protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { // Initialize conversion service for this context. if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { beanFactory.setConversionService( beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); } // Register a default embedded value resolver if no bean post-processor // (such as a PropertyPlaceholderConfigurer bean) registered any before: // at this point, primarily for resolution in annotation attribute values. if (!beanFactory.hasEmbeddedValueResolver()) { beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal)); } // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early. String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); for (String weaverAwareName : weaverAwareNames) { getBean(weaverAwareName); } // Stop using the temporary ClassLoader for type matching. beanFactory.setTempClassLoader(null); // Allow for caching all bean definition metadata, not expecting further changes. beanFactory.freezeConfiguration(); // Instantiate all remaining (non-lazy-init) singletons. beanFactory.preInstantiateSingletons(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 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(); } } } |
總結
首先,我們講了單例和原型作用域之間的區別。第一個為每個容器創建一個對象,而第二個在每個請求時創建一個新的bean對象。單例和原型都可以一起交叉使用,但原型不能通過@Autowired
或其他註入方式來解決。它們應該使用getBean()方法
或方法查找
來生成新實例。最後隨意說了一說關於bean及其初始化的內容。