SpringBoot預加載與懶加載實現(xiàn)方法超詳細講解
預加載
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 子接口,擁有更加細粒度操作原數(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),當然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,可以認為是是 User,也可以認為是 SuperUser,如果是 User 就代表了已經(jīng)進行了合并,如果 SuperUser 由于其本身就是頂級類,所以不需要合并,這里會排除掉 Object
需要注意的是第一次 從 mergedBeanDefinitions 是從 當前的 BeanFactory 中查找
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
//從當前 BeanFactory 中的緩存中獲取
RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
// 不是NUll 并且 沒有過期的話,如果過期了或者修改了 會重新去查找
if (mbd != null && !mbd.stale) {
//返回當前的
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è)務并不一定是線程安全的,所以要加鎖
synchronized (this.mergedBeanDefinitions) {
RootBeanDefinition mbd = null;
RootBeanDefinition previous = null;
// 這里為空,代表的是 當前的 BeanDefinition 是頂層的 Bean 不存在 嵌套Bean
if (containingBd == null) {
// 獲取當前Bean,為什么又一次獲取了呢?因為如果在多線程操作下 可能 這個Bean已經(jīng)被修改了,所以重新獲取一次
mbd = this.mergedBeanDefinitions.get(beanName);
}
//如果緩存中沒有,或者過期了,則會重新創(chuàng)建一個
if (mbd == null || mbd.stale) {
previous = mbd;
//如果 父 parentName 為空
if (bd.getParentName() == null) {
// 如果當前類型就是 RootBeanDefinition
if (bd instanceof RootBeanDefinition) {
// 進行克隆~~~
mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
} else {
// 如果不是就會將 其他 BeanDefinition 轉(zhuǎn)換成 RootBeanDefinition 代表的是當前 BeanDefinition 為頂級 Bean
mbd = new RootBeanDefinition(bd);
}
}
// 如果父 BeanDefinition 不為 空,也就代表了 當前類存在繼承,如果不理解這段的話,可以看一下<bean parent=""> bean標簽中的 paretn 屬性,
else {
// 子bean定義:需要與父bean合并。
BeanDefinition pbd;
try {
String parentBeanName = transformedBeanName(bd.getParentName());
// 當前的 BeanName 不是 parentBeanName,會去獲取 父BeanDefinition,否則則會去父工廠去獲取
if (!beanName.equals(parentBeanName)) {
// 獲取 parent BeanDefinition這里會進行一個遞歸操作,
pbd = getMergedBeanDefinition(parentBeanName);
} else {
// 如果當前的BeanName 和傳遞的 parentName 一模一樣 則會去父 ParentBeanFactory 查找
BeanFactory parent = getParentBeanFactory();
// 如果當前是層次 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())) {
// 默認為單例
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()) {
// 將當前的 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方法比較復雜,簡單總結(jié)下就創(chuàng)建bean的對象并且創(chuàng)建bean依賴的對象并且注入到當前bean完成對bean的初始化
懶加載
@Lazy
在類上加上@Lazy標簽,那么就開啟了懶加載
@Service("helloServiceB")
@Lazy
public class HelloServiceB {
············懶加載指的是,初始化時不會創(chuàng)建實例,在真正被使用到的時候再進行加載。
看了前面的預加載可以知道,在preInstantiateSingletons方法中會跳過懶加載的bean。
如果懶加載的bean被依賴會怎么樣?
比如又有serviceA依賴了ServiceB
@Service("helloServiceA")
public class HelloServiceA implements HelloService {
@Autowired
private HelloService helloServiceB;那么此時HelloServiceB懶加載會失效
HelloServiceA沒有@Lazy標簽會在啟動時預加載通過getBean方法創(chuàng)建。同時會注入其依賴的bean。serviceB也會被創(chuàng)建。
因此要使懶加載生效,應該在HelloServiceA也加@Lazy注解
全局懶加載
一般情況程序在啟動時時有大量的 Bean 需要初始化,例如 數(shù)據(jù)源初始化、緩存初始化等導致應用程序啟動非常的慢。在 spring boot 2.2 之前的版本,我們對這些 bean 使用手動增加 @Lazy 注解,來實現(xiàn)啟動時不初始化,業(yè)務程序在調(diào)用需要時再去初始化,如上代碼修改為即可:
為什么需要全局懶加載
同上文中提到我們需要手動在 bean 增加 @Lazy 注解,這就意味著我們僅能對程序中自行實現(xiàn)的 bean 進行添加。但是現(xiàn)在 spring boot 應用中引入了很多第三方 starter ,比如 druid-spring-boot-starter 數(shù)據(jù)源注入、spring-boot-starter-data-redis 緩存等默認情況下, 引入即注入了相關(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 {}
當然也可以指定規(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); }全局懶加載的好處與問題
當項目比較大時。開發(fā)人員本地調(diào)試時,并不需要使用到全部的bean,那么開啟全局懶加載可以節(jié)省很多啟動項目的時間
通過設(shè)置全局懶加載,我們可以減少啟動時的創(chuàng)建任務從而大幅度的縮減應用的啟動時間。但全局懶加載的缺點可以歸納為以下兩點:
- Http 請求處理時間變長。 這里準確的來說是第一次 http 請求處理的時間變長,之后的請求不受影響
- 錯誤不會在應用啟動時拋出,不利于早發(fā)現(xiàn)、早解決、早下班。
到此這篇關(guān)于SpringBoot預加載與懶加載實現(xiàn)方法超詳細講解的文章就介紹到這了,更多相關(guān)SpringBoot預加載與懶加載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Annotation(Java 注解)的實現(xiàn)代碼
本篇文章介紹了,Java Annotation(Java 注解)的實現(xiàn)代碼。需要的朋友參考下2013-05-05
springcloud-gateway集成knife4j的示例詳解
這篇文章主要介紹了springcloud-gateway集成knife4j的示例詳解,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
解決IDEA中多模塊下Mybatis逆向工程不生成相應文件的情況
這篇文章主要介紹了解決IDEA中多模塊下Mybatis逆向工程不生成相應文件的情況,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01
SpringMVC后端Controller頁面跳轉(zhuǎn)的三種方式匯總
這篇文章主要介紹了SpringMVC后端Controller頁面跳轉(zhuǎn)的三種方式匯總,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10
Java中的ReadWriteLock高效處理并發(fā)讀寫操作實例探究
這篇文章主要為大家介紹了Java中的ReadWriteLock高效處理并發(fā)讀寫操作實例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01
解決MybatisPlus批量插入數(shù)據(jù)報錯:Error getting generated 
在使用MybatisPlus進行批量插入數(shù)據(jù)時遇到空指針異常錯誤,分析原因是由于主鍵生成策略導致的,嘗試通過設(shè)置useGeneratedKeys屬性解決問題,但因批量插入方法限制,該方法未能成功,最終通過自定義mapper方法實現(xiàn)批量插入,解決了問題2024-09-09
JavaMail實現(xiàn)發(fā)送超文本(html)格式郵件的方法
這篇文章主要介紹了JavaMail實現(xiàn)發(fā)送超文本(html)格式郵件的方法,實例分析了java發(fā)送超文本文件的相關(guān)技巧,需要的朋友可以參考下2015-05-05

