1. 程式人生 > >框架源碼系列七:Spring源碼學習之BeanDefinition源碼學習(BeanDefinition、Annotation 方式配置的BeanDefinition的解析)

框架源碼系列七:Spring源碼學習之BeanDefinition源碼學習(BeanDefinition、Annotation 方式配置的BeanDefinition的解析)

nvi 意圖 cat ans 我們 繼承體系 reference ann ply

一、BeanDefinition

1. bean定義都定義了什麽?

技術分享圖片

2、BeanDefinition的繼承體系

父類:

技術分享圖片

AttributeAccessor

可以在xmlbean定義裏面加上DTD文件裏面沒有的屬性,如

    <bean id="cbean" class="com.study.spring.samples.CBean" name="leeSamll" age="18">
        <constructor-arg type="String" value="cbean01"></constructor-arg>
</bean>

BeanMetadataElement :

定義bean定義來源於哪裏,在BeanDefinition 裏面的getResourceDescrption裏面獲取

子類:

技術分享圖片

類圖:

技術分享圖片

請思考:為什麽要增加AnnotatedBeanDefinition?用GenericBeanDefinition不可以嗎?是不是註解方式的Bean定義信息的存放及使用方式與通用的Bean定義方式不一樣了?

GenericBeanDefinition要定義的東西太多了,xml方式的處理和註解方式的處理可能不太一樣了,所以做了擴展加入AnnotatedBeanDefinition,是為了只定義自己需要的東西,後面直接從AnnotatedBeanDefinition獲取就行了

二、Annotation 方式配置的BeanDefinition的解析

1. 掃描的過程,如何掃描

技術分享圖片

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        
for (String basePackage : basePackages) { Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }

findCandidateComponents方法說明:

技術分享圖片

組件索引方式獲取bean定義:在pom.xml裏面加入spring-context-indexer這個依賴,在編譯的時候就會生成META-INF/spring.components文件(加了註解的類),然後bean定義就可以從spring.components裏面獲取,而不用在啟動的時候去包下面掃描獲取bean定義,變得很快

scanCandidateComponents方法說明:

技術分享圖片

MetadataReader說明:

技術分享圖片

說明:

獲取類的信息,不只只有反射,ASM也可以獲取,ASM通過獲取類的字節碼,參觀Class(ClassVisistor可以獲取到類上的註解和類對應的信息(類的屬性、類的方法等),用到的ASM組件有ClassReaderClassVisistor

2. 註解的解析

2.1 如何從掃到的 .class 文件中獲得註解信息?

我們自己實現是如何做的:

1)Class.forname("className")加載類獲得Class對象

2)反射獲取註解

3)判斷是否存在組件,存在為其創建BeanDefinition

4)看指定了名字沒,如果沒有,應用名字生成策略生成一個名字

5)註冊BeanDefinition

2.2 Spring的解析過程

spring解析註解利用的是ASM,ASM是一個低層次的字節碼操作庫

技術分享圖片

技術分享圖片

2.3 提問:spring 中通過 ASM 字節碼操作庫來讀取的類信息、註解信息。它沒有加載類,為什麽不用加載類的方式?

  因為加載類都要放到內存裏面,用不到的話內存就會浪費了,用ASM加載類不會進內存裏面。在spring的org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(String)的方法裏面的這段代碼讀取類的信息、註解信息的

MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

 * Copyright 2002-2009 the original author or authors.

package org.springframework.core.type.classreading;

import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;

/**
 * Simple facade for accessing class metadata,
 * as read by an ASM {@link org.springframework.asm.ClassReader}.
 *
 * @author Juergen Hoeller
 * @since 2.5
 */
public interface MetadataReader {

    /**
     * Return the resource reference for the class file.
     */
    Resource getResource();

    /**
     * Read basic class metadata for the underlying class.
     */
    ClassMetadata getClassMetadata();

    /**
     * Read full annotation metadata for the underlying class,
     * including metadata for annotated methods.
     */
    AnnotationMetadata getAnnotationMetadata();

}

2.4 MetadataReader讀取到類信息、註解信息後,如何進行判斷及創建BeanDefinition的,往BeanDefintion中給入了哪些信息。

在spring的org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(String)的方法裏面的這段代碼判斷和創建BeanDefinition的

                        if (isCandidateComponent(metadataReader)) {
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setResource(resource);
                            sbd.setSource(resource);
                            if (isCandidateComponent(sbd)) {
                                if (debugEnabled) {
                                    logger.debug("Identified candidate component class: " + resource);
                                }
                                candidates.add(sbd);
                            }
                            else {
                                if (debugEnabled) {
                                    logger.debug("Ignored because not a concrete top-level class: " + resource);
                                }
                            }
                        }

判斷是否是候選組件的方法:

    /**
     * Determine whether the given class does not match any exclude filter
     * and does match at least one include filter.
     * @param metadataReader the ASM ClassReader for the class
     * @return whether the class qualifies as a candidate component
     */
    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }

2.5 請思考,我們可以自己定義標註組件的註解嗎?【擴展點】

可以,示例如下:

package com.study.leesamll.spring.ext;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Component;

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyComponetAnno {

    String value() default "";
}

使用:

package com.study.leesamll.spring.service;

import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import com.study.leesmall.spring.ext.MyComponetAnno;

//@Componet
//@Service
@MyComponetAnno
public class Abean {

    @Autowired
    private ApplicationContext applicationContext;

    public Abean() {
        System.out.println("-----------------Abean 被實例化了。。。。。。。。。");
    }

    public void doSomething() {
        System.out.println(this + " do something .....mike.love="
                + this.applicationContext.getEnvironment().getProperty("mike.love"));
        System.out
                .println("-----------mike.name=" + this.applicationContext.getMessage("mike.name", null, Locale.CHINA));
    }
}

2.6 掃描的過濾這塊你在實際項目中是否用過?如何使用的?【擴展點】

示例:

package com.study.leesmall.spring.ext;

import java.io.IOException;

import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import com.study.leesmall.spring.service.Abean;

public class MyTypeFilter implements TypeFilter {

    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
            throws IOException {
        // 使用metadataReader中的類信息、註解信息來進行你的過濾判斷邏輯
        return metadataReader.getClassMetadata().getClassName().equals(Abean.class.getName());
    }

}

TypeFilter的子類:

技術分享圖片

技術分享圖片

請思考:像@Controller 註解,它和@Service、@Component 註解有不同的意圖,這種不同的意圖將會在哪裏實現?如果我們自己也有類似的需要自定義組件註解,是不就可以模仿@Controller。猜想 spring是如何做到靈活擴展這個的?

3、BeanDefinition註冊

在前面我們已經拿到BeanDefinition了,下面就是註冊BeanDefinition了

1.目標

1.1 搞清楚BeanFactory中BeanDefinition是如何存儲的?
1.2 搞清楚註冊的過程是怎樣的。

2. 思路

2.1 思考:我們原來是如何來存儲beanName,BeanDefinition的?
  用並發的Map來存,beanName作為鍵,BeanDefinition作為值
2.2 思考:我們註冊BeanDefinition的處理邏輯是怎樣的?
  首先判斷beanName是不是合法的,如果是合法的再放到Map裏面

3. 看spring的註冊過程

入口:DefaultListableBeanFactory.registerBeanDefinition(String beanName,BeanDefinitionbeanDefinition)

還是先拿到調用棧來分析:

技術分享圖片

處理BeanDefinition的過程:

技術分享圖片

String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

說明:Spring裏面默認的bean的名字的生成策略是拿類的名稱來當bean的名稱

看一下AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);方法裏面是怎麽處理註解BeanDefinition的:

技術分享圖片

下面就來看具體的註冊bean定義邏輯:

技術分享圖片

技術分享圖片

技術分享圖片

技術分享圖片

PS:

1.Maven怎麽加依賴時怎麽同時下載源碼?
Eclipse-window-preference-maven-勾選download artifact sources
下載慢的話把maven的setting.xml配置文件的鏡像地址換成阿裏的更快

完整代碼獲取地址:https://github.com/leeSmall/FrameSourceCodeStudy/tree/master/spring-source-study

框架源碼系列七:Spring源碼學習之BeanDefinition源碼學習(BeanDefinition、Annotation 方式配置的BeanDefinition的解析)