【追根究底】@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註解爲什麼會失效?它並沒有失效,一直都是生效着的,之所以認爲它失效了,是沒有用對它,沒有理解它!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章