Spring5源碼解析-@Autowired

你有沒有思考過Spring中的@Autowired注解弹砚?通常用于方便依賴注入,而隱藏在這個過程之后的機(jī)制到底是怎樣枢希,將在本篇中進(jìn)行講述桌吃。


@Autowired所具有的功能

@Autowired是一個用來執(zhí)行依賴注入的注解。每當(dāng)一個Spring管理的bean發(fā)現(xiàn)有這個注解時候苞轿,它會直接注入相應(yīng)的另一個Spring管理的bean茅诱。

該注解可以在不同的層次上應(yīng)用:

類字段:Spring將通過掃描自定義的packages(例如在我們所注解的controllers)或通過在配置文件中直接查找bean逗物。
方法:使用@Autowired注解的每個方法都要用到依賴注入。但要注意的是瑟俭,方法簽名中呈現(xiàn)的所有對象都必須是Spring所管理的bean翎卓。如果你有一個方法,比如setTest(Article article, NoSpringArticle noSpringArt) 摆寄,其中只有一個參數(shù) (Article article)是由Spring管理的失暴,那么就將拋出一個org.springframework.beans.factory.BeanCreationException異常。這是由于Spring容器里并沒有指定的一個或多個參數(shù)所指向的bean微饥,所以也就無法解析它們逗扒。完整的異常跟蹤如下:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.krams.tutorial.controller.TestController.ix(com.mysite.controller.IndexController,com.mysite.nospring.data.Article); nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.mysite.nospring.data.Article] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

構(gòu)造函數(shù):@Autowired的工作方式和方法相同。
對象注入需要遵循一些規(guī)則欠橘。一個bean可以按照下面的方式注入:

名稱:bean解析是通過bean名稱(看后面的例子)矩肩。
類型:解析過程基于bean的類型。
在某些情況下肃续,@Autowired應(yīng)該通過@Qualifier注解協(xié)作注入蛮拔。例如下面幾個是相同類型的bean:

<bean name="comment1" class="com.waitingforcode.Comment">
    <property name="text" value="Content of the 1st comment" />
</bean>
 
<bean name="comment2" class="com.waitingforcode.Comment">
    <property name="text" value="Content of the 2nd comment" />
</bean>

上面這種情況,假如只是一個簡單的@Autowired痹升,Spring根本不知道你要注入哪個bean。這就是為什么我們要使用@Qualifier(value =“beanName”)這個注解畦韭。在我們的例子中疼蛾,要從 com.waitingforcode.Comment這個類型的bean中區(qū)分comment1,comment2,我們可以寫下面的代碼:

@Qualifier(value="comment1")
@Autowired
private Comment firstComment;
 
@Qualifier(value="comment2")
@Autowired
private Comment secondComment;


在Spring中如何使用@Autowired

正如前面部分所看到的艺配,我們知道了在Spring中實現(xiàn)@Autowired的不同方法察郁。在這一部分中,我們將使用XML配置的方式激活@Autowired注解來自動注入转唉。然后皮钠,我們將編寫一個簡單的類并配置一些bean。最后赠法,我們將分別在另外兩個類中使用它們:由@Controller注解的控件和不由Spring所管理的類麦轰。(為什么用XML配置來做例子,我覺得這樣更直觀砖织,其實XML和使用注解沒多少區(qū)別款侵,都是往容器里添加一些bean和組織下彼此之間的依賴而已,不必要非要拘泥于一種形式侧纯,哪種順手用哪種新锈,不過Springboot自定義的這些還是推薦使用注解了)

我們從啟動注解的自動注入開始:

<context:annotation-config />

你必須將上面這個放在應(yīng)用程序上下文配置中。它可以使在遇到@Autowired注解時啟用依賴注入眶熬。

現(xiàn)在妹笆,我們來編寫和配置我們的bean:

// beans first
public class Comment {
 
    private String content;
     
    public void setContent(String content) {
        this.content = content;
    }
     
    public String getContent() {
        return this.content;
    }
     
}
 
// sample controller
@Controller
public class TestController {
     
    @Qualifier(value="comment1")
    @Autowired
    private Comment firstComment;
     
    @Qualifier(value="comment2")
    @Autowired
    private Comment secondComment;
     
    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public String test() {
        System.out.println("1st comment text: "+firstComment.getText());
        System.out.println("2nd comment text: "+secondComment.getText());
        return "test";
    }
 
}
 
// no-Spring managed class
public class TestNoSpring {
 
    @Autowired
    private Comment comment;
     
     
    public void testComment(String content) {
        if (comment == null) {
            System.out.println("Comment's instance wasn't autowired because this class is not Spring-managed bean");
        } else {
            comment.setContent(content);
            System.out.println("Comment's content: "+comment.getContent());
        }
    }
     
}

XML配置(在前面部分已經(jīng)看到過):

<bean name="comment1" class="com.specimen.exchanger.Comment">
    <property name="content" value="Content of the 1st comment" />
</bean>
 
<bean name="comment2" class="com.specimen.exchanger.Comment">
    <property name="content" value="Content of the 2nd comment" />
</bean>

現(xiàn)在块请,我們打開http://localhost:8080/test來運(yùn)行TestController。如預(yù)期的那樣拳缠,TestController的注解字段正確地自動注入墩新,而TestNoSpring的注解字段并沒有注入進(jìn)去:

1st comment text: Content of the 1st comment
2nd comment text: Content of the 2nd comment
Comment's instance wasn't autowired because this class is not Spring-managed bean

哪里不對 ?TestNoSpring類不由Spring所管理脊凰。這就是為什么Spring不能注入Comment實例的依賴抖棘。我們將在下一部分中解釋這個概念。


@Autowired注解背后的工作原理狸涌?

在討論代碼細(xì)節(jié)之前切省,我們再來了解下基礎(chǔ)知識。Spring管理可用于整個應(yīng)用程序的Java對象bean帕胆。他們所在的Spring容器朝捆,被稱為應(yīng)用程序上下文。這意味著我們不需要處理他們的生命周期(初始化懒豹,銷毀)芙盘。該任務(wù)由此容器來完成。另外脸秽,該上下文具有入口點儒老,在Web應(yīng)用程序中,是dispatcher servlet记餐。容器(也就是該上下文)會在它那里被啟動并且所有的bean都會被注入驮樊。

說的再清楚點,請看<context:annotation-config />的定義:

<xsd:element name="annotation-config">
        <xsd:annotation>
            <xsd:documentation><![CDATA[
    Activates various annotations to be detected in bean classes: Spring's @Required and
    @Autowired, as well as JSR 250's @PostConstruct, @PreDestroy and @Resource (if available),
    JAX-WS's @WebServiceRef (if available), EJB 3's @EJB (if available), and JPA's
    @PersistenceContext and @PersistenceUnit (if available). Alternatively, you may
    choose to activate the individual BeanPostProcessors for those annotations.
    Note: This tag does not activate processing of Spring's @Transactional or EJB 3's
    @TransactionAttribute annotation. Consider the use of the <tx:annotation-driven>
    tag for that purpose.
    See javadoc for org.springframework.context.annotation.AnnotationConfigApplicationContext
    for information on code-based alternatives to bootstrapping annotation-driven support.
            ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>

可以看出 : 類內(nèi)部的注解片酝,如:@Autowired囚衔、@Value、@Required雕沿、@Resource以及EJB和WebSerivce相關(guān)的注解练湿,是容器對Bean對象實例化和依賴注入時,通過容器中注冊的Bean后置處理器處理這些注解的审轮。

所以配置了上面這個配置(<context:component-scan>假如有配置這個肥哎,那么就可以省略<context:annotation-config />)后,將隱式地向Spring容器注冊AutowiredAnnotationBeanPostProcessor断国、CommonAnnotationBeanPostProcessor贤姆、RequiredAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor以及這4個專門用于處理注解的Bean后置處理器稳衬。

當(dāng) Spring 容器啟動時霞捡,AutowiredAnnotationBeanPostProcessor 將掃描 Spring 容器中所有 Bean,當(dāng)發(fā)現(xiàn) Bean 中擁有@Autowired 注解時就找到和其匹配(默認(rèn)按類型匹配)的 Bean薄疚,并注入到對應(yīng)的地方中去碧信。 源碼分析如下:

通過org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor可以實現(xiàn)依賴自動注入赊琳。通過這個類來處理@Autowired和@Value這倆Spring注解。它也可以管理JSR-303的@Inject注解(如果可用的話)砰碴。在AutowiredAnnotationBeanPostProcessor構(gòu)造函數(shù)中定義要處理的注解:

public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
        implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
        ...
    /**
     * Create a new AutowiredAnnotationBeanPostProcessor
     * for Spring's standard {@link Autowired} annotation.
     * <p>Also supports JSR-330's {@link javax.inject.Inject} annotation, if available.
     */
    @SuppressWarnings("unchecked")
    public AutowiredAnnotationBeanPostProcessor() {
        this.autowiredAnnotationTypes.add(Autowired.class);
        this.autowiredAnnotationTypes.add(Value.class);
        try {
            this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
                    ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
            logger.info("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
        }
        catch (ClassNotFoundException ex) {
            // JSR-330 API not available - simply skip.
        }
    }
    ...
    }

之后躏筏,有幾種方法來對@Autowired注解進(jìn)行處理。

第一個呈枉,private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz)解析等待自動注入類的所有屬性趁尼。它通過分析所有字段和方法并初始化org.springframework.beans.factory.annotation.InjectionMetadata類的實例來實現(xiàn)。

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
        LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>();
        Class<?> targetClass = clazz;
        do {
            final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>();
            //分析所有字段
            ReflectionUtils.doWithLocalFields(targetClass, field -> {
              //findAutowiredAnnotation(field)此方法后面會解釋
                AnnotationAttributes ann = findAutowiredAnnotation(field);
                if (ann != null) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("Autowired annotation is not supported on static fields: " + field);
                        }
                        return;
                    }
                    boolean required = determineRequiredStatus(ann);
                    currElements.add(new AutowiredFieldElement(field, required));
                }
            });
            //分析所有方法
            ReflectionUtils.doWithLocalMethods(targetClass, method -> {
                Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
                if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                    return;
                }
                AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
                if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                    if (Modifier.isStatic(method.getModifiers())) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("Autowired annotation is not supported on static methods: " + method);
                        }
                        return;
                    }
                    if (method.getParameterCount() == 0) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("Autowired annotation should only be used on methods with parameters: " +
                                    method);
                        }
                    }
                    boolean required = determineRequiredStatus(ann);
                    PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                    currElements.add(new AutowiredMethodElement(method, required, pd));
                }
            });
            elements.addAll(0, currElements);
            targetClass = targetClass.getSuperclass();
        }
        while (targetClass != null && targetClass != Object.class);
        //返回一個InjectionMetadata初始化的對象實例
        return new InjectionMetadata(clazz, elements);
    }
...
  /**
     * 'Native' processing method for direct calls with an arbitrary target instance,
     * resolving all of its fields and methods which are annotated with {@code @Autowired}.
     * @param bean the target instance to process
     * @throws BeanCreationException if autowiring failed
     */
    public void processInjection(Object bean) throws BeanCreationException {
        Class<?> clazz = bean.getClass();
        InjectionMetadata metadata = findAutowiringMetadata(clazz.getName(), clazz, null);
        try {
            metadata.inject(bean, null, null);
        }
        catch (BeanCreationException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    "Injection of autowired dependencies failed for class [" + clazz + "]", ex);
        }
    }

InjectionMetadata類包含要注入的元素的列表猖辫。注入是通過Java的API Reflection (Field set(Object obj, Object value) 或Method invoke(Object obj酥泞,Object ... args)方法完成的。此過程直接在AutowiredAnnotationBeanPostProcessor的方法中調(diào)用public void processInjection(Object bean) throws BeanCreationException啃憎。它將所有可注入的bean檢索為InjectionMetadata實例芝囤,并調(diào)用它們的inject()方法。

public class InjectionMetadata {
  ...
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
        Collection<InjectedElement> checkedElements = this.checkedElements;
        Collection<InjectedElement> elementsToIterate =
                (checkedElements != null ? checkedElements : this.injectedElements);
        if (!elementsToIterate.isEmpty()) {
            boolean debug = logger.isDebugEnabled();
            for (InjectedElement element : elementsToIterate) {
                if (debug) {
                    logger.debug("Processing injected element of bean '" + beanName + "': " + element);
                }
                //看下面靜態(tài)內(nèi)部類的方法
                element.inject(target, beanName, pvs);
            }
        }
    }
  ...
    public static abstract class InjectedElement {
        protected final Member member;
        protected final boolean isField;
      ...
        /**
         * Either this or {@link #getResourceToInject} needs to be overridden.
         */
        protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
                throws Throwable {
            if (this.isField) {
                Field field = (Field) this.member;
                ReflectionUtils.makeAccessible(field);
                field.set(target, getResourceToInject(target, requestingBeanName));
            }
            else {
                if (checkPropertySkipping(pvs)) {
                    return;
                }
                try {
                    //具體的注入看此處咯
                    Method method = (Method) this.member;
                    ReflectionUtils.makeAccessible(method);
                    method.invoke(target, getResourceToInject(target, requestingBeanName));
                }
                catch (InvocationTargetException ex) {
                    throw ex.getTargetException();
                }
            }
        }
      ...
    }
}

AutowiredAnnotationBeanPostProcessor類中的另一個重要方法是private AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao)辛萍。它通過分析屬于一個字段或一個方法的所有注解來查找@Autowired注解悯姊。如果未找到@Autowired注解,則返回null贩毕,字段或方法也就視為不可注入悯许。

@Nullable
    private AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao) {
        if (ao.getAnnotations().length > 0) {
            for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
                AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ao, type);
                if (attributes != null) {
                    return attributes;
                }
            }
        }
        return null;
    }

在上面的文章中,我們看到了Spring中自動注入過程辉阶。通過整篇文章可以看到岸晦,這種依賴注入是一種便捷易操作方式(可以在字段以及方法上完成),也促使我們逐漸在拋棄XML配置文件睛藻。還增強(qiáng)了代碼的可讀性。

原文: Spring5源碼解析-@Autowired

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邢隧,一起剝皮案震驚了整個濱河市店印,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倒慧,老刑警劉巖按摘,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纫谅,居然都是意外死亡炫贤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門付秕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兰珍,“玉大人,你說我怎么就攤上這事询吴÷雍樱” “怎么了亮元?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唠摹。 經(jīng)常有香客問我爆捞,道長,這世上最難降的妖魔是什么勾拉? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任煮甥,我火速辦了婚禮,結(jié)果婚禮上藕赞,老公的妹妹穿的比我還像新娘成肘。我一直安慰自己,他們只是感情好找默,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布艇劫。 她就那樣靜靜地躺著,像睡著了一般惩激。 火紅的嫁衣襯著肌膚如雪店煞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天风钻,我揣著相機(jī)與錄音顷蟀,去河邊找鬼。 笑死骡技,一個胖子當(dāng)著我的面吹牛鸣个,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播布朦,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼囤萤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了是趴?” 一聲冷哼從身側(cè)響起涛舍,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唆途,沒想到半個月后富雅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肛搬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年没佑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片温赔。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛤奢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情远剩,我是刑警寧澤扣溺,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站瓜晤,受9級特大地震影響锥余,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜痢掠,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一驱犹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧足画,春花似錦雄驹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至象缀,卻和暖如春蔬将,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背央星。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工霞怀, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人莉给。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓毙石,卻偏偏與公主長得像,于是被迫代替她去往敵國和親颓遏。 傳聞我的和親對象是個殘疾皇子徐矩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容