SpringBoot預(yù)加載與懶加載實現(xiàn)方法超詳細(xì)講解
預(yù)加載
bean在springBoot啟動過程中就完成創(chuàng)建加載
在AbstractApplicationContext的refresh方法中
// Instantiate all remaining (non-lazy-init) singletons. beanFactory.preInstantiateSingletons();
public void preInstantiateSingletons() throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this); } //所有要進行初始的Bean List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // 對所有Bean進行初始化,除了懶加載 for (String beanName : beanNames) { // 去合并Bean RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); //非抽象、單例、懶加載才會進行注冊 if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { //判斷是不是 FactoryBean,簡單說是我們這個 Bean 實現(xiàn)了 if (isFactoryBean(beanName)) { // 是不是 FactoryBean,獲取 FactoryBean 的方式就是 前綴+beanName Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); // 判斷是否 FactoryBean if (bean instanceof FactoryBean) { // 強轉(zhuǎn) FactoryBean<?> factory = (FactoryBean<?>) bean; boolean isEagerInit; if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { isEagerInit = AccessController.doPrivileged( (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext()); } else { // 判斷 是不是 這個類的,如果是就去創(chuàng)建 SmartFactoryBean 屬于 FactoryBean 子接口,擁有更加細(xì)粒度操作原數(shù)據(jù)的方式, isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit()); } if (isEagerInit) { getBean(beanName); } } } else { //獲取具體的Bean getBean(beanName); } } } // Trigger post-initialization callback for all applicable beans... for (String beanName : beanNames) { Object singletonInstance = getSingleton(beanName); // 執(zhí)行所有 單例Bean 的回調(diào),當(dāng)然Bean 需要實現(xiàn)這個接口~~~ if (singletonInstance instanceof SmartInitializingSingleton) { SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance; if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { smartSingleton.afterSingletonsInstantiated(); return null; }, getAccessControlContext()); } else { smartSingleton.afterSingletonsInstantiated(); } } } }
getMergedLocalBeanDefinition
這里是去合并Bean去了,這里其實有兩個動作
將取出來的 BeanDefinition 進行合并
將BeanDefinition 轉(zhuǎn)換成 RottBeanDefinition 也就說頂級的 Bean ,此處的頂級Bean 指的就是User extends SuperUser,可以認(rèn)為是是 User,也可以認(rèn)為是 SuperUser,如果是 User 就代表了已經(jīng)進行了合并,如果 SuperUser 由于其本身就是頂級類,所以不需要合并,這里會排除掉 Object
需要注意的是第一次 從 mergedBeanDefinitions 是從 當(dāng)前的 BeanFactory 中查找
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException { //從當(dāng)前 BeanFactory 中的緩存中獲取 RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName); // 不是NUll 并且 沒有過期的話,如果過期了或者修改了 會重新去查找 if (mbd != null && !mbd.stale) { //返回當(dāng)前的 return mbd; } // 先去 beanDefinitionMap 中去獲取 BeanDefinition thisBeanDefinition= getBeanDefinition(beanName); // 緩存中未找到,就到 BeanFactory 中尋找 return getMergedBeanDefinition(beanName, thisBeanDefinition); } protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd) throws BeanDefinitionStoreException { //傳遞Name 和BeanDefinition return getMergedBeanDefinition(beanName, bd, null); }
具體的邏輯
protected RootBeanDefinition getMergedBeanDefinition( String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) throws BeanDefinitionStoreException { // 將 mergedBeanDefinitions 防止線程安全,為什么? // 雖然 ConcurrentHashMap 是線程安全的,但是下面的業(yè)務(wù)并不一定是線程安全的,所以要加鎖 synchronized (this.mergedBeanDefinitions) { RootBeanDefinition mbd = null; RootBeanDefinition previous = null; // 這里為空,代表的是 當(dāng)前的 BeanDefinition 是頂層的 Bean 不存在 嵌套Bean if (containingBd == null) { // 獲取當(dāng)前Bean,為什么又一次獲取了呢?因為如果在多線程操作下 可能 這個Bean已經(jīng)被修改了,所以重新獲取一次 mbd = this.mergedBeanDefinitions.get(beanName); } //如果緩存中沒有,或者過期了,則會重新創(chuàng)建一個 if (mbd == null || mbd.stale) { previous = mbd; //如果 父 parentName 為空 if (bd.getParentName() == null) { // 如果當(dāng)前類型就是 RootBeanDefinition if (bd instanceof RootBeanDefinition) { // 進行克隆~~~ mbd = ((RootBeanDefinition) bd).cloneBeanDefinition(); } else { // 如果不是就會將 其他 BeanDefinition 轉(zhuǎn)換成 RootBeanDefinition 代表的是當(dāng)前 BeanDefinition 為頂級 Bean mbd = new RootBeanDefinition(bd); } } // 如果父 BeanDefinition 不為 空,也就代表了 當(dāng)前類存在繼承,如果不理解這段的話,可以看一下<bean parent=""> bean標(biāo)簽中的 paretn 屬性, else { // 子bean定義:需要與父bean合并。 BeanDefinition pbd; try { String parentBeanName = transformedBeanName(bd.getParentName()); // 當(dāng)前的 BeanName 不是 parentBeanName,會去獲取 父BeanDefinition,否則則會去父工廠去獲取 if (!beanName.equals(parentBeanName)) { // 獲取 parent BeanDefinition這里會進行一個遞歸操作, pbd = getMergedBeanDefinition(parentBeanName); } else { // 如果當(dāng)前的BeanName 和傳遞的 parentName 一模一樣 則會去父 ParentBeanFactory 查找 BeanFactory parent = getParentBeanFactory(); // 如果當(dāng)前是層次 BeanFactory 轉(zhuǎn)換查找,如果不是 拋出異常 if (parent instanceof ConfigurableBeanFactory) { pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName); } else { throw new NoSuchBeanDefinitionException(parentBeanName, "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName + "': cannot be resolved without a ConfigurableBeanFactory parent"); } } } catch (NoSuchBeanDefinitionException ex) { throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName, "Could not resolve parent bean definition '" + bd.getParentName() + "'", ex); } // 進行合并 // 進行合并,這里的合并是指將 父級的Bean 合并到子 中,例如 user extends superUser // 也就說講 super中的 屬性 合并到 user 中 // Deep copy with overridden values. mbd = new RootBeanDefinition(pbd); mbd.overrideFrom(bd); } //設(shè)置成單例,如果之前沒設(shè)置的話~~~~ if (!StringUtils.hasLength(mbd.getScope())) { // 默認(rèn)為單例 mbd.setScope(SCOPE_SINGLETON); } // A bean contained in a non-singleton bean cannot be a singleton itself. // Let's correct this on the fly here, since this might be the result of // parent-child merging for the outer bean, in which case the original inner bean // definition will not have inherited the merged outer bean's singleton status. if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) { mbd.setScope(containingBd.getScope()); } // Cache the merged bean definition for the time being // (it might still get re-merged later on in order to pick up metadata changes) // 是否緩存Bean的元數(shù)據(jù) if (containingBd == null && isCacheBeanMetadata()) { // 將當(dāng)前的 BeanDefinitions 放入到map中,進行緩存 this.mergedBeanDefinitions.put(beanName, mbd); } } if (previous != null) { copyRelevantMergedBeanDefinitionCaches(previous, mbd); } return mbd; } }
總結(jié)這里的合并是指將 父級的Bean 合并到子 中,例如 user extends superUser
也就說講 super中的 屬性 合并到 user 中,父類的BeanDefinition會被子類的BeanDefinition繼承。
循環(huán)創(chuàng)建bean
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->
對于非抽象,單例,非懶加載的bean分別調(diào)用getBean方法
getBean方法比較復(fù)雜,簡單總結(jié)下就創(chuàng)建bean的對象并且創(chuàng)建bean依賴的對象并且注入到當(dāng)前bean完成對bean的初始化
懶加載
@Lazy
在類上加上@Lazy標(biāo)簽,那么就開啟了懶加載
@Service("helloServiceB") @Lazy public class HelloServiceB { ············
懶加載指的是,初始化時不會創(chuàng)建實例,在真正被使用到的時候再進行加載。
看了前面的預(yù)加載可以知道,在preInstantiateSingletons方法中會跳過懶加載的bean。
如果懶加載的bean被依賴會怎么樣?
比如又有serviceA依賴了ServiceB
@Service("helloServiceA") public class HelloServiceA implements HelloService { @Autowired private HelloService helloServiceB;
那么此時HelloServiceB懶加載會失效
HelloServiceA沒有@Lazy標(biāo)簽會在啟動時預(yù)加載通過getBean方法創(chuàng)建。同時會注入其依賴的bean。serviceB也會被創(chuàng)建。
因此要使懶加載生效,應(yīng)該在HelloServiceA也加@Lazy注解
全局懶加載
一般情況程序在啟動時時有大量的 Bean 需要初始化,例如 數(shù)據(jù)源初始化、緩存初始化等導(dǎo)致應(yīng)用程序啟動非常的慢。在 spring boot 2.2 之前的版本,我們對這些 bean 使用手動增加 @Lazy 注解,來實現(xiàn)啟動時不初始化,業(yè)務(wù)程序在調(diào)用需要時再去初始化,如上代碼修改為即可:
為什么需要全局懶加載
同上文中提到我們需要手動在 bean 增加 @Lazy 注解,這就意味著我們僅能對程序中自行實現(xiàn)的 bean 進行添加。但是現(xiàn)在 spring boot 應(yīng)用中引入了很多第三方 starter ,比如 druid-spring-boot-starter 數(shù)據(jù)源注入、spring-boot-starter-data-redis 緩存等默認(rèn)情況下, 引入即注入了相關(guān) bean 我們無法去修改添加 @Lazy。
spring boot 2.2 新增全局懶加載屬性,開啟后全局 bean 被設(shè)置為懶加載,需要時再去創(chuàng)建
spring:
main:
lazy-initialization: true
原理
在SpringApplication#prepareContext方法中
if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); }
如果開啟了lazy-initialization,那么添加LazyInitializationBeanFactoryPostProcessor
LazyInitializationBeanFactoryPostProcessor執(zhí)行,會將beanFactory中的bean設(shè)置lazyinit
public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // Take care not to force the eager init of factory beans when getting filters Collection<LazyInitializationExcludeFilter> filters = beanFactory .getBeansOfType(LazyInitializationExcludeFilter.class, false, false).values(); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); if (beanDefinition instanceof AbstractBeanDefinition) { postProcess(beanFactory, filters, beanName, (AbstractBeanDefinition) beanDefinition); } } } private void postProcess(ConfigurableListableBeanFactory beanFactory, Collection<LazyInitializationExcludeFilter> filters, String beanName, AbstractBeanDefinition beanDefinition) { Boolean lazyInit = beanDefinition.getLazyInit(); if (lazyInit != null) { return; } Class<?> beanType = getBeanType(beanFactory, beanName); if (!isExcluded(filters, beanName, beanDefinition, beanType)) { beanDefinition.setLazyInit(true); } }
對于全局懶加載
個別 bean 可以通過設(shè)置 @Lazy(false) 排除,設(shè)置為啟動時加載
@Lazy(false) @Configuration public class DemoConfig {}
當(dāng)然也可以指定規(guī)則實現(xiàn) LazyInitializationExcludeFilter 規(guī)則實現(xiàn)排除
@Bean LazyInitializationExcludeFilter integrationLazyInitExcludeFilter() {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E--> return LazyInitializationExcludeFilter.forBeanTypes(DemoConfig.class); }
全局懶加載的好處與問題
當(dāng)項目比較大時。開發(fā)人員本地調(diào)試時,并不需要使用到全部的bean,那么開啟全局懶加載可以節(jié)省很多啟動項目的時間
通過設(shè)置全局懶加載,我們可以減少啟動時的創(chuàng)建任務(wù)從而大幅度的縮減應(yīng)用的啟動時間。但全局懶加載的缺點可以歸納為以下兩點:
- Http 請求處理時間變長。 這里準(zhǔn)確的來說是第一次 http 請求處理的時間變長,之后的請求不受影響
- 錯誤不會在應(yīng)用啟動時拋出,不利于早發(fā)現(xiàn)、早解決、早下班。
到此這篇關(guān)于SpringBoot預(yù)加載與懶加載實現(xiàn)方法超詳細(xì)講解的文章就介紹到這了,更多相關(guān)SpringBoot預(yù)加載與懶加載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Annotation(Java 注解)的實現(xiàn)代碼
本篇文章介紹了,Java Annotation(Java 注解)的實現(xiàn)代碼。需要的朋友參考下2013-05-05springcloud-gateway集成knife4j的示例詳解
這篇文章主要介紹了springcloud-gateway集成knife4j的示例詳解,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03解決IDEA中多模塊下Mybatis逆向工程不生成相應(yīng)文件的情況
這篇文章主要介紹了解決IDEA中多模塊下Mybatis逆向工程不生成相應(yīng)文件的情況,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01SpringMVC后端Controller頁面跳轉(zhuǎn)的三種方式匯總
這篇文章主要介紹了SpringMVC后端Controller頁面跳轉(zhuǎn)的三種方式匯總,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10Java中的ReadWriteLock高效處理并發(fā)讀寫操作實例探究
這篇文章主要為大家介紹了Java中的ReadWriteLock高效處理并發(fā)讀寫操作實例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01Java?對象在?JVM?中的內(nèi)存布局超詳細(xì)解說
這篇文章主要介紹了Java?對象在?JVM?中的內(nèi)存布局超詳細(xì)解說,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09解決MybatisPlus批量插入數(shù)據(jù)報錯:Error getting generated 
在使用MybatisPlus進行批量插入數(shù)據(jù)時遇到空指針異常錯誤,分析原因是由于主鍵生成策略導(dǎo)致的,嘗試通過設(shè)置useGeneratedKeys屬性解決問題,但因批量插入方法限制,該方法未能成功,最終通過自定義mapper方法實現(xiàn)批量插入,解決了問題2024-09-09JavaMail實現(xiàn)發(fā)送超文本(html)格式郵件的方法
這篇文章主要介紹了JavaMail實現(xiàn)發(fā)送超文本(html)格式郵件的方法,實例分析了java發(fā)送超文本文件的相關(guān)技巧,需要的朋友可以參考下2015-05-05