最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring框架中@Lazy延遲加載原理和使用詳解

 更新時間:2025年05月07日 09:39:56   作者:Armyyyyy丶  
這篇文章主要介紹了Spring框架中@Lazy延遲加載原理和使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

一、@Lazy延遲加載原理

如果某個類想要使它在Spring啟動時不加載我們聽的最多的便是為其加上@Lazy注解或者在@ComponentScan掃描注解中設(shè)置lazyInit為true即可完成。那么我們先來看看這兩者分別的實現(xiàn)原理。

1.延遲加載原理

1.1 @Lazy三種配置方法

我們使用延遲加載一般有三種實現(xiàn)方式,第一種也是最原始的配置方式是在XML文件中直接配置標(biāo)簽屬性:

<bean id="XXX" class="XXX.XXX.XXXX" lazy-init="true"/>

第二種方式為在@Component類上加上@Lazy注解:

@Lazy
@Component
public class XXXX {
    ...
}

第三種方式是在@Configuration類中配置@Bean時添加@Lazy注解:

@Configuration
public class XXXX {
    @Lazy
    @Bean
    public XXX getXXX() {
        return new XXX();
    }
}

1.2 @ComponentScan配置延遲加載

使用包掃描的配置方式如下:

@ComponentScan(value = "XXX.XXX", lazyInit = true)
@Configuration
public class XXXX {
    ...
}

1.3 加載原理

當(dāng)使用上述三種配置后,Spring在掃描加載Bean時會讀取@Lazy和@Component注解相應(yīng)值,并設(shè)置Bean定義的lazyInit屬性。

讀取注解配置時最終會調(diào)用ClassPathBeanDefinitionScanner及其子類實現(xiàn)的doScan方法,在這個方法中完成注解的讀取配置。

關(guān)鍵源碼如下:

public class ClassPathBeanDefinitionScanner 
        extends ClassPathScanningCandidateComponentProvider {
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
       // 不管是讀取注解或者XML配置方式bean,最終讀取加載Bean時都會進(jìn)入到該方法
       // 對相應(yīng)的包進(jìn)行處理
       // beanDefinitions是保存返回bean定義的集合
       Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
       // 遍歷多個包下的類
       for (String basePackage : basePackages) {
          // 獲取滿足條件的bean定義集合
          Set<BeanDefinition> candidates = 
                  findCandidateComponents(basePackage);
          // 對每個bean定義進(jìn)行處理
          for (BeanDefinition candidate : candidates) {
             ScopeMetadata scopeMetadata = this.scopeMetadataResolver
                     .resolveScopeMetadata(candidate);
             candidate.setScope(scopeMetadata.getScopeName());
             String beanName = this.beanNameGenerator
                     .generateBeanName(candidate, this.registry);
             // 這個方法會處理@ComponentScan中的lazyInit值,因為在使用
             // @ComponentScan注解時會首先把該值賦值到beanDefinitionDefaults
             // 默認(rèn)bean定義值的對象中,在postProcessBeanDefinition方法中
             // 會首先應(yīng)用一次這些默認(rèn)值,其中就包括lazyInit、autowireMode等
             if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition(
                        (AbstractBeanDefinition) candidate, beanName);
             }
             // 讀取@Lazy、@Primary和@DependsOn等注解值
             if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils
                        .processCommonDefinitionAnnotations(
                                (AnnotatedBeanDefinition) candidate);
             }
             // 如果候選者滿足要求則將其注冊到Bean定義中心
             if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = 
                        new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils
                        .applyScopedProxyMode(scopeMetadata, 
                            definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                // 注冊bean定義
                registerBeanDefinition(definitionHolder, this.registry);
             }
          }
       }
       return beanDefinitions;
    }
    protected void postProcessBeanDefinition(
            AbstractBeanDefinition beanDefinition, String beanName) {
       // 此處會應(yīng)用默認(rèn)值,如lazyInit、autowireMode、initMethod等
       beanDefinition.applyDefaults(this.beanDefinitionDefaults);
       if (this.autowireCandidatePatterns != null) {
          beanDefinition.setAutowireCandidate(PatternMatchUtils
                  .simpleMatch(this.autowireCandidatePatterns, beanName));
       }
    }
}

經(jīng)過ClassPathBeanDefinitionScanner或子類實現(xiàn)的掃描讀取后,延遲加載的配置便被配置到了Bean定義中,等初始化時再使用該屬性,這里需要注意的是@ComponentScan延遲加載屬性是可以被@Lazy覆蓋的,因為@Lazy是在@ComponentScan后面處理的。

2.延遲加載實現(xiàn)原理

前面我們已經(jīng)知道了在何處讀取注解配置的屬性,現(xiàn)在我們稍微看下其具體判斷實現(xiàn)的地方。

2.1 AbstractApplicationContext

Spring框架在刷新時會初始化非延遲加載的單例bean,而一般我們使用的bean都是單例的。其關(guān)鍵源碼如下:

public abstract class AbstractApplicationContext 
        extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        ...
        // 刷新流程中執(zhí)行初始化非延遲單例的方法
        finishBeanFactoryInitialization(beanFactory);
        ...
    }
    protected void finishBeanFactoryInitialization(
            ConfigurableListableBeanFactory beanFactory) {
        ...
        // 實際執(zhí)行初始化非延遲加載單例
        beanFactory.preInstantiateSingletons();
    }
}

2.2 DefaultListableBeanFactory

最終會調(diào)用Spring工廠來實例化,直接看到其實現(xiàn)方法源碼:

public class DefaultListableBeanFactory 
        extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry,
        Serializable {
    // 當(dāng)BeanDefinition被創(chuàng)建注冊到工廠中時bean定義的名字將會被保存到這個集合中
    // 且里面的順序為注冊進(jìn)來的順序
    private volatile List<String> beanDefinitionNames = 
            new ArrayList<>(256);
    @Override
    public void preInstantiateSingletons() throws BeansException {
        // 獲取所有已注冊到Spring工廠中的bean定義
        List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
        // 遍歷bean定義,初始化非抽象、單例且非延遲加載的bean對象
        for (String beanName : beanNames) {
           RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
           // !bd.isLazyInit()便是判斷非延遲加載的,因此前面獲取到的延遲加載
           // 屬性會在這里進(jìn)行判斷
           if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
              // 這里面會判斷是否是FactoryBean類型,最終都會調(diào)用到getBean中
              ...
           }
        }
        // 后續(xù)略過
        ...
    }
}

二、使用細(xì)節(jié)

讀取源碼其中一個目的是為了更好的實際的應(yīng)用以及準(zhǔn)確的把握對應(yīng)功能的生效范圍,因此在使用延遲加載時需要額外注意一些點。

Spring框架延遲加載屬性在調(diào)用getBean之后將會失效,因為getBean方法是初始化bean的入口,這不難理解,那么平時我們使用@Autowired等自動注入注解時能和@Lazy注解一起使用嗎?

接下來我們從兩個實例來說明一下,這兩個實例都是使用平時的使用用法,在Component上添加@Lazy注解,且讓其實現(xiàn)InitializingBean接口,當(dāng)Bean被加載時我們便能得知,看其是否會生效,示例如下:

1.@Lazy失效實例

1.1 Controller非延遲加載類

聲明一個Controller控制器:

@Controller
public class TestController implements InitializingBean{
    @Autowired
    private TestService testService;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testController Initializing");
    }
}

1.2 Service延遲加載類

再聲明一個Service服務(wù)類:

@Lazy
@Service
public class TestService implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testService Initializing");
    }
}

1.3 結(jié)果輸出

啟動程序后控制臺輸出:

testService Initializing

testController Initializing

啟動完Spring程序后輸出了TestService里面打印的字符串。這就奇怪了,明明使用了@Lazy注解,但是卻并沒有其作用,在Spring啟動項目時還是加載了這個類?這就涉及到@Autowired等自動注入注解的使用了,如果有興趣了解其實現(xiàn)的可以去看文章Spring框架原理之實例化bean和@Autowired實現(xiàn)原理

由于Controller類不是延遲加載的,且里面使用@Autowired自動注入注解注入了Service,因此在程序初始化時Controller將會被初始化,同時在處理@Autowired注解的字段時,會調(diào)用getBean方法從Spring工廠中獲取字段的bean對象,因此通過@Autowired路線加在了Service,這就導(dǎo)致了@Lazy注解失效了,因此雖然沒通過refresh方法流程初始化,但是卻通過@Autowired的處理類初始化了。

2.@Lazy起效實例

想要@Lazy注解起作用,只需要改一步,即把Controller也改成@Lazy,讓其在啟動時不被加載,不觸發(fā)@Autowired注解依賴鏈的調(diào)用即可。

2.1 修改的Controller實例

修改后如下:

@Lazy
@Controller
public class TestController implements InitializingBean{
    @Autowired
    private TestService testService;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testController Initializing");
    }
}

如果是這種配置@Lazy將會起作用,在項目啟動時將不會加載這兩個需要延遲加載的bean。

總結(jié)

從上面的例子我們可以總結(jié)及延伸出兩個注意點:

  1. 非延遲加載的類中不能自動注入延遲加載的類,會導(dǎo)致延遲加載失效;
  2. 如果想要實現(xiàn)某個類延遲加載使用自動注入功能時需要調(diào)用鏈前都不存在非延遲加載類,否則延遲加載失效。

作用效果總結(jié)圖如下:

其實@Scope指定原型和單例時有些情況也會導(dǎo)致原型bean“失效”,這又是另外一個故事了,后面有機(jī)會再分析一波。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringBoot配置加載順序?qū)嵺`

    SpringBoot配置加載順序?qū)嵺`

    這篇文章主要介紹了SpringBoot配置加載順序?qū)嵺`,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2026-03-03
  • java Timer 定時每天凌晨1點執(zhí)行任務(wù)

    java Timer 定時每天凌晨1點執(zhí)行任務(wù)

    這篇文章主要介紹了java Timer 定時每天凌晨1點執(zhí)行任務(wù)的代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-09-09
  • SpringBoot使用Spring Test進(jìn)行集成測試的流程步驟

    SpringBoot使用Spring Test進(jìn)行集成測試的流程步驟

    Spring Test 是 Spring Framework 提供的一個測試框架,它可以幫助我們進(jìn)行集成測試,在本文中,我們將介紹如何使用 Spring Test 進(jìn)行集成測試,需要的朋友可以參考下
    2023-06-06
  • java中為什么要謹(jǐn)慎使用Arrays.asList、ArrayList的subList

    java中為什么要謹(jǐn)慎使用Arrays.asList、ArrayList的subList

    這篇文章主要介紹了java中為什么要謹(jǐn)慎使用Arrays.asList、ArrayList的subList,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • Java字符串詳解的實例介紹

    Java字符串詳解的實例介紹

    本篇文章介紹了,在Java中關(guān)于字符串詳解一些實例操作,需要的朋友參考下
    2013-04-04
  • Java分別利用深度優(yōu)先和廣度優(yōu)先求解迷宮路徑

    Java分別利用深度優(yōu)先和廣度優(yōu)先求解迷宮路徑

    這篇文章主要為大家詳細(xì)介紹了Java如何利用深度優(yōu)先的非遞歸遍歷方法和廣度優(yōu)先的遍歷方法實現(xiàn)求解迷宮路徑,文中的示例代碼講解詳細(xì),需要的可以參考一下
    2022-08-08
  • 淺析java移位符的具體使用

    淺析java移位符的具體使用

    這篇文章主要介紹了淺析java移位符的具體使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • 淺談Java線程間通信方式

    淺談Java線程間通信方式

    這篇文章主要為大家詳細(xì)介紹了Java線程間的通信方式,以代碼結(jié)合文字的方式來討論線程間的通信,感興趣的朋友可以參考一下
    2021-11-11
  • 使用Java解析JSON數(shù)據(jù)并提取特定字段的實現(xiàn)步驟(以提取mailNo為例)

    使用Java解析JSON數(shù)據(jù)并提取特定字段的實現(xiàn)步驟(以提取mailNo為例)

    在現(xiàn)代軟件開發(fā)中,處理JSON數(shù)據(jù)是一項非常常見的任務(wù),無論是從API接口獲取數(shù)據(jù),還是將數(shù)據(jù)存儲為JSON格式,解析和提取JSON中的特定字段都是開發(fā)人員需要掌握的基本技能,本文將以一個實際案例為例,詳細(xì)介紹如何使用Java解析JSON數(shù)據(jù)并提取其中的mailNo字段
    2025-01-01
  • SpringBoot配置文件中數(shù)據(jù)庫密碼加密兩種方案(推薦)

    SpringBoot配置文件中數(shù)據(jù)庫密碼加密兩種方案(推薦)

    SpringBoot項目經(jīng)常將連接數(shù)據(jù)庫的密碼明文放在配置文件里,安全性就比較低一些,尤其在一些企業(yè)對安全性要求很高,因此我們就考慮如何對密碼進(jìn)行加密,文中給大家介紹加密的兩種方式,感興趣的朋友一起看看吧
    2019-10-10

最新評論

云浮市| 中方县| 永川市| 崇阳县| 抚顺县| 柳江县| 唐海县| 全椒县| 大英县| 丰原市| 穆棱市| 社旗县| 皮山县| 左云县| 鄂伦春自治旗| 苏尼特右旗| 华蓥市| 金华市| 湖南省| 大冶市| 临澧县| 高州市| 齐河县| 芜湖市| 临城县| 长阳| 嘉义县| 通海县| 宁陵县| 逊克县| 台南市| 雅安市| 广丰县| 深州市| 芮城县| 丹寨县| 达孜县| 晋江市| 静宁县| 汉寿县| 喀喇沁旗|