Spring框架中@Lazy延迟加载原理和使用细节

目录

一、@Lazy延迟加载原理

1.延迟加载原理

1.1 @Lazy三种配置方法

1.2 @ComponentScan配置延迟加载

1.3 加载原理

2.延迟加载实现原理

2.1 AbstractApplicationContext

2.2 DefaultListableBeanFactory

二、使用细节

1.@Lazy失效实例

1.1 Controller非延迟加载类

1.2 Service延迟加载类

1.3 结果输出

2.@Lazy起效实例

2.1 修改的Controller实例

三、总结


一、@Lazy延迟加载原理

如果某个类想要使它在Spring启动时不加载我们听的最多的便是为其加上@Lazy注解或者在@ComponentScan扫描注解中设置lazyInit为true即可完成。那么我们先来看看这两者分别的实现原理。

1.延迟加载原理

1.1 @Lazy三种配置方法

我们使用延迟加载一般有三种实现方式,第一种也是最原始的配置方式是在XML文件中直接配置标签属性:

<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 加载原理

当使用上述三种配置后,Spring在扫描加载Bean时会读取@Lazy和@Component注解相应值,并设置Bean定义的lazyInit属性。读取注解配置时最终会调用ClassPathBeanDefinitionScanner及其子类实现的doScan方法,在这个方法中完成注解的读取配置。

关键源码如下:

public class ClassPathBeanDefinitionScanner 
        extends ClassPathScanningCandidateComponentProvider {
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
       // 不管是读取注解或者XML配置方式bean,最终读取加载Bean时都会进入到该方法
       // 对相应的包进行处理
       // beanDefinitions是保存返回bean定义的集合
       Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
       // 遍历多个包下的类
       for (String basePackage : basePackages) {
          // 获取满足条件的bean定义集合
          Set<BeanDefinition> candidates = 
                  findCandidateComponents(basePackage);
          // 对每个bean定义进行处理
          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
             // 默认bean定义值的对象中,在postProcessBeanDefinition方法中
             // 会首先应用一次这些默认值,其中就包括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) {
       // 此处会应用默认值,如lazyInit、autowireMode、initMethod等
       beanDefinition.applyDefaults(this.beanDefinitionDefaults);
       if (this.autowireCandidatePatterns != null) {
          beanDefinition.setAutowireCandidate(PatternMatchUtils
                  .simpleMatch(this.autowireCandidatePatterns, beanName));
       }
    }
}

经过ClassPathBeanDefinitionScanner或子类实现的扫描读取后,延迟加载的配置便被配置到了Bean定义中,等初始化时再使用该属性,这里需要注意的是@ComponentScan延迟加载属性是可以被@Lazy覆盖的,因为@Lazy是在@ComponentScan后面处理的。

2.延迟加载实现原理

前面我们已经知道了在何处读取注解配置的属性,现在我们稍微看下其具体判断实现的地方。

2.1 AbstractApplicationContext

Spring框架在刷新时会初始化非延迟加载的单例bean,而一般我们使用的bean都是单例的。其关键源码如下:

public abstract class AbstractApplicationContext 
        extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        ...
        // 刷新流程中执行初始化非延迟单例的方法
        finishBeanFactoryInitialization(beanFactory);
        ...
    }
    protected void finishBeanFactoryInitialization(
            ConfigurableListableBeanFactory beanFactory) {
        ...
        // 实际执行初始化非延迟加载单例
        beanFactory.preInstantiateSingletons();
    }
}

2.2 DefaultListableBeanFactory

最终会调用Spring工厂来实例化,直接看到其实现方法源码:

public class DefaultListableBeanFactory 
        extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry,
        Serializable {
    // 当BeanDefinition被创建注册到工厂中时bean定义的名字将会被保存到这个集合中
    // 且里面的顺序为注册进来的顺序
    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()便是判断非延迟加载的,因此前面获取到的延迟加载
           // 属性会在这里进行判断
           if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
              // 这里面会判断是否是FactoryBean类型,最终都会调用到getBean中
              ...
           }
        }
        // 后续略过
        ...
    }
}

二、使用细节

读取源码其中一个目的是为了更好的实际的应用以及准确的把握对应功能的生效范围,因此在使用延迟加载时需要额外注意一些点。Spring框架延迟加载属性在调用getBean之后将会失效,因为getBean方法是初始化bean的入口,这不难理解,那么平时我们使用@Autowired等自动注入注解时能和@Lazy注解一起使用吗?接下来我们从两个实例来说明一下,这两个实例都是使用平时的使用用法,在Component上添加@Lazy注解,且让其实现InitializingBean接口,当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服务类:

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

1.3 结果输出

启动程序后控制台输出:

testService Initializing
testController Initializing

启动完Spring程序后输出了TestService里面打印的字符串。这就奇怪了,明明使用了@Lazy注解,但是却并没有其作用,在Spring启动项目时还是加载了这个类?这就涉及到@Autowired等自动注入注解的使用了,如果有兴趣了解其实现的可以去看文章(二)Spring框架原理之实例化bean和@Autowired实现原理

由于Controller类不是延迟加载的,且里面使用@Autowired自动注入注解注入了Service,因此在程序初始化时Controller将会被初始化,同时在处理@Autowired注解的字段时,会调用getBean方法从Spring工厂中获取字段的bean对象,因此通过@Autowired路线加在了Service,这就导致了@Lazy注解失效了,因此虽然没通过refresh方法流程初始化,但是却通过@Autowired的处理类初始化了。

2.@Lazy起效实例

想要@Lazy注解起作用,只需要改一步,即把Controller也改成@Lazy,让其在启动时不被加载,不触发@Autowired注解依赖链的调用即可。

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。

三、总结

从上面的例子我们可以总结及延伸出两个注意点:

  1. 非延迟加载的类中不能自动注入延迟加载的类,会导致延迟加载失效;
  2. 如果想要实现某个类延迟加载使用自动注入功能时需要调用链前都不存在非延迟加载类,否则延迟加载失效。

作用效果总结图如下:

其实@Scope指定原型和单例时有些情况也会导致原型bean“失效”,这又是另外一个故事了,后面有机会再分析一波。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值