SpringBoot预加载与懒加载
创始人
2024-01-22 14:22:34

预加载

bean在springBoot启动过程中就完成创建加载
在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);}//所有要进行初始的BeanList beanNames = new ArrayList<>(this.beanDefinitionNames);// 对所有Bean进行初始化,除了懒加载for (String beanName : beanNames) {// 去合并BeanRootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);//非抽象、单例、懒加载才会进行注册if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {//判断是不是 FactoryBean,简单说是我们这个 Bean 实现了if (isFactoryBean(beanName)) {// 是不是 FactoryBean,获取 FactoryBean 的方式就是  前缀+beanNameObject bean = getBean(FACTORY_BEAN_PREFIX + beanName);// 判断是否 FactoryBeanif (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 {// 判断 是不是 这个类的,如果是就去创建 SmartFactoryBean 属于 FactoryBean 子接口,拥有更加细粒度操作原数据的方式,isEagerInit = (factory instanceof SmartFactoryBean &&((SmartFactoryBean) factory).isEagerInit());}if (isEagerInit) {getBean(beanName);}}} else {//获取具体的BeangetBean(beanName);}}}// Trigger post-initialization callback for all applicable beans...for (String beanName : beanNames) {Object singletonInstance = getSingleton(beanName);// 执行所有 单例Bean 的回调,当然Bean 需要实现这个接口~~~if (singletonInstance instanceof SmartInitializingSingleton) {SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction) () -> {smartSingleton.afterSingletonsInstantiated();return null;}, getAccessControlContext());} else {smartSingleton.afterSingletonsInstantiated();}}}} 

getMergedLocalBeanDefinition

这里是去合并Bean去了,这里其实有两个动作

将取出来的 BeanDefinition 进行合并
将BeanDefinition 转换成 RottBeanDefinition 也就说顶级的 Bean ,此处的顶级Bean 指的就是User extends SuperUser,可以认为是是 User,也可以认为是 SuperUser,如果是 User 就代表了已经进行了合并,如果 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 和BeanDefinitionreturn getMergedBeanDefinition(beanName, bd, null);
}
  • 具体的逻辑
    protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)throws BeanDefinitionStoreException {// 将 mergedBeanDefinitions 防止线程安全,为什么?// 虽然 ConcurrentHashMap 是线程安全的,但是下面的业务并不一定是线程安全的,所以要加锁synchronized (this.mergedBeanDefinitions) {RootBeanDefinition mbd = null;RootBeanDefinition previous = null;// 这里为空,代表的是 当前的 BeanDefinition 是顶层的 Bean 不存在 嵌套Beanif (containingBd == null) {// 获取当前Bean,为什么又一次获取了呢?因为如果在多线程操作下 可能 这个Bean已经被修改了,所以重新获取一次mbd = this.mergedBeanDefinitions.get(beanName);}//如果缓存中没有,或者过期了,则会重新创建一个if (mbd == null || mbd.stale) {previous = mbd;//如果 父 parentName 为空if (bd.getParentName() == null) {// 如果当前类型就是 RootBeanDefinitionif (bd instanceof RootBeanDefinition) {// 进行克隆~~~mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();} else {// 如果不是就会将 其他 BeanDefinition 转换成 RootBeanDefinition 代表的是当前 BeanDefinition 为顶级 Beanmbd = new RootBeanDefinition(bd);}}//  如果父 BeanDefinition 不为 空,也就代表了 当前类存在继承,如果不理解这段的话,可以看一下 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 转换查找,如果不是 抛出异常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);}//设置成单例,如果之前没设置的话~~~~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的元数据if (containingBd == null && isCacheBeanMetadata()) {// 将当前的 BeanDefinitions 放入到map中,进行缓存this.mergedBeanDefinitions.put(beanName, mbd);}}if (previous != null) {copyRelevantMergedBeanDefinitionCaches(previous, mbd);}return mbd;}}

总结这里的合并是指将 父级的Bean 合并到子 中,例如 user extends superUser
也就说讲 super中的 属性 合并到 user 中,父类的BeanDefinition会被子类的BeanDefinition继承。

循环创建bean

	if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {

对于非抽象,单例,非懒加载的bean分别调用getBean方法
getBean方法比较复杂,简单总结下就创建bean的对象并且创建bean依赖的对象并且注入到当前bean完成对bean的初始化

懒加载

@Lazy

在类上加上@Lazy标签,那么就开启了懒加载

@Service("helloServiceB")
@Lazy
public class HelloServiceB {
············

懒加载指的是,初始化时不会创建实例,在真正被使用到的时候再进行加载。
看了前面的预加载可以知道,在preInstantiateSingletons方法中会跳过懒加载的bean。

如果懒加载的bean被依赖会怎么样?
比如又有serviceA依赖了ServiceB

@Service("helloServiceA")
public class HelloServiceA implements HelloService {@Autowiredprivate HelloService helloServiceB;

那么此时HelloServiceB懒加载会失效
HelloServiceA没有@Lazy标签会在启动时预加载通过getBean方法创建。同时会注入其依赖的bean。serviceB也会被创建。
因此要使懒加载生效,应该在HelloServiceA也加@Lazy注解

全局懒加载

一般情况程序在启动时时有大量的 Bean 需要初始化,例如 数据源初始化、缓存初始化等导致应用程序启动非常的慢。在 spring boot 2.2 之前的版本,我们对这些 bean 使用手动增加 @Lazy 注解,来实现启动时不初始化,业务程序在调用需要时再去初始化,如上代码修改为即可:

为什么需要全局懒加载

同上文中提到我们需要手动在 bean 增加 @Lazy 注解,这就意味着我们仅能对程序中自行实现的 bean 进行添加。但是现在 spring boot 应用中引入了很多第三方 starter ,比如 druid-spring-boot-starter 数据源注入、spring-boot-starter-data-redis 缓存等默认情况下, 引入即注入了相关 bean 我们无法去修改添加 @Lazy

  • spring boot 2.2 新增全局懒加载属性,开启后全局 bean 被设置为懒加载,需要时再去创建
spring:main:lazy-initialization: true

原理
在SpringApplication#prepareContext方法中

if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}

如果开启了lazy-initialization,那么添加LazyInitializationBeanFactoryPostProcessor
LazyInitializationBeanFactoryPostProcessor执行,会将beanFactory中的bean设置lazyinit

public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// Take care not to force the eager init of factory beans when getting filtersCollection 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 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 可以通过设置 @Lazy(false) 排除,设置为启动时加载
@Lazy(false)
@Configuration
public class DemoConfig {}
  • 当然也可以指定规则实现 LazyInitializationExcludeFilter 规则实现排除
@Bean LazyInitializationExcludeFilter integrationLazyInitExcludeFilter() { return LazyInitializationExcludeFilter.forBeanTypes(DemoConfig.class); }

全局懒加载的好处与问题

当项目比较大时。开发人员本地调试时,并不需要使用到全部的bean,那么开启全局懒加载可以节省很多启动项目的时间
通过设置全局懒加载,我们可以减少启动时的创建任务从而大幅度的缩减应用的启动时间。但全局懒加载的缺点可以归纳为以下两点:

  • Http 请求处理时间变长。 这里准确的来说是第一次 http 请求处理的时间变长,之后的请求不受影响
  • 错误不会在应用启动时抛出,不利于早发现、早解决、早下班。

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
ps局部放大 放大某个部位的图... 软件类学习建议:边看边练习,可以先收藏后学习。一、使用Ctrl快捷键放大:Ctrl + +缩小:Ct...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
ps局部放大 放大某个部位的图... 软件类学习建议:边看边练习,可以先收藏后学习。一、使用Ctrl快捷键放大:Ctrl + +缩小:Ct...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...