【追根究底】@Lazy注解为什么会失效?

==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍


@Lazy注解为什么会失效?它并没有失效,一直都是生效着的,之所以认为它失效了,是没有用对它,没有理解它!

不想看分析的,可以直接飞到总结

我想让B最后再实例化,因为实例化的时候,会为B创建代理,并且加入增强器。但是有些情况,实例化其它类的时候,某个增强器还未生成,这时候其它类又使用到了B,导致B在增强器之前实例化了,最后B就加入不了增强器了。下面2个使用@Lazy的CASE,都会发生什么?B又能不能在最后再实例化?来个图可能会更清楚我提出的问题。其实问题来源自【追根究底】 为什么@Transactional注解失效了?,从这个问题,引起我对@Lazy原理的探究。
在这里插入图片描述


以下所有CASE的测试入口,都是这个

@SpringBootTest
class LazyDemoApplicationTests {
	@Autowired
	A a;
	@Test
	void contextLoads() {
		a.sayA();
	}
}
  • CASE1
    B会在A之后实例化吗?

    @Component
    public class A {
        @Autowired
        B b;
    	
    	public void sayA(){
        	b.sayB();
    	}
    }
    
    @Lazy
    @Component
    public class B {
    	public void sayB(){}
    }
    

    答案是会


  • CASE2
    B会在A初始化的时候实例化吗?
    @Component
    public class A {
    	@Lazy
        @Autowired
        B b;
    	
    	public void sayA(){
            b.sayB();
        }
    }
    
    @Component
    public class B {
    	public void sayB(){}
    }
    
    答案是不会的

  • CASE3
    @Component
    public class A {
    	@Lazy
        @Autowired
        B b;
    	
    	public void sayA(){
            b.sayB();
        }
    }
    
    @Lazy
    @Component
    public class B {
    	public void sayB(){}
    }
    

  • CASE1分析
    首先,以菜鸟的思维先揣测一下,因为B类加了@Lazy,所以必定会晚于A实例化,就算A里用了B。
    好,我们跟进源码一看便知!
    第一阶段线路,refreshContext -> refresh -> finishBeanFactoryInitialization -> preInstantiateSingletons

    @Override
    public void preInstantiateSingletons() throws BeansException {
    	……
    	// Iterate over a copy to allow for init methods which in turn register new bean definitions.
    	// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
    	List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
    
    	// Trigger initialization of all non-lazy singleton beans...
    	for (String beanName : beanNames) {
    		RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    		if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
    			if (isFactoryBean(beanName)) {
    				Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
    				……
    			}
    			else {
    				getBean(beanName);
    			}
    		}
    	}
    
    	……
    }
    

    看英文注释,也可以知道,就是从这里开始实例化所有非懒加载的类的。if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit())条件表示,非抽象类并且是单例,而且不是非懒加载的bean,就getBean,否则啥也不做。所以可以判断出,B是不会在这一步实例化的。那么我接着往下看A的实例化会发生什么。
    第二阶段线路,getBean -> doGetBean -> createBean -> doCreateBean

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    		throws BeanCreationException {
    
    	// Instantiate the bean.
    	BeanWrapper instanceWrapper = null;
    	if (mbd.isSingleton()) {
    		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    	}
    	if (instanceWrapper == null) {
    		instanceWrapper = createBeanInstance(beanName, mbd, args);
    	}
    	……
    	// Initialize the bean instance.
    	Object exposedObject = bean;
    	try {
    		populateBean(beanName, mbd, instanceWrapper);
    		exposedObject = initializeBean(beanName, exposedObject, mbd);
    	}
    	catch (Throwable ex) {
    		……
    	}
    	……
    	return exposedObject;
    }
    

    看注释也能看出,第一步先实例化bean A,然后初始化它,初始化的时候,需要为它注入B。由此,引出第三阶段路线。
    第三阶段线路,populateBean -> postProcessProperties -- AutowiredAnnotationBeanPostProcessor -> inject -- AutowiredFieldElement -> resolveDependency

    public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
    		@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    	……
    	else {
    		Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
    				descriptor, requestingBeanName);
    		if (result == null) {
    			result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    		}
    		return result;
    	}
    }
    

    从截出来的代码可以看出,获取xxx,获取不到,就想另一种办法获取xxx。接着看此次的重点getLazyResolutionProxyIfNecessary方法

    public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
    	return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
    }
    

    它会先判断这个bean是否标记了@Lazy

      protected boolean isLazy(DependencyDescriptor descriptor) {
      	for (Annotation ann : descriptor.getAnnotations()) {
      		Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
      		if (lazy != null && lazy.value()) {
      			return true;
      		}
      	}
      	MethodParameter methodParam = descriptor.getMethodParameter();
      	if (methodParam != null) {
      		Method method = methodParam.getMethod();
      		if (method == null || void.class == method.getReturnType()) {
      			Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
      			if (lazy != null && lazy.value()) {
      				return true;
      			}
      		}
      	}
      	return false;
      }
    

    CASE1中,A里面的B并没有标记@Lazy,所以不会走这一步,直接返回null。然后通过doResolveDependency这个方法去实例化B。
    所以CASE1,B在A之前实例化了。

  • CASE2分析
    首先,以菜鸟的思维先揣测一下,因为A中的B加了@Lazy,所以必定会晚于A实例化,就B类没有标@Lazy
    由CASE1的分析得出,必定会走buildLazyResolutionProxy这个方法,看这个方法的名字,似乎是要为B创建一个代理?

    protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
    	Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory,
    			"BeanFactory needs to be a DefaultListableBeanFactory");
    	final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
    	TargetSource ts = new TargetSource() {
    		@Override
    		public Class<?> getTargetClass() {
    			return descriptor.getDependencyType();
    		}
    		@Override
    		public boolean isStatic() {
    			return false;
    		}
    		@Override
    		public Object getTarget() {
    			Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);
    			if (target == null) {
    				Class<?> type = getTargetClass();
    				if (Map.class == type) {
    					return Collections.emptyMap();
    				}
    				else if (List.class == type) {
    					return Collections.emptyList();
    				}
    				else if (Set.class == type || Collection.class == type) {
    					return Collections.emptySet();
    				}
    				throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
    						"Optional dependency not present for lazy injection point");
    			}
    			return target;
    		}
    		@Override
    		public void releaseTarget(Object target) {
    		}
    	};
    	ProxyFactory pf = new ProxyFactory();
    	pf.setTargetSource(ts);
    	Class<?> dependencyType = descriptor.getDependencyType();
    	if (dependencyType.isInterface()) {
    		pf.addInterface(dependencyType);
    	}
    	return pf.getProxy(beanFactory.getBeanClassLoader());
    }
    

    接下来看看TargetSource是这个什么东西,但是现在又说不清楚,等后面说,所以我们跳过这一大段,看最后的,很明显,为B创建一个代
    理,记住,这里并没有走bean的实例化。

    好了,A就算初始化完了,接下来该到B了,看图debug!
    在这里插入图片描述
    跟进去
    在这里插入图片描述
    尝试从缓存中拿B的单例,如果存在,就会直接返回,如果不存在,就走createBean创建。
    在这里插入图片描述
    返回是null,也再次说明了,刚才B并没有被创建,只是创建了个代理对象而已!

    所以CASE2,B在A之后实例化。
    接下来看看哪里会用到TargetSource,跟着调试一下
    在这里插入图片描述
    接着,单步跟进去,可以看到,b确实是一个代理对象
    在这里插入图片描述
    再单步跟进去,这里拿到的就是刚才那个TargetSource,然后调用getTarget的时候,会走到刚才那里。
    在这里插入图片描述
    再单步跟进去,beanFactory.doResolveDependency
    在这里插入图片描述
    接着就会尝试去创建B(有兴趣的同学可以一步步跟到这里,因为链路太长,所以就不一一截图了)
    在这里插入图片描述
    但是,发现B已经创建好了,直接从缓存中拿到了
    在这里插入图片描述
    可以看到,确实返回了B的实例
    在这里插入图片描述
    在这里插入图片描述
    经过上面的分析,可以知道,不管B在A实例化之前有没有创建,A中的B都是一个代理对象,不是真实实例,只有当使用到了才会去创建或者拿到已经存在的实例!

  • CASE3分析
    由CASE2分析得知,B只有在使用的时候才会被实例化。并且和CASE2不同的是,由于类上也加了@Lazy,所以在preInstantiateSingletons中不会进入if中。

  • 总结

    • @Lazy注解永远遵循一条规则,如果被@Lazy标记的bean,只要使用到了,就会被实例化。这和@Lazy想要表达的并不矛盾,只是和你使用的方式产生矛盾,你想人家最后才加载,但是你又在别的bean加载的时候,使用到了这个懒加载的bean,那它还不得乖乖加载?不然程序就起不来了。
    • 如果是某个bean中某个字段被标记为@Lazy,那么初始化这个bean的时候,会为这个字段创建一个代理,这仅仅只是一个代理,并没有走bean的创建过程
    • @Lazy处理的情况只有两种,第一种是在类上直接标记,那么在preInstantiateSingletons方法中就不会去getBean,也就是直接跳过了。第二种就是其它标记(属性、方法、参数),那么会为它创建一个代理对象,并无并且将真真正实例化bean放在了TargetSource中的getTarget方法中。
    • 最后,回到标题,@Lazy注解为什么会失效?它并没有失效,一直都是生效着的,之所以认为它失效了,是没有用对它,没有理解它!
原创文章 257 获赞 277 访问量 69万+

猜你喜欢

转载自blog.csdn.net/qq_18297675/article/details/103267125