解密Spring中的Bean例項化:推斷構造方法(上)

努力的小雨發表於2024-03-04

在Spring中,一個bean需要透過例項化來獲取一個物件,而例項化的過程涉及到構造方法的呼叫。本文將主要探討簡單的構造推斷和例項化過程,讓我們首先深入瞭解例項化的步驟。

例項化原始碼

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    // Make sure bean class is actually resolved at this point.
    Class<?> beanClass = resolveBeanClass(mbd, beanName);

    .....

    // BeanDefinition中新增了Supplier,則呼叫Supplier來得到物件
    Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
    if (instanceSupplier != null) {
        return obtainFromSupplier(instanceSupplier, beanName);
    }

    // @Bean對應的BeanDefinition
    if (mbd.getFactoryMethodName() != null) {
        return instantiateUsingFactoryMethod(beanName, mbd, args);
    }

    // Shortcut when re-creating the same bean...
    // 一個原型BeanDefinition,會多次來建立Bean,那麼就可以把該BeanDefinition所要使用的構造方法快取起來,避免每次都進行構造方法推斷
    boolean resolved = false;
    boolean autowireNecessary = false;
    if (args == null) {
        synchronized (mbd.constructorArgumentLock) {
            if (mbd.resolvedConstructorOrFactoryMethod != null) {
                resolved = true;
                // autowireNecessary表示有沒有必要要進行注入,比如當前BeanDefinition用的是無參構造方法,那麼autowireNecessary為false,否則為true,表示需要給構造方法引數注入值
                autowireNecessary = mbd.constructorArgumentsResolved;
            }
        }
    }
    if (resolved) {
        // 如果確定了當前BeanDefinition的構造方法,那麼看是否需要進行對構造方法進行引數的依賴注入(構造方法注入)
        if (autowireNecessary) {
            // 方法內會拿到快取好的構造方法的入參
            return autowireConstructor(beanName, mbd, null, null);
        }
        else {
            // 構造方法已經找到了,但是沒有引數,那就表示是無參,直接進行例項化
            return instantiateBean(beanName, mbd);
        }
    }

    // 如果沒有找過構造方法,那麼就開始找了

    // Candidate constructors for autowiring?
    // 提供一個擴充套件點,可以利用SmartInstantiationAwareBeanPostProcessor來控制用beanClass中的哪些構造方法
    // 比如AutowiredAnnotationBeanPostProcessor會把加了@Autowired註解的構造方法找出來,具體看程式碼實現會更復雜一點
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);

    // 如果推斷出來了構造方法,則需要給構造方法賦值,也就是給構造方法引數賦值,也就是構造方法注入
    // 如果沒有推斷出來構造方法,但是autowiremode為AUTOWIRE_CONSTRUCTOR,則也可能需要給構造方法賦值,因為不確定是用無參的還是有參的構造方法
    // 如果透過BeanDefinition指定了構造方法引數值,那肯定就是要進行構造方法注入了
    // 如果呼叫getBean的時候傳入了構造方法引數值,那肯定就是要進行構造方法注入了
    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
            mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
        return autowireConstructor(beanName, mbd, ctors, args);
    }

    // Preferred constructors for default construction?
    ctors = mbd.getPreferredConstructors();
    if (ctors != null) {
        return autowireConstructor(beanName, mbd, ctors, null);
    }

    // No special handling: simply use no-arg constructor.
    // 不匹配以上情況,則直接使用無參構造方法
    return instantiateBean(beanName, mbd);
}

在Spring框架中的AbstractAutowireCapableBeanFactory類中的createBeanInstance()方法是用來執行例項化Bean物件的操作,該方法會根據Bean定義資訊和配置選項,經過一系列步驟和邏輯判斷,最終建立一個全新的Bean例項。大致步驟如下:

  • 根據BeanDefinition載入類並獲取對應的Class物件
  • 如果BeanDefinition繫結了一個Supplier,那麼應呼叫Supplier的get方法以獲取一個物件,並將其直接返回。
  • 如果在BeanDefinition中存在factoryMethodName屬性,則呼叫該工廠方法以獲取一個bean物件,並將其返回。
  • 如果BeanDefinition已經自動構造過了,那麼就呼叫autowireConstructor()方法來自動構造一個物件。
  • 呼叫SmartInstantiationAwareBeanPostProcessor介面的determineCandidateConstructors()方法,以確定哪些構造方法是可用的。
  • 如果存在可用的構造方法,或者當前BeanDefinition的autowired屬性設定為AUTOWIRE_CONSTRUCTOR,或者BeanDefinition中指定了構造方法引數值,或者在建立Bean時指定了構造方法引數值,那麼將呼叫autowireConstructor()方法來自動構造一個物件。
  • 最後,如果不符合前述情況,那麼將根據不帶引數的構造方法來例項化一個物件。

推斷構造方法

我們看下原始碼:

public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
        throws BeanCreationException {
  //前面跟@Lookup相關,我們不看
    .....

    // Quick check on the concurrent map first, with minimal locking.
    Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
    if (candidateConstructors == null) {
        // Fully synchronized resolution now...
        synchronized (this.candidateConstructorsCache) {
            candidateConstructors = this.candidateConstructorsCache.get(beanClass);
            if (candidateConstructors == null) {
                Constructor<?>[] rawCandidates;
                try {
                    // 拿到所有的構造方法
                    rawCandidates = beanClass.getDeclaredConstructors();
                }
                catch (Throwable ex) {
                    throw new BeanCreationException(beanName,
                            "Resolution of declared constructors on bean Class [" + beanClass.getName() +
                            "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
                }
                List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);

                // 用來記錄required為true的構造方法,一個類中只能有一個required為true的構造方法
                Constructor<?> requiredConstructor = null;
                // 用來記錄預設無參的構造方法
                Constructor<?> defaultConstructor = null;
                ......
                int nonSyntheticConstructors = 0;

                // 遍歷每個構造方法
                for (Constructor<?> candidate : rawCandidates) {
                    if (!candidate.isSynthetic()) {
                        // 記錄一下普通的構造方法
                        nonSyntheticConstructors++;
                    }
                    else if (primaryConstructor != null) {
                        continue;
                    }

                    // 當前遍歷的構造方法是否寫了@Autowired
                    MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);
                    if (ann == null) {
                        // 如果beanClass是代理類,則得到被代理的類的型別,我們不看這種情況
                        .......
                    }

                    // 當前構造方法上加了@Autowired
                    if (ann != null) {
                        // 整個類中如果有一個required為true的構造方法,那就不能有其他的加了@Autowired的構造方法
                        if (requiredConstructor != null) {
                            throw new BeanCreationException(beanName,
                                    "Invalid autowire-marked constructor: " + candidate +
                                    ". Found constructor with 'required' Autowired annotation already: " +
                                    requiredConstructor);
                        }

                        boolean required = determineRequiredStatus(ann);
                        if (required) {
                            if (!candidates.isEmpty()) {
                                throw new BeanCreationException(beanName,
                                        "Invalid autowire-marked constructors: " + candidates +
                                        ". Found constructor with 'required' Autowired annotation: " +
                                        candidate);
                            }
                            // 記錄唯一一個required為true的構造方法
                            requiredConstructor = candidate;
                        }
                        // 記錄所有加了@Autowired的構造方法,不管required是true還是false
                        // 如果預設無參的構造方法上也加了@Autowired,那麼也會加到candidates中
                        candidates.add(candidate);

                        // 從上面程式碼可以得到一個結論,在一個類中,要麼只能有一個required為true的構造方法,要麼只能有一個或多個required為false的方法
                    }
                    else if (candidate.getParameterCount() == 0) {
                        // 記錄唯一一個無參的構造方法
                        defaultConstructor = candidate;
                    }

                    // 有可能存在有參、並且沒有新增@Autowired的構造方法
                }


                if (!candidates.isEmpty()) {
                    // Add default constructor to list of optional constructors, as fallback.
                    // 如果不存在一個required為true的構造方法,則所有required為false的構造方法和無參構造方法都是合格的
                    if (requiredConstructor == null) {
                        if (defaultConstructor != null) {
                            candidates.add(defaultConstructor);
                        }
                        else if (candidates.size() == 1 && logger.isInfoEnabled()) {
                            logger.info("Inconsistent constructor declaration on bean with name '" + beanName +
                                    "': single autowire-marked constructor flagged as optional - " +
                                    "this constructor is effectively required since there is no " +
                                    "default constructor to fall back to: " + candidates.get(0));
                        }
                    }
                    // 如果只存在一個required為true的構造方法,那就只有這一個是合格的
                    candidateConstructors = candidates.toArray(new Constructor<?>[0]);
                }
                // 沒有新增了@Autowired註解的構造方法,並且類中只有一個構造方法,並且是有參的
                else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
                    candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
                }
                ......
                else {
                    // 如果有多個有參、並且沒有新增@Autowired的構造方法,是會返回空的
                    candidateConstructors = new Constructor<?>[0];
                }
                this.candidateConstructorsCache.put(beanClass, candidateConstructors);
            }
        }
    }
    return (candidateConstructors.length > 0 ? candidateConstructors : null);
}

接下來,讓我們更深入地探討推斷構造方法,並且我還簡單繪製了一張圖示。

image

通常情況下,一個類通常只包含一個構造方法:

要麼是無參的構造方法,要麼是有參的構造方法。

如果一個類只有一個無參構造方法,那麼在例項化時只能使用這個構造方法;而如果一個類只有一個有參構造方法,例項化時是否可以使用這個構造方法則取決於具體情況:

  • 使用AnnotationConfigApplicationContext進行例項化時,Spring會根據構造方法的引數資訊去尋找對應的bean,並將找到的bean傳遞給構造方法。這樣可以實現依賴注入,確保例項化的物件具有所需的依賴項。
  • 當使用ClassPathXmlApplicationContext時,表明使用XML配置檔案的方式來定義bean。在XML中,可以手動指定構造方法的引數值來例項化物件,也可以透過配置autowire=constructor屬性,讓Spring自動根據構造方法的引數型別去尋找對應的bean作為引數值,實現自動裝配。

上面是隻有一個構造方法的情況,那麼如果一個類存在多個構造方法,那麼Spring進行例項化之前,該如何去確定到底用哪個構造方法呢?

  • 如果開發者明確定義了他們想要使用的構造方法,那麼程式將會優先使用這個構造方法。
  • 如果開發者沒有明確指定他們想要使用的構造方法,系統會自動檢查開發者是否已經配置了讓Spring框架自動選擇構造方法的選項。
  • 如果開發者沒有顯式地指定讓Spring框架自動選擇構造方法的情況下,Spring將會預設嘗試使用無參構造方法。如果目標類中沒有無參構造方法,則系統將會丟擲錯誤提示。

針對第一點,開發者可以透過什麼方式來指定使用哪個構造方法呢?

  • 在XML中,標籤用於表示構造方法的引數。開發者可以根據這個標籤確定所需使用的構造方法的引數個數,從而精確指定想要使用的構造方法。
  • 透過@Autowired註解,我們可以在構造方法上使用@Autowired註解。因此,當我們在特定構造方法上使用@Autowired註解時,表示開發者希望使用該構造方法。與透過xml方式直接指定構造方法引數值的方式不同,@Autowired註解方式需要Spring透過byType+byName的方式來查詢符合條件的bean作為構造方法的引數值。
  • 當然,還有一種情況需要考慮,即當多個構造方法上都標註了@Autowired註解時,此時Spring會丟擲錯誤。然而,由於@Autowired註解具有一個required屬性,預設值為true,因此在一個類中只能有一個構造方法標註了@Autowired或者@Autowired(required=true),否則會導致錯誤。不過,可以有多個構造方法標註@Autowired(required=false)。在這種情況下,Spring會自動從這些構造方法中選擇一個進行注入。

在第二種情況下,如果開發者沒有明確指定要使用的構造方法,Spring將嘗試自動選擇一個合適的構造方法進行注入。這種情況下,只能透過ClassPathXmlApplicationContext實現,因為使用AnnotationConfigApplicationContext無法指定讓Spring自動選擇構造方法。透過ClassPathXmlApplicationContext,可以在XML配置檔案中指定某個bean的autowire屬性為constructor,從而告訴Spring可以自動選擇構造方法。

總結

在Spring中,例項化Bean物件涉及構造方法的呼叫。透過分析原始碼,我們瞭解到例項化的步驟和推斷構造方法的過程。當一個類只有一個構造方法時,Spring會根據具體情況決定是否使用該構造方法。如果一個類存在多個構造方法,就需要根據具體情況具體分析。本文簡單判斷了哪些構造方法是符合例項化的,但在存在多個符合條件的構造方法時,具體使用哪個構造方法尚未討論。因此,我計劃單獨寫一篇文章來詳細討論這個問題。畢竟是太複雜了。

相關文章