From b925d1928431efbab4dda1aa8aa08474c716a473 Mon Sep 17 00:00:00 2001 From: xuchengsheng Date: Wed, 11 Oct 2023 17:44:40 +0800 Subject: [PATCH] =?UTF-8?q?@Lazy=E6=BA=90=E7=A0=81=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +- .../spring-annotation-lazy/README.md | 728 ++++++++++++++++++ .../java/com/xcs/spring/LazyApplication.java | 5 +- .../main/java/com/xcs/spring/bean/MyBean.java | 2 +- .../com/xcs/spring/service/MyService.java | 2 +- 5 files changed, 736 insertions(+), 8 deletions(-) create mode 100644 spring-annotation/spring-annotation-lazy/README.md diff --git a/README.md b/README.md index a1dfa1c..34627a7 100644 --- a/README.md +++ b/README.md @@ -73,11 +73,12 @@ *了解 Spring 如何通过注解驱动开发,简化和加强代码。* - [**解析@Configuration的力量**](spring-annotation/spring-annotation-configuration/README.md) - 揭露如何使用 Java 配置定义 beans。 -- [**组件扫描的魅力: @ComponentScan**](spring-annotation/spring-annotation-componentScan/README.md) - 探索如何自动检测和注册 beans。 +- [**组件扫描的魅力@ComponentScan**](spring-annotation/spring-annotation-componentScan/README.md) - 探索如何自动检测和注册 beans。 - [**深入@Bean的秘密**](spring-annotation/spring-annotation-bean/README.md) - 理解如何通过 Java 方法定义 beans。 - [**@Import的高级策略**](spring-annotation/spring-annotation-import/README.md) - 揭示如何导入其他配置类或组件。 -- [**属性源探查: @PropertySource**](spring-annotation/spring-annotation-propertySource/README.md) - 深入了解如何为应用上下文添加属性源。 -- [**掌控顺序: @DependsOn**](spring-annotation/spring-annotation-dependsOn/README.md) - 精确控制 Spring Beans 的加载顺序。 +- [**属性源探查@PropertySource**](spring-annotation/spring-annotation-propertySource/README.md) - 深入了解如何为应用上下文添加属性源。 +- [**掌控顺序@DependsOn**](spring-annotation/spring-annotation-dependsOn/README.md) - 精确控制 Spring Beans 的加载顺序。 +- [**深入探究@Lazy注解**](spring-annotation/spring-annotation-lazy/README.md) - 如何优雅地实现 Spring Beans 的延迟加载。 ## 🔄持续更新中 diff --git a/spring-annotation/spring-annotation-lazy/README.md b/spring-annotation/spring-annotation-lazy/README.md new file mode 100644 index 0000000..7914852 --- /dev/null +++ b/spring-annotation/spring-annotation-lazy/README.md @@ -0,0 +1,728 @@ +## @Lazy + +- [@Lazy](#lazy) + - [一、注解描述](#一注解描述) + - [二、注解源码](#二注解源码) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、时序图](#五时序图) + - [5.1、Bean注册时序图](#51bean注册时序图) + - [5.2、Bean延迟创建时序图](#52bean延迟创建时序图) + - [5.3、Bean延迟注入时序图](#53bean延迟注入时序图) + - [六、源码分析](#六源码分析) + - [6.1、延迟初始化](#61延迟初始化) + - [6.2、延迟注入](#62延迟注入) + - [七、注意事项](#七注意事项) + - [八、总结](#八总结) + - [8.1、最佳实践总结](#81最佳实践总结) + - [8.2、源码分析总结](#82源码分析总结) + + +### 一、注解描述 + +`@Lazy`注解,它的主要用途是延迟依赖注入的初始化。通常情况下,当 ApplicationContext 被启动和刷新时,所有的单例 bean 会被立即初始化。但有时,可能希望某些 bean 在首次使用时才被初始化。 + +### 二、注解源码 + +`@Lazy`注解是 Spring 框架自 3.0 版本开始引入的一个核心注解,用于控制 bean 的懒加载行为。默认情况下,当 `@Lazy` 被应用,bean 不会在 Spring 容器启动时立即初始化,而是在首次被引用或请求时。这适用于通过 `@Component` 或 `@Bean` 定义的 bean。此外,`@Lazy` 还可以用于注入点,如 `@Autowired`,创建一个懒解析代理,从而实现延迟注入。当用在 `@Configuration` 类上时,它影响该配置中所有的 `@Bean` 定义。 + +```java +/** + * 指示一个bean是否应懒加载初始化。 + * + * 可直接或间接地用于带 org.springframework.stereotype.Component @Component 注解的类, + * 或者用于带有 Bean @Bean 注解的方法。 + * + * 如果此注解不在 @Component 或 @Bean 定义上,将会立即初始化。 + * 如果存在并且设置为 true,除非被另一个bean引用或从包围的 org.springframework.beans.factory.BeanFactory BeanFactory 中显式检索, + * 否则 @Bean 或 @Component 不会初始化。如果存在并设置为 false,那么执行积极初始化单例的bean工厂将在启动时实例化bean。 + * + * 如果Lazy存在于 Configuration @Configuration 类上,表示该 @Configuration 中的所有 @Bean 方法都应懒加载。 + * 如果在一个带有 @Lazy 注解的 @Configuration 类的 @Bean 方法上 @Lazy 设置为false,则表示覆盖了“默认懒惰”行为,该bean应立即初始化。 + * + * 除了其在组件初始化中的作用外,此注解也可用于带有 org.springframework.beans.factory.annotation.Autowired 或 javax.inject.Inject 的注入点: + * 在这种情况下,它会导致为所有受影响的依赖关系创建一个懒解析代理,作为使用 org.springframework.beans.factory.ObjectFactory 或 javax.inject.Provider 的替代方法。 + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.0 + * @see Primary + * @see Bean + * @see Configuration + * @see org.springframework.stereotype.Component + */ +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Lazy { + + /** + * 是否应进行懒加载初始化。 + */ + boolean value() default true; + +} +``` + +### 三、主要功能 + +1. **延迟初始化** + + 当 `@Lazy` 注解应用于一个 `@Bean` 或 `@Component` 上时,该 bean 不会在 Spring 容器启动时立即初始化。而是直到首次被引用或请求时才进行初始化。 +2. **延迟注入** + + 当 `@Lazy` 注解应用于 `@Autowired` 或其他注入点上时,它导致为所注入的依赖关系创建一个懒解析代理,实现首次访问时的延迟注入。 + +### 四、最佳实践 + +首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,然后从中获取一个 `MyService` bean 并调用其 `show` 方法。 + +```java +public class LazyApplication { + + public static void main(String[] args) { + System.out.println("启动 Spring ApplicationContext..."); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class); + System.out.println("启动完成 Spring ApplicationContext..."); + + System.out.println("获取MyService..."); + MyService myService = context.getBean(MyService.class); + + System.out.println("调用show方法..."); + myService.show(); + } +} +``` + +这里使用`@Bean`注解,`MyBean` 被标注了 `@Lazy`,这意味着它只会在首次被请求时才会初始化。`MyService` 没有 `@Lazy` 注解,所以它会被立即初始化。 + +```java +@Configuration +public class MyConfiguration { + + @Bean + @Lazy + public MyBean myBean(){ + System.out.println("MyBean 初始化了!"); + return new MyBean(); + } + + @Bean + public MyService myService(){ + return new MyService(); + } +} +``` + + `MyBean` 类定义很简单。它有一个构造函数,在构造函数中打印了 "MyBean 的构造函数被调用了!",并有一个 `show` 方法,该方法打印 "hello world!"。 + +```java +public class MyBean { + + public MyBean() { + System.out.println("MyBean 的构造函数被调用了!"); + } + + public void show() { + System.out.println("hello world!"); + } +} +``` + +`MyService` 类有一个对 `MyBean` 的引用,而该引用通过 `@Autowired` 进行依赖注入。由于我们还在这个注入点上添加了 `@Lazy` 注解,这意味着 `myBean` 的实际注入将被延迟,直到我们首次尝试访问它时。 + +```java +public class MyService { + + @Autowired + @Lazy + private MyBean myBean; + + public void show() { + System.out.println("MyBean Class = " + myBean.getClass()); + myBean.show(); + } +} +``` + +运行结果发现 + +1. **延迟初始化**: + - 尽管 `MyService` 在应用上下文启动后可用,但由于 `MyBean` 上的 `@Lazy` 注解,`MyBean` 在启动时并未被初始化。 + - 只有在首次访问或使用 `MyBean` 时,它才真正被初始化。这在 "MyBean 初始化了!" 和 "MyBean 的构造函数被调用了!" 的输出中得到了体现。 +2. **延迟注入**: + - 在 `MyService` 的 `show` 方法首次被调用时,由于 `@Autowired` 和 `@Lazy` 注解的组合,Spring 不是直接注入原始的 `MyBean` 实例,而是注入一个代理对象。 + - 这个代理对象的类名为 `com.xcs.spring.bean.MyBean$$EnhancerBySpringCGLIB$$2a517c55`,表明它是由 Spring 的 CGLIB 动态生成的。 + - 当尝试访问该代理对象上的方法(如 `show` 方法)时,代理会确保真正的 `MyBean` 被初始化并代理该方法调用。 + +```java +启动 Spring ApplicationContext... +启动完成 Spring ApplicationContext... +获取MyService... +调用show方法... +MyBean Class = class com.xcs.spring.bean.MyBean$$EnhancerBySpringCGLIB$$2a517c55 +MyBean 初始化了! +MyBean 的构造函数被调用了! +hello world! +``` + +### 五、时序图 + +#### 5.1、Bean注册时序图 + +~~~mermaid +sequenceDiagram +LazyApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)
创建应用上下文 +AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()
刷新应用上下文 +AbstractApplicationContext->>AbstractApplicationContext:invokeBeanFactoryPostProcessors(beanFactory)
调用BFPP方法 +AbstractApplicationContext->>PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors)
委托BFPP处理 +PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate:invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup)
调用BDRPP方法 +PostProcessorRegistrationDelegate->>ConfigurationClassPostProcessor:postProcessBeanDefinitionRegistry(registry)
处理Bean定义 +ConfigurationClassPostProcessor->>ConfigurationClassPostProcessor:processConfigBeanDefinitions(registry)
处理配置类Bean +ConfigurationClassPostProcessor->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitions(configurationModel)
加载Bean定义 +ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitionsForConfigurationClass(configClass,trackedConditionEvaluator)
为配置类加载 +ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitionsForBeanMethod(beanMethod)
为@Bean方法加载 +ConfigurationClassBeanDefinitionReader->>AnnotationConfigUtils:processCommonDefinitionAnnotations(abd,metadata)
处理注解定义 +AnnotationConfigUtils->>AnnotationConfigUtils:attributesFor(metadata, Lazy.class)
获取注解属性 +AnnotationConfigUtils->>AbstractBeanDefinition:setLazyInit(lazyInit)
设置懒加载属性 +~~~ + +#### 5.2、Bean延迟创建时序图 + +~~~mermaid +sequenceDiagram +DependsOnApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)
创建应用上下文 +AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()
刷新应用上下文 +AbstractApplicationContext->>AbstractApplicationContext:finishBeanFactoryInitialization(beanFactory)
完成bean工厂初始化 +AbstractApplicationContext->>DefaultListableBeanFactory:preInstantiateSingletons()
预实例化单例beans +DefaultListableBeanFactory->>AbstractBeanDefinition:isLazyInit() +AbstractBeanDefinition->>DefaultListableBeanFactory:返回Bean是否懒加载 +DefaultListableBeanFactory->>AbstractBeanFactory:getBean(beanName) +alt Bean是懒加载 + AbstractBeanFactory->>DefaultListableBeanFactory: 执行初始化Bean对象 +else Bean不是懒加载 + AbstractBeanFactory->>DefaultListableBeanFactory: 跳过Bean初始化,等待真正使用时在初始化 +end +~~~ + +#### 5.3、Bean延迟注入时序图 + +~~~mermaid +sequenceDiagram +Title: InstantiationAwareBeanPostProcessor时序图 +InstantiationAwareBeanPostProcessorApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext
开始初始化 +AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh
刷新上下文 +AbstractApplicationContext->>AbstractApplicationContext:finishBeanFactoryInitialization
实例化非懒加载的单列Bean +AbstractApplicationContext->>DefaultListableBeanFactory:preInstantiateSingletons
预实例化Singleton +DefaultListableBeanFactory->>AbstractBeanFactory:getBean
根据beanName获取对象 +AbstractBeanFactory->>AbstractBeanFactory:doGetBean
返回指定bean的实例 +AbstractBeanFactory->>DefaultSingletonBeanRegistry:getSingleton
获取单例对象 +DefaultSingletonBeanRegistry->>AbstractBeanFactory:getObject
ObjectFactory接口的工厂方法 +AbstractBeanFactory->>AbstractAutowireCapableBeanFactory:createBean
创建一个bean实例,填充bean实例,应用后处理器 +AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:doCreateBean(beanName,mbd,args) +AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:populateBean(beanName,mbd,bw) +AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessProperties(pvs,bean,beanName) +AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs) +AutowiredAnnotationBeanPostProcessor->>InjectionMetadata:inject(target,beanName,pvs) +InjectionMetadata->>AutowiredFieldElement:inject(bean,beanName,pvs) +AutowiredFieldElement->>AutowiredFieldElement:resolveFieldValue(field,bean,beanName) +AutowiredFieldElement->>DefaultListableBeanFactory:resolveDependency(descriptor,requestingBeanName,autowiredBeanNames,typeConverter) +DefaultListableBeanFactory->>DefaultListableBeanFactory:getAutowireCandidateResolver() +DefaultListableBeanFactory->>ContextAnnotationAutowireCandidateResolver:getLazyResolutionProxyIfNecessary(descriptor,beanName) +ContextAnnotationAutowireCandidateResolver->>ContextAnnotationAutowireCandidateResolver:isLazy(descriptor) +ContextAnnotationAutowireCandidateResolver->>ContextAnnotationAutowireCandidateResolver:buildLazyResolutionProxy(descriptor, beanName) +ContextAnnotationAutowireCandidateResolver->>ProxyFactory:getProxy(classLoader) +ProxyFactory->>ContextAnnotationAutowireCandidateResolver:返回被代理的对象 +ContextAnnotationAutowireCandidateResolver->>DefaultListableBeanFactory:返回被代理的对象 +DefaultListableBeanFactory->>AutowiredFieldElement:返回被代理的对象 +AutowiredFieldElement->>Field:field.set(bean, value)注入被代理的对象 +~~~ + +### 六、源码分析 + +首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,然后从中获取一个 `MyService` bean 并调用其 `show` 方法。 + +```java +public class LazyApplication { + + public static void main(String[] args) { + System.out.println("启动 Spring ApplicationContext..."); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class); + System.out.println("启动完成 Spring ApplicationContext..."); + + System.out.println("获取MyService..."); + MyService myService = context.getBean(MyService.class); + + System.out.println("调用show方法..."); + myService.show(); + } +} +``` + +在`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中,执行了三个步骤。 + +```java +public AnnotationConfigApplicationContext(Class... componentClasses) { + this(); + register(componentClasses); + refresh(); +} +``` + +在`org.springframework.context.support.AbstractApplicationContext#refresh`方法中,我们重点关注一下`finishBeanFactoryInitialization(beanFactory)`这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。 + +```java +@Override +public void refresh() throws BeansException, IllegalStateException { + // ... [代码部分省略以简化] + // 实例化所有剩余非懒加载的单列Bean对象 + finishBeanFactoryInitialization(beanFactory); + // ... [代码部分省略以简化] +} +``` + +在`org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization`方法中,会继续调用`DefaultListableBeanFactory`类中的`preInstantiateSingletons`方法来完成所有剩余非懒加载的单列Bean对象。 + +```java +protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { + // ... [代码部分省略以简化] + // 完成所有剩余非懒加载的单列Bean对象。 + beanFactory.preInstantiateSingletons(); +} +``` + +#### 6.1、延迟初始化 + +在`org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons`方法中,确保所有非懒加载的单例 beans 在容器启动时都被初始化,除非它们显式地标记为懒加载。这也是为什么 `@Lazy` 注解对于那些想要推迟 bean 初始化的场景非常有用。 + +```java +public void preInstantiateSingletons() throws BeansException { + // ... [代码部分省略以简化] + // 复制Bean名称列表 + List beanNames = new ArrayList<>(this.beanDefinitionNames); + + // 初始化非懒加载单例 + for (String beanName : beanNames) { + RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); + // Bean属性检查 + if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { + // 是否为工厂Bean + if (isFactoryBean(beanName)) { + Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); + // 是否为FactoryBean实例 + if (bean instanceof FactoryBean) { + FactoryBean factory = (FactoryBean) bean; + boolean isEagerInit; + // 安全权限检查 + if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { + isEagerInit = AccessController.doPrivileged( + (PrivilegedAction) ((SmartFactoryBean) factory)::isEagerInit, + getAccessControlContext()); + } + else { + // 是否立即初始化 + isEagerInit = (factory instanceof SmartFactoryBean && + ((SmartFactoryBean) factory).isEagerInit()); + } + // 立即初始化Bean + if (isEagerInit) { + getBean(beanName); + } + } + } + else { + // 获取/创建Bean + getBean(beanName); + } + } + } + // ... [代码部分省略以简化] +} +``` + +#### 6.2、延迟注入 + +在`org.springframework.beans.factory.support.AbstractBeanFactory#getBean()`方法中,又调用了`doGetBean`方法来实际执行创建Bean的过程,传递给它bean的名称和一些其他默认的参数值。此处,`doGetBean`负责大部分工作,如查找bean定义、创建bean(如果尚未创建)、处理依赖关系等。 + +```java +@Override +public Object getBean(String name) throws BeansException { + return doGetBean(name, null, null, false); +} +``` + +在`org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean`方法中,首先检查所请求的bean是否是一个单例并且已经创建。如果尚未创建,它将创建一个新的实例。在这个过程中,它处理可能的异常情况,如循环引用,并确保返回的bean是正确的类型。这是Spring容器bean生命周期管理的核心部分。 + +```java +protected T doGetBean( + String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly) + throws BeansException { + // ... [代码部分省略以简化] + + // 开始创建bean实例 + if (mbd.isSingleton()) { + // 如果bean是单例的,我们会尝试从单例缓存中获取它 + // 如果不存在,则使用lambda创建一个新的实例 + sharedInstance = getSingleton(beanName, () -> { + try { + // 尝试创建bean实例 + return createBean(beanName, mbd, args); + } + catch (BeansException ex) { + // ... [代码部分省略以简化] + } + }); + // 对于某些bean(例如FactoryBeans),可能需要进一步处理以获取真正的bean实例 + beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); + } + // ... [代码部分省略以简化] + + // 确保返回的bean实例与请求的类型匹配 + return adaptBeanInstance(name, beanInstance, requiredType); +} +``` + +在`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton()`方法中,主要负责从单例缓存中获取一个已存在的bean实例,或者使用提供的`ObjectFactory`创建一个新的实例。这是确保bean在Spring容器中作为单例存在的关键部分。 + +```java +public Object getSingleton(String beanName, ObjectFactory singletonFactory) { + // ... [代码部分省略以简化] + + // 同步访问单例对象缓存,确保线程安全 + synchronized (this.singletonObjects) { + // 从缓存中获取单例对象 + Object singletonObject = this.singletonObjects.get(beanName); + + // 如果缓存中没有找到 + if (singletonObject == null) { + // ... [代码部分省略以简化] + + try { + // 使用工厂创建新的单例实例 + singletonObject = singletonFactory.getObject(); + newSingleton = true; + } + catch (IllegalStateException ex) { + // ... [代码部分省略以简化] + } + catch (BeanCreationException ex) { + // ... [代码部分省略以简化] + } + finally { + // ... [代码部分省略以简化] + } + + // ... [代码部分省略以简化] + } + + // 返回单例对象 + return singletonObject; + } +} +``` + +在`org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean()`方法中,主要的逻辑是调用 `doCreateBean`,这是真正进行 bean 实例化、属性填充和初始化的地方。这个方法会返回新创建的 bean 实例。 + +```java +@Override +protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) + throws BeanCreationException { + + // ... [代码部分省略以简化] + + try { + // 正常的bean实例化、属性注入和初始化。 + // 这里是真正进行bean创建的部分。 + Object beanInstance = doCreateBean(beanName, mbdToUse, args); + // 记录bean成功创建的日志 + if (logger.isTraceEnabled()) { + logger.trace("Finished creating instance of bean '" + beanName + "'"); + } + return beanInstance; + } + catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { + // ... [代码部分省略以简化] + } + catch (Throwable ex) { + // ... [代码部分省略以简化] + } +} +``` + +在`org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean`方法中,对 bean 的属性进行注入。 + +```java +protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) + throws BeanCreationException { + + // ... [代码部分省略以简化] + + try { + // 属性注入:这一步将配置中的属性值注入到bean实例中。例如,XML中定义的属性或使用@Autowired和@Value注解的属性都会在这里被注入 + populateBean(beanName, mbd, instanceWrapper); + + // ... [代码部分省略以简化] + } + catch (Throwable ex) { + // ... [代码部分省略以简化] + } + + // ... [代码部分省略以简化] +} +``` + +在`org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean`方法中,如果一个属性被标记为 `@Autowired`,并且与 `@Lazy` 结合使用,那么实际的懒加载逻辑会在这个步骤中的其他部分被处理(特别是通过 `AutowiredAnnotationBeanPostProcessor`)。 + +```java +protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { + // ... [代码部分省略以简化] + // 对每一个InstantiationAwareBeanPostProcessor进行处理,这些处理器可能会修改Bean的属性值。 + for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { + // 尝试使用新版本的方法 postProcessProperties + PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); + // ... [代码部分省略以简化] + } + // ... [代码部分省略以简化] +} +``` + +在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties`方法中,用于处理 `@Autowired` 注解的依赖注入。 + +```java +@Override +public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { + // 根据bean的名称和类查找@Autowired注解元数据 + InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); + try { + // 执行实际的依赖注入 + metadata.inject(bean, beanName, pvs); + } + catch (BeanCreationException ex) { + // 抛出bean创建异常 + throw ex; + } + catch (Throwable ex) { + // 处理其他类型的异常,转换为Bean创建异常 + throw new BeanCreationException(beanName, "依赖自动注入失败", ex); + } + // 返回属性值 + return pvs; +} +``` + +在`org.springframework.beans.factory.annotation.InjectionMetadata#inject`方法中,遍历所有这些元素并调用其 `inject` 方法,这样实现了对带有注解的字段或方法的实际注入。 + +```java +public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { + // 获取已校验的注入元素 + Collection checkedElements = this.checkedElements; + // 如果没有已校验的元素,则使用所有注入元素 + Collection elementsToIterate = + (checkedElements != null ? checkedElements : this.injectedElements); + + if (!elementsToIterate.isEmpty()) { + // 遍历所有待注入的元素(字段或方法) + for (InjectedElement element : elementsToIterate) { + // 执行实际的注入操作 + element.inject(target, beanName, pvs); + } + } +} +``` + +在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject`方法中,这个 `inject` 方法的核心逻辑是解析 `@Autowired` 字段的值(即找到匹配的 bean 依赖)并注入到目标 bean 中。在这个过程中,使用缓存以提高性能。 + +```java +@Override +protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { + // 获取带有@Autowired注解的字段 + Field field = (Field) this.member; + Object value; + // 如果字段的值已经被缓存,则直接从缓存中获取 + if (this.cached) { + try { + value = resolvedCachedArgument(beanName, this.cachedFieldValue); + } + catch (NoSuchBeanDefinitionException ex) { + // 如果缓存中的bean意外被移除 -> 重新解析 + value = resolveFieldValue(field, bean, beanName); + } + } + else { + // 解析字段的值(即找到要注入的bean) + value = resolveFieldValue(field, bean, beanName); + } + if (value != null) { + // 使字段可访问(例如,如果它是私有的) + ReflectionUtils.makeAccessible(field); + // 将解析出的值(bean)注入到目标bean的字段中 + field.set(bean, value); + } +} +``` + +在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue`方法中,主要用于解析 `@Autowired` 注解的字段值。它确定了应该为目标字段注入哪个 bean。 + +```java +@Nullable +private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) { + // ... [代码部分省略以简化] + + Object value; + try { + // 解析依赖:这里的核心逻辑是使用bean工厂去解析字段的依赖。它会查找合适的bean来注入到此字段。 + value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); + } + catch (BeansException ex) { + // 当无法解析依赖时,抛出异常 + throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); + } + + // ... [代码部分省略以简化] + + // 返回解析到的值(bean) + return value; +} +``` + +在`org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency`方法中,这里的关键逻辑是 `getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary`,该方法尝试为描述的依赖关系获取一个懒加载代理。如果该依赖关系标记为懒加载 (`@Lazy`),并且被 `@Autowired` 引用,那么它将返回一个懒加载代理,而不是实际的 bean。这样,只有当应用程序真正访问该依赖关系时,实际的 bean 才会被初始化。 + +```java +@Override +@Nullable +public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, + @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { + // ... [代码部分省略以简化] + + // 尝试为描述的依赖关系获取一个懒加载代理。如果依赖是懒加载的,这将返回一个代理对象。 + Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( + descriptor, requestingBeanName); + + // 如果没有为懒加载的依赖关系获取到代理,则尝试直接解析依赖关系 + if (result == null) { + result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); + } + + return result; // 返回解析得到的bean或者懒加载代理 +} +``` + +在`org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary`方法中,如果是懒加载,会调用 `buildLazyResolutionProxy` 来创建一个代理,当首次访问该代理时,代理会触发实际的 bean 初始化。 + +```java +@Override +@Nullable +public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) { + // 判断依赖描述是否被标记为懒加载 + // 如果是懒加载,为其构建一个懒加载代理 + // 如果不是懒加载,则返回null + return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null); +} +``` + +在`org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy`方法中,核心部分是 `TargetSource` 和 `ProxyFactory`。当我们访问这个懒加载代理时,`TargetSource` 的 `getTarget` 方法会被调用,它会解析和返回真正的 bean(或其他依赖项)。使用 `ProxyFactory`,可以为给定的 `TargetSource` 创建一个新的代理。这是 `@Lazy` 注解在字段注入时的实际实现,确保在首次访问字段时才触发依赖的解析和bean的初始化。 + +```java +protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) { + // 获取当前的BeanFactory + BeanFactory beanFactory = getBeanFactory(); + // 确认BeanFactory是DefaultListableBeanFactory的实例 + Assert.state(beanFactory instanceof DefaultListableBeanFactory, + "BeanFactory needs to be a DefaultListableBeanFactory"); + final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory; + + // 创建一个目标源(TargetSource)用于懒加载代理 + TargetSource ts = new TargetSource() { + // 获取依赖的类型 + @Override + public Class getTargetClass() { + return descriptor.getDependencyType(); + } + @Override + public boolean isStatic() { + return false; + } + // 当访问代理时,该方法被调用来解析实际的依赖关系 + @Override + public Object getTarget() { + // ... [代码部分省略以简化] + return target; // 返回解析得到的bean + } + @Override + public void releaseTarget(Object target) { + } + }; + + // 使用Spring的ProxyFactory创建一个新的代理 + ProxyFactory pf = new ProxyFactory(); + pf.setTargetSource(ts); + Class dependencyType = descriptor.getDependencyType(); + if (dependencyType.isInterface()) { + pf.addInterface(dependencyType); + } + // 返回懒加载代理 + return pf.getProxy(dlbf.getBeanClassLoader()); +} +``` + +### 七、注意事项 + +1. **默认行为** + + 如果没有使用 `@Lazy` 注解,Spring 容器会在启动时立即实例化单例 bean。通过使用 `@Lazy`,我们可以改变这个行为,使得 bean 只在首次请求时被初始化。 +2. **构造函数注入** + + 如果一个懒加载的 bean 依赖于另一个非懒加载的 bean,那么该懒加载的 bean 会在容器启动时被初始化,因为它的依赖需要它。这种情况常常在构造函数注入时出现。 +3. **上下文的生命周期** + + `@Lazy` 对于应用上下文的启动速度可能有益,因为少了一些即时初始化的工作。但请注意,延迟初始化可能会导致我们在首次访问 bean 时遇到延迟。 +4. **与 `@Autowired` 结合使用** + + 如果我们在一个字段或 setter 方法上同时使用 `@Autowired` 和 `@Lazy`,Spring 会注入一个代理对象。这个代理对象会在我们首次访问它时触发真正的 bean 初始化。 +5. **懒加载的代理** + + 当与 `@Autowired` 结合使用时,要确保 bean 的类型与代理类型兼容。如果注入的 bean 类型是一个接口,Spring 会创建一个基于接口的代理。如果是一个类,Spring 会创建一个基于类的代理。 +6. **错误处理** + + 延迟初始化意味着某些错误可能在应用启动时不会被立即发现,例如 bean 配置错误。这样的错误只有在实际尝试初始化 bean 时才会被抛出。 +7. **在组件类上使用** + + 对于直接或间接使用 `@Component`、`@Service`、`@Repository` 或 `@Controller` 注解的类,可以使用 `@Lazy` 注解来使这些组件在首次被注入或查找时才被初始化。 + +### 八、总结 + +#### 8.1、最佳实践总结 + +1. **上下文初始化**: + - 使用 `AnnotationConfigApplicationContext` 初始化应用上下文是针对基于Java的配置的推荐做法。 + - 提供一个配置类(如 `MyConfiguration`)作为参数可以帮助上下文知道如何加载和配置beans。 +2. **懒加载配置**: + - 通过在配置类的 `@Bean` 方法上添加 `@Lazy` 注解,可以确保特定的bean只在首次请求时被初始化,而不是在应用上下文启动时。 + - 这可以提高应用启动速度,特别是对于资源密集型的beans或需要长时间初始化的beans。 +3. **依赖注入**: + - 使用 `@Autowired` 注解是Spring的核心特性,它可以自动注入bean的依赖关系。 + - 当与 `@Lazy` 注解组合使用时,Spring会注入一个代理对象而不是实际的bean实例。这个代理对象在首次访问时会触发真正的bean初始化。 +4. **理解代理**: + - 由于延迟注入,通过 `@Autowired` 和 `@Lazy` 注解注入的对象是一个由CGLIB生成的代理对象。 + - 这个代理对象负责在首次访问时初始化真正的bean。 +5. **输出与验证**: + - 通过在bean的初始化和方法调用中添加日志或打印语句,可以验证和观察懒加载和代理的行为。 + - 这对于确保应用的预期行为和性能调优非常有用。 + +#### 8.2、源码分析总结 + +1. **启动及初始化**: + - 使用`AnnotationConfigApplicationContext`初始化应用上下文。 + - 在`AnnotationConfigApplicationContext`的构造函数中,执行了注册(`register`)和刷新(`refresh`)方法。 +2. **Bean的实例化**: + - 在上下文刷新过程中,`finishBeanFactoryInitialization(beanFactory)`方法确保所有非懒加载的单例Beans被实例化。 + - `DefaultListableBeanFactory#preInstantiateSingletons`方法确保所有非懒加载的单例Beans在容器启动时被初始化。 +3. **延迟初始化**: + - 如果Bean被标记为`@Lazy`,它将不会在容器启动时被初始化,但只在首次请求时。 + - `DefaultListableBeanFactory#preInstantiateSingletons`方法中,对于`isLazyInit`返回`true`的Beans,不会进行预初始化。 +4. **Bean的获取与依赖注入**: + - 使用`AbstractBeanFactory#getBean`方法获取Bean实例。 + - 如果Bean尚未创建,`doGetBean`方法将执行Bean的实际创建,包括解析依赖关系、处理循环引用等。 + - 对于单例Beans,它们将被缓存,确保每次都返回相同的实例。 + - 通过`AbstractAutowireCapableBeanFactory#createBean`来进行实际的Bean创建,并且将其属性通过`populateBean`方法注入。 +5. **延迟注入**: + - 如果一个字段或属性被`@Autowired`注解,并与`@Lazy`结合使用,实际的懒加载逻辑会在属性填充阶段被处理。 + - 使用`AutowiredAnnotationBeanPostProcessor`来处理带有`@Autowired`注解的属性的注入。 + - 在`AutowiredFieldElement#inject`方法中,如果字段被标记为`@Lazy`,Spring不会直接注入真实的Bean,而是注入一个懒加载代理。 + - 这个懒加载代理的实际行为是在首次访问时触发真正的Bean初始化。 +6. **懒加载代理的创建**: + - 使用`ContextAnnotationAutowireCandidateResolver`来检查依赖关系是否需要懒加载。 + - 如果需要懒加载,它将使用`buildLazyResolutionProxy`方法来为该依赖关系创建一个代理。 + - 这个代理的行为是:在首次访问时,它会解析和返回真正的Bean或其他依赖项。 + - 使用Spring的`ProxyFactory`来为给定的目标源创建新的代理。 \ No newline at end of file diff --git a/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/LazyApplication.java b/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/LazyApplication.java index 64964c2..4ca816f 100644 --- a/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/LazyApplication.java +++ b/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/LazyApplication.java @@ -13,11 +13,10 @@ public class LazyApplication { public static void main(String[] args) { System.out.println("启动 Spring ApplicationContext..."); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class); - System.out.println("完成启动 Spring ApplicationContext..."); + System.out.println("启动完成 Spring ApplicationContext..."); - System.out.println("准备获取MyService..."); + System.out.println("获取MyService..."); MyService myService = context.getBean(MyService.class); - System.out.println("成功获取MyService...-->" + myService); System.out.println("调用show方法..."); myService.show(); diff --git a/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/bean/MyBean.java b/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/bean/MyBean.java index 87fad41..65883f3 100644 --- a/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/bean/MyBean.java +++ b/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/bean/MyBean.java @@ -7,6 +7,6 @@ public class MyBean { } public void show() { - System.out.println("hello world"); + System.out.println("hello world!"); } } diff --git a/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/service/MyService.java b/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/service/MyService.java index 4fe18e8..74c368e 100644 --- a/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/service/MyService.java +++ b/spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/service/MyService.java @@ -11,7 +11,7 @@ public class MyService { private MyBean myBean; public void show() { - System.out.println("@Lazy MyBean = " + myBean.getClass()); + System.out.println("MyBean Class = " + myBean.getClass()); myBean.show(); } }