大战熟女丰满人妻av-荡女精品导航-岛国aaaa级午夜福利片-岛国av动作片在线观看-岛国av无码免费无禁网站-岛国大片激情做爰视频

專注Java教育14年 全國咨詢/投訴熱線:400-8080-105
動力節點LOGO圖
始于2009,口口相傳的Java黃埔軍校
首頁 hot資訊 Spring包掃描機制詳解

Spring包掃描機制詳解

更新時間:2021-09-10 10:54:20 來源:動力節點 瀏覽1985次

目標

此篇文章會主要介紹Spring中兩個非常重要的關于包掃描的基礎類,由于Spring代碼太龐大,因此本文不會細致地說明每一行代碼地作用,只會講清楚關鍵的地方有什么作用,以及一些子類可以重寫的方法,用來覆蓋默認掃描行為。最后會基于Spring提供的包掃描設施來寫一個簡單的例子來模仿MyBatis-Spring掃描Mapper接口,生成代理注冊到容器中。我們主要關注ClassPathScanningCandidateComponentProvider以及ClassPathBeanDefinitionScanner這兩個類,講清楚這兩個類的作用以及開發者需要關注的方法。

ClassPathScanningCandidateComponentProvider

此類是Spring中包掃描機制最底層的類,用于掃描指定包下面的類文件,并且會根據用戶提供的includeFilters以及excludeFilters來過濾掉不想注冊的類,最后生成一個基本的BeanDefinition。

先看下兩個比較重要的屬性吧

/**
 * 包含集合,如果類文件匹配includeFilters集合中任意一個TypeFilter條件,那么就通過篩選。
 * 其中最常見的TypeFilter有AnnotationTypeFilter、AssignableTypeFilter
 * AnnotationTypeFilter: 代表類是否被指定注解標注
 * AssignableTypeFilter: 代表類是否繼承(實現)自指定的超類
 */
private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
/**
 * 排除集合, 如果類文件匹配excludeFilters集合中任何一個TypeFilter條件,那么就不會通過篩選
 * 并且excludeFilters優先級高于includeFilters
 */
private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();

初始化,只看參數最長的那個構造方法

public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, 
                                                   Environment environment) {
    // 主要關注useDefaultFilters這個參數, 如果為true, 會注冊一個默認的
    // includeFilter
    if (useDefaultFilters) {
        registerDefaultFilters();
    }
    setEnvironment(environment);
    setResourceLoader(null);
}
protected void registerDefaultFilters() {
    // 注冊一個注解的TypeFilter,意思是如果類定義時有被@Component注解標注
    // 那么就會通過篩選,需要注意的是衍生注解也是會通過篩選的
    // 比如@Service、@Controller、@Repository,它們有一個共同點,那就是這三個
    // 注解本身就是被@Component注解標注的
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    // 下面是注冊JAVA EE里的一些注解,一般開發也不用, 就省略了
}

接下來就是最重要的方法了,也就是掃描包文件,并且將符合篩選條件的類生成BeanDefinition。下面代碼將日志打印剔除了。

/**
 * 此方法會掃描指定包以及子包
 * @param basePackage 需要掃描的包,形如com.wangtao.dao
 * @return 返回一個符合篩選條件后的BeanDefinition集合
 */
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
    try {
        // 將包名解析成路徑
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        // 獲取此包以及子包下的所有.class文件資源
        Resource[] resources = this.resourcePatternResolver.
            getResources(packageSearchPath);
        for (Resource resource : resources) {
            if (resource.isReadable()) {
                try {
                    // 得到類文件的元數據,是基于ASM字節碼技術實現的,此時還沒有加載類文件
                    // 包括類名、注解信息、父類、接口等等一系列信息
                    MetadataReader metadataReader = this.metadataReaderFactory.
                        getMetadataReader(resource);
                    // 匹配篩選條件,也就是上述includeFilters、excludeFilters這兩個集合
                    if (isCandidateComponent(metadataReader)) {
                        // 創建一個BeanDefinition
                        // 只是簡單的設置了beanClassName屬性為類的完全限定名
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBean
                            Definition(metadataReader);
                        sbd.setResource(resource);
                        sbd.setSource(resource);
                        // 這里會再有一個篩選條件,一般是根據類文件的元數據篩選
                        // 比如是不是具體類,是不是頂層類,是不是抽象類等
                        // 默認情況下只添加頂層的具體類,頂層的意思是可以獨立實例化而不會依賴外部類
                        // 成員內部類需要外部類對象才能實例化,就不會通過。
                        if (isCandidateComponent(sbd)) {
                            candidates.add(sbd);
                        }
                    }
                }
                catch (Throwable ex) {
                    throw new BeanDefinitionStoreException(
                        "Failed to read candidate component class: " + resource, ex);
                }
            }
        }
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException("I/O failure during classpath 
                                               scanning", ex);
    }
    return candidates;
}

再看看兩個isCandidateComponent方法的默認實現,一般來說我們可能需要重寫這兩個方法來改變默認的篩選條件。

// 根據excludeFilters、excludeFilters初步篩選
// 一目了然,基本不用再需要解釋
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    for (TypeFilter tf : this.excludeFilters) {
        if (tf.match(metadataReader, this.metadataReaderFactory)) {
            return false;
        }
    }
    for (TypeFilter tf : this.excludeFilters) {
        if (tf.match(metadataReader, this.metadataReaderFactory)) {
            return isConditionMatch(metadataReader);
        }
    }
    return false;
}
// 根據類文件元數據篩選
// 只掃描頂層具體類或者雖然是抽象類但是存在@Lookup標記的方法
// 后面那個是用于方法注入,我從來沒用過。
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		AnnotationMetadata metadata = beanDefinition.getMetadata();
		return (metadata.isIndependent() && (metadata.isConcrete() ||
				(metadata.isAbstract() && metadata.hasAnnotatedMethods(
                    Lookup.class.getName()))));

總結:此類在默認情況下會將指定包以及子包下中被@Component(以及衍生注解)標記的頂層類創建一個BeanDefinition。

說到這插下Spring開啟注解掃描的配置,有時我們可能只想在SpringMVC的配置文件中掃描@Controller標記的類,其它層掃描@Service、@Component、@Repository標記的類,就可以像下面這樣分層配置。

springmvc.xml

<context:component-scan base-package="com.wangtao.controller" 
                        use-default-filters="false">
    <context:include-filter 
        type="annotation" 
        expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

applicationContext.xml

<context:component-scan base-package="com.wangtao.service,com.wangtao.dao">
    <context:exclude-filter 
        type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

也就是說SpringMVC中禁掉默認的includeFilters,添加了一個使用@Controller標記的條件,而applicationContext.xml使用默認的includeFilters,但是排除對@Controller標記的類。

ClassPathBeanDefinitionScanner

此類繼承自ClassPathScanningCandidateComponentProvider,除了擁有父類掃描包的功能外,還會對掃描后的BeanDefinition加工并注冊到Spring容器中,所謂的加工就是指會設置一些類文件中用注解標記的一些屬性值,如@Lazy、@Scope、@Primary等。

先看一些重要屬性

/** 用于注冊bean **/
private final BeanDefinitionRegistry registry;
/** 
 * 此類存儲了BeanDefinition一些默認屬性值
 * lazyInit: false
 * autowireMode: AbstractBeanDefinition.AUTOWIRE_NO
 * initMethodName: null
 * destroyMethodName: null
**/
private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults();
/** bean name 生成器 **/
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
/**
 * 默認值:true
 * 相當于開啟<context:annotation-config>
 * 意味著我們可以使用@Autowired、@Resource、@PostConstruct、@PreDestroy注解
 * 會自動幫我們注冊解析這幾個注解的BeanPostProcessor
 */
private boolean includeAnnotationConfig = true;

構造方法

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, 
                                      boolean useDefaultFilters,
                                      Environment environment, 
                                      ResourceLoader resourceLoader) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    this.registry = registry;
    // 同ClassPathScanningCandidateComponentProvider
    if (useDefaultFilters) {
        registerDefaultFilters();
    }
    setEnvironment(environment);
    setResourceLoader(resourceLoader);
}

接下來看最重要的掃描方法

/**
 * 返回掃描真正注冊bean的數量
 */
public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    doScan(basePackages);
	// 注冊幾個BeanPostProcessor用來解析@Autowired、@Reource等幾個注解
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }
    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
/**
 * 將BeanDefinition集合返回,子類若有必要可以繼續對BeanDefinition做修改
 */
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new 
        LinkedHashSet<BeanDefinitionHolder>();
    for (String basePackage : basePackages) {
        // 得到所有符合掃描條件的BeanDefinition集合,接下來會對這些BeanDefinition加工
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            // 主要包括bean的scope屬性(默認單例)以及代理策略(不需要代理,JDK動態代理、CGLIB)
            // 來適配AOP
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver
                .resolveScopeMetadata(candidate);
            // 設置scope屬性
            candidate.setScope(scopeMetadata.getScopeName());
            // 生成bean name
            String beanName = this.beanNameGenerator
                .generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                // 根據beanDefinitionDefaults設置一些默認值
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            // 如果是注解定義的Bean, findCandidateComponents默認實現返回的BeanDefinition
            // 是一個ScannedGenericBeanDefinition,其實現了AnnotatedBeanDefinition接口
            if (candidate instanceof AnnotatedBeanDefinition) {
                // 解析@Scope、@Primary、@Lazy等屬性并設置到BeanDefinition中
                AnnotationConfigUtils.processCommonDefinitionAnnotations(
                    (AnnotatedBeanDefinition) candidate);
            }
            // 檢查BeanDefinition
            // 主要檢查這個容器中是否已經存在此BeanDefinition
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = 
                    new BeanDefinitionHolder(candidate, beanName);
                // 設置代理策略
                definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(
                    scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                // 注冊到Spring容器中
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

因此如果我們需要自定義掃描實現bean的注冊,基本上就是要繼承ClassPathBeanDefinitionScanner并且重寫doScan方法了。大致框架就是

public class MyScanner extends ClassPathBeanDefinitionScanner {    
    public MyScanner(BeanDefinitionRegistry registry) {
        super(registry)
    }   
    @Overide
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // 父類已經將這些bean注冊了
        Set<BeanDefinitionHolder> holders = super.doScan(basePackages);
        for(BeanDefinitionHolder holder : holders) {
            // 在這里修改BeanDefinition引用的對象即可
        }
        return holders;
    }
}

以上就是動力節點小編介紹的"Spring包掃描機制詳解",希望對大家有幫助,想了解更多可查看Spring框架教程。動力節點在線學習教程,針對沒有任何Java基礎的讀者學習,讓你從入門到精通,主要介紹了一些Java基礎的核心知識,讓同學們更好更方便的學習和了解Java編程,感興趣的同學可以關注一下。

提交申請后,顧問老師會電話與您溝通安排學習

免費課程推薦 >>
技術文檔推薦 >>
主站蜘蛛池模板: 日本在线观看中文字幕 | 国产日韩欧美一区二区 | 亚洲欧美日韩国产综合专区 | 久久只有这里有精品 | 亚洲在线国产 | 精品中文字幕一区在线 | 国产欧美综合一区二区 | 久久国产精品二国产精品 | 日韩男人天堂 | 午夜影院一区二区 | 亚洲欧美日韩高清专区一区 | 国产一级αv片免费观看 | 九九久久国产精品大片 | 91精品国产免费久久国语麻豆 | 嘿咻视频在线观看 | 99久久99久久久精品久久 | 在线播放 亚洲 | 中文字幕一区日韩在线视频 | 亚洲欧美一区在线 | 精品视频999 | 青青草免费在线视频 | 在线看国产精品 | 四虎中文 | 欧美日韩中出 | 日韩伊人| 草草影院一级毛片a级 | 国产swag在线观看 | 天天干人人干 | 国产精品成人四虎免费视频 | 国产码欧美日韩高清综合一区 | 中国大陆一级毛片 免费 | 亚洲欧美日韩国产一区二区精品 | 欧美色视频日本片免费高清 | 欧美亚洲一区二区三区 | 亚洲国产精品久久久久婷婷软件 | 天海翼精品久久中文字幕 | 国产在线视频99 | 天天爱天天做天天爽天天躁 | julia紧身裙中文字幕在线看 | 日韩免费在线 | 亚洲视频在线网站 |